diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | README | 19 | ||||
-rw-r--r-- | README.org | 36 | ||||
-rw-r--r-- | leo.1 | 42 | ||||
-rwxr-xr-x | leo.raku | 249 | ||||
-rw-r--r-- | share/leo.1 | 26 | ||||
-rw-r--r-- | share/leo.toml | 25 |
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. |