about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndinus <andinus@nand.sh>2021-01-13 11:29:00 +0530
committerAndinus <andinus@nand.sh>2021-01-13 11:29:00 +0530
commitc0a2300b52c682975fbe365de21547db71d18a38 (patch)
treeb1d7c030badefbcf81c23f4a2eb97ed1899d4791
parent8aee745720995831cfe2a288d0945ce20f677a0e (diff)
downloadleo-0.6.0.tar.gz
Port leo to Raku HEAD v0.6.0 master
This is a huge change. I had some more ideas in mind but I won't be
able to work on them. leo's raku version can be improved.

I'm personally using the Raku version because the rotate feature is
nice.
-rw-r--r--.gitignore1
-rw-r--r--Makefile13
-rw-r--r--README19
-rw-r--r--README.org36
-rw-r--r--leo.142
-rwxr-xr-xleo.raku249
-rw-r--r--share/leo.126
-rw-r--r--share/leo.toml25
8 files changed, 353 insertions, 58 deletions
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.