diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 143d814..0000000
--- a/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.org
diff --git a/Makefile b/Makefile
index 7030f2e..2765ac3 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ PREFIX?=/usr/local
INSTALL?=install
INSTALL_PROGRAM=$(INSTALL) -Dm 755
-INSTALL_DATA=$(INSTALL) -Dm 644
+INSTALL_DATA=install -Dm 644
bindir=$(DESTDIR)$(PREFIX)/bin
sharedir=$(DESTDIR)$(PREFIX)/share
@@ -24,13 +24,14 @@ help:
| sed -n 's/^\(.*\): \(.*\)#\(.*\)/ \1|-\3/p' \
| column -t -s '|'
-install: leo.pl leo.1 share/leo.conf README # system install
- $(INSTALL_PROGRAM) leo.pl $(bindir)/leo
+install: leo.raku share/leo.1 share/leo.toml README README.org # system install
+ $(INSTALL_PROGRAM) leo.raku $(bindir)/leo
- $(INSTALL_DATA) leo.1 $(mandir)/man1/leo.1
- $(INSTALL_DATA) share/leo.conf $(sharedir)/leo/leo.conf
- $(INSTALL_DATA) README $(sharedir)/doc/leo/README
+ $(INSTALL_DATA) share/leo.1 $(mandir)/man1/leo.1
+ $(INSTALL_DATA) share/leo.toml $(sharedir)/leo/leo.toml
+ $(INSTALL_DATA) README $(sharedir)/doc/leo/README
+ $(INSTALL_DATA) README.org $(sharedir)/doc/leo/README.org
uninstall: # system uninstall
rm -f $(bindir)/leo
diff --git a/README b/README
index f05b18b..715a718 100644
--- a/README
+++ b/README
@@ -10,11 +10,11 @@ Table of Contents
1. Installation
2. Demo
+3. History
Leo is a simple backup program. It creates tar(1) files from a
-pre-defined list. It can encrypt/sign files with gpg2(1) & sign files
-with signify(1).
+pre-defined list. It can encrypt/sign files with gpg2(1).
• Web-site: <https://andinus.nand.sh/leo>
• Source: <https://git.tilde.institute/andinus/leo>
@@ -29,14 +29,8 @@ with signify(1).
│ git clone https://git.tilde.institute/andinus/leo
│ cd leo
│
- │ # Install dependencies with cpanm.
- │ cpanm --installdeps .
- │
- │ # Install all the dependencies.
- │ doas pkg_add p5-IPC-Run3 p5-Config-Tiny p5-Path-Tiny # OpenBSD only.
- │
│ # Copy the config.
- │ cp share/leo.conf $HOME/.config/leo.conf
+ │ cp share/leo.toml $HOME/.config/leo.toml
│
│ # Copy the script & make it executable.
│ sudo make install
@@ -59,3 +53,10 @@ with signify(1).
[cast file]
<https://andinus.nand.sh/static/leo/2020-08-31_leo-demo.cast>
+
+
+3 History
+═════════
+
+ Leo was a Perl script until v0.5.1, it was ported to Raku in next
+ release.
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..6c80270
--- /dev/null
+++ b/README.org
@@ -0,0 +1,36 @@
+#+SETUPFILE: ~/.emacs.d/org-templates/projects.org
+#+EXPORT_FILE_NAME: index
+#+OPTIONS: toc:3
+#+TITLE: Leo
+
+Leo is a simple backup program. It creates tar(1) files from a
+pre-defined list. It can encrypt/sign files with gpg2(1).
+
+- Web-site: https://andinus.nand.sh/leo
+- Source: https://git.tilde.institute/andinus/leo
+- Source (mirror): https://github.com/andinus/leo
+
+* Installation
+#+BEGIN_SRC sh
+# Clone the project.
+git clone https://git.tilde.institute/andinus/leo
+cd leo
+
+# Copy the config.
+cp share/leo.toml $HOME/.config/leo.toml
+
+# Copy the script & make it executable.
+sudo make install
+#+END_SRC
+* Demo
+It's very easy to setup =leo=, I made a demo video to show this. I already
+have Perl environment setup for this.
+
+*Note*: Leo has changed *a lot* since this was published.
+
+- Leo 2020-08-31: https://asciinema.org/a/F97hVfgXDcd9g5IlST1t27ps3
+
+You can also download the [[https://andinus.nand.sh/static/leo/2020-08-31_leo-demo.cast][cast file]] directly & play it with =asciinema=.
+* History
+Leo was a Perl script until v0.5.1, it was ported to Raku in next
+release.
diff --git a/leo.1 b/leo.1
deleted file mode 100644
index 7cb7cfd..0000000
--- a/leo.1
+++ /dev/null
@@ -1,42 +0,0 @@
-.TH LEO 1 "21 November 2020" "v0.5.1"
-
-.SH NAME
-leo \- a simple backup program
-
-.SH SYNOPSIS
-.B leo
-[-hpPvV] profiles
-.P
-
-.SH DESRIPTION
-.B leo
-is a simple backup program. It creates tar(1) files from a pre-defined
-list. It can encrypt/sign files with gpg2(1) & sign files with
-signify(1).
-
-I use this to quickly backup some of my files. It works on profiles,
-profiles are simple lists of files which get backed up.
-.SH PROFILE
-Profile is a simple hash table which contains the list of profiles.
-The profiles are mapped to a list of file paths which are to be backed
-up.
-.SH OPTIONS
-The options are as follows:
-.TP
-.B -h
-Print help.
-.TP
-.B -p
-Print profiles.
-.TP
-.B -P
-Print profiles with all the files to backup/exclude.
-.TP
-.B -v
-Increase verbosity.
-.TP
-.B -V
-Print version.
-.TP
-.SH AUTHOR
-Andinus <andinus@nand.sh>
diff --git a/leo.raku b/leo.raku
new file mode 100755
index 0000000..365e3ad
--- /dev/null
+++ b/leo.raku
@@ -0,0 +1,249 @@
+#!/usr/bin/env raku
+
+use v6.d;
+use Config;
+
+enum Actions (
+ backup => "Make backup",
+ rotate => "Rotate the backups",
+ list => "List profiles / profile options",
+);
+
+sub USAGE {
+ say $*USAGE;
+ say "\nActions:";
+ for Actions.enums.kv -> $action, $description {
+ say " " x 4, $action,
+ " " x (Actions.enums.keys.map(*.chars).max - $action.chars) + 4,
+ $description;
+ }
+}
+
+sub read-configuration(--> Config) {
+ return Config.new.read(
+ %*ENV<XDG_CONFIG_HOME> ??
+ %*ENV<XDG_CONFIG_HOME> ~ "/leo.toml" !! %*ENV<HOME> ~ "/.config/leo.toml"
+ );
+}
+
+# If nothing is passed then named argument multi prevails so we just
+# create one so that USAGE() gets printed if no argument is passed.
+multi sub MAIN(Bool :$help-implied) is hidden-from-USAGE { USAGE() }
+multi sub MAIN(Bool :$version) is hidden-from-USAGE { say "Leo v0.6.0" }
+
+multi sub MAIN(Actions $action, Bool :v(:$verbose)) is hidden-from-USAGE {
+ note 'Verbosity on' if $verbose;
+ note 'Dispatched Actions only sub' if $verbose;
+ note 'Reading configuration' if $verbose;
+
+ my Config $config = read-configuration();
+
+ given $action.key {
+ when 'list' {
+ say "Profiles:";
+ say " " x 4, $_ for $config<profiles>.keys.sort;
+ exit 0;
+ }
+ default {
+ note "(!!) No profile passed";
+ exit 1;
+ }
+ }
+}
+
+multi sub MAIN(
+ Actions $action, #= action to perform
+ *@profiles, #= profiles to perform the action on
+ Bool :v(:$verbose), #= increase verbosity
+ Bool :$version, #= print version
+) {
+ note 'Verbosity on' if $verbose;
+ note 'Reading configuration' if $verbose;
+
+ my Config $config = read-configuration();
+
+ # Default number of backups to hold / keep.
+ my int ($default_hold, $default_keep) = (1, 2);
+
+ # $backup_dir holds path to backup directory.
+ my Str $backup_dir;
+ my DateTime $date = DateTime.new(time);
+
+ $backup_dir = $config<backup> ?? $config<backup> !! "/tmp/backups";
+
+ # Fix $backup_dir permission.
+ unless $backup_dir.IO.mode eq "0700" {
+ note "Setting mode to 700: '$backup_dir'" if $verbose;
+ $backup_dir.IO.chmod(0o700);
+ }
+
+ for @profiles -> $profile {
+ say "[$profile]";
+ without ($config<profiles>{$profile}) {
+ note "(!!) No such profile";
+ exit 1;
+ }
+
+ my Str $profile_dir = $backup_dir ~ "/$profile";
+
+ # Create the profile backup directory if it doesn't exist.
+ unless $profile_dir.IO ~~ :d {
+ note "Creating profile backup directory: '$profile_dir'" if $verbose;
+ mkdir($profile_dir, 0o700) or die "$!";
+ }
+
+ given $action.key {
+ when 'backup' {
+ my IO $backup_file = "$profile_dir/$date.tgz".IO;
+
+ say "Backup: ", $backup_file.Str;
+
+ note "\nCalling backup-profile subroutine" if $verbose;
+ backup-profile($profile, $backup_file, $config, $verbose);
+
+ if (
+ ($config<profiles>{$profile}<encrypt> // $config<gpg><encrypt>) or
+ ($config<profiles>{$profile}<sign> // $config<gpg><sign>)
+ ) {
+ note "\nCalling gnupg subroutine" if $verbose;
+ gnupg($profile, $backup_file, $config, $verbose);
+ }
+ }
+ when 'list' {
+ say "Encryption ", ($config<profiles>{$profile}<encrypt> //
+ $config<gpg><encrypt>) ??
+ "ON" !! "OFF";
+
+ say "GnuPG sign ", ($config<profiles>{$profile}<sign> //
+ $config<gpg><sign>) ??
+ "ON" !! "OFF";
+
+ say "Holding ", (
+ $config<profiles>{$profile}<rotate><hold> //
+ $config<rotate><hold> // $default_hold
+ ), " backups";
+
+ say "Keeping ", (
+ $config<profiles>{$profile}<rotate><keep> //
+ $config<rotate><keep> // $default_keep
+ ), " backups";
+
+ say "Base directory: ",
+ ($config<profiles>{$profile}<base_dir> //
+ $config<base_dir> //
+ "/");
+
+ say "\nPaths: ";
+ say " " x 4, $_ for @($config<profiles>{$profile}<paths> //
+ ".");
+
+ if $config<profiles>{$profile}<exclude>.defined {
+ say "\nExclude: ";
+ say " " x 4, $_ for @($config<profiles>{$profile}<exclude>);
+ }
+ }
+ when 'rotate' {
+ my DateTime %backups;
+ for dir $profile_dir -> $path {
+ next unless $path.f;
+ if $path.Str ~~ /$profile '/' (.*)
+ ['.tar'|'.tar.gpg'|'.tgz'|'.tgz.gpg']$/ -> $match {
+ %backups{$path.Str} = DateTime.new($match[0].Str);
+ }
+ }
+
+ say "Total backups: ", %backups.elems;
+ exit 0 if %backups.elems == 0; # Exit if 0 backups.
+
+ # Hold the backups that are to be held. Default is to hold 1
+ # backup.
+ for %backups.sort(*.value).map(*.key).head(
+ $config<profiles>{$profile}<rotate><hold> //
+ $config<rotate><hold> // $default_hold
+ ) {
+ note "(H) Holding: ", $_ if $verbose;
+ %backups{$_}:delete;
+ }
+
+ # We'll keep `n' latest backups where `n' equals to
+ # the number of backups we want to keep. Default is to
+ # keep 2 backups.
+ for %backups.sort(*.value).map(*.key).tail(
+ $config<profiles>{$profile}<rotate><keep> //
+ $config<rotate><keep> // $default_keep
+ ) {
+ note "(K) Keeping: ", $_ if $verbose;
+ %backups{$_}:delete;
+ }
+
+ # Now we just remove all backups in %backups.
+ for %backups -> $backup {
+ note "(D) Deleting: ", $backup.key if $verbose;
+ unlink($backup.key);
+ }
+ }
+ default {
+ note "Invalid action";
+ exit 1;
+ }
+ }
+ }
+}
+
+sub backup-profile (
+ Str $profile, IO $backup_file, Config $config, Bool $verbose
+) {
+ my IO $base_dir = $_.IO with (
+ $config<profiles>{$profile}<base_dir> //
+ $config<base_dir> //
+ "/"
+ );
+ chdir $base_dir or die "$!";
+
+ my Str @options = <-c -z>;
+ push @options, '-vv' if $verbose;
+ push @options, '-f', $backup_file.Str;
+ push @options, '-C', $base_dir.Str;
+
+ my Str @backup_paths;
+ for (
+ @($config<profiles>{$profile}<paths> // ".")
+ ) -> $path {
+ if $path.IO.d {
+ push @backup_paths, $_.Str for dir $path;
+ } else {
+ push @backup_paths, $path;
+ }
+ }
+
+ run <tar>, @options, (
+ @backup_paths (-) @($config<profiles>{$profile}<exclude>)
+ ).keys;
+}
+
+sub gnupg (
+ Str $profile, IO $backup_file, Config $config, Bool $verbose
+) {
+ my Str @options = '--yes';
+
+ if (
+ $config<profiles>{$profile}<encrypt> //
+ $config<gpg><encrypt>
+ ) {
+ push @options, '--encrypt';
+ with $config<gpg><recipients> {
+ push @options, "--recipient", $_ for @($_);
+ }
+ }
+
+ push @options, $verbose ?? '--verbose' !! '--quiet';
+ push @options, '--sign' if (
+ $config<profiles>{$profile}<sign> //
+ $config<gpg><sign>
+ );
+ push @options, '--default-key', $_ with $config<gpg><fingerprint>;
+
+ run <gpg2>, @options, $backup_file;
+
+ unlink($backup_file) or die "$!";
+}
diff --git a/share/leo.1 b/share/leo.1
new file mode 100644
index 0000000..cfd3d73
--- /dev/null
+++ b/share/leo.1
@@ -0,0 +1,26 @@
+.TH LEO 1 "04 January 2021" "v0.6.0"
+
+.SH NAME
+leo \- a simple backup program
+.SH SYNOPSIS
+.B leo
+[-v|--verbose] [--version] <action> [<profiles> ...]
+.P
+.SH DESRIPTION
+.B leo
+is a simple backup program. It creates tar(1) files from a pre-defined
+list. It can also encrypt/sign files with gpg2(1).
+
+I use this to quickly backup some of my files. It works on profiles,
+profiles are simple lists of files which get backed up.
+.SH ACTIONS
+The actions are as follows:
+.TP
+.B backup
+Backup the given profile.
+.TP
+.B rotate
+Rotate the given profile's backups.
+.TP
+.SH AUTHOR
+Andinus <andinus@nand.sh>
diff --git a/share/leo.toml b/share/leo.toml
new file mode 100644
index 0000000..0336d4e
--- /dev/null
+++ b/share/leo.toml
@@ -0,0 +1,25 @@
+backup = "/tmp/backups"
+base_dir = "/home/andinus" # Default base_dir.
+
+[gpg] # GnuPG related configuration.
+sign = true
+encrypt = true
+
+fingerprint = "andinus@nand.sh"
+recipients = ["D9AE4AEEE1F1B3598E81D9DFB67D55D482A799FD"]
+
+[rotate]
+hold = 1 # Hold these many old backups.
+keep = 2 # Keep these many latest backups.
+
+[profiles]
+[profiles.journal]
+encrypt = false
+paths = ["journal.org.gpg"]
+ [profiles.journal.rotate]
+ hold = 2
+ keep = 4
+
+[profiles.projects]
+base_dir = "/home/andinus/projects"
+# Not specifying paths is okay, it'll just archive base_dir.
|