From c0a2300b52c682975fbe365de21547db71d18a38 Mon Sep 17 00:00:00 2001 From: Andinus Date: Wed, 13 Jan 2021 11:29:00 +0530 Subject: Port leo to Raku 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. --- .gitignore | 1 - Makefile | 13 +-- README | 19 ++--- README.org | 36 +++++++++ leo.1 | 42 ---------- leo.raku | 249 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/leo.1 | 26 ++++++ share/leo.toml | 25 ++++++ 8 files changed, 353 insertions(+), 58 deletions(-) delete mode 100644 .gitignore create mode 100644 README.org delete mode 100644 leo.1 create mode 100755 leo.raku create mode 100644 share/leo.1 create mode 100644 share/leo.toml 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: • Source: @@ -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] + + +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 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 ?? + %*ENV ~ "/leo.toml" !! %*ENV ~ "/.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.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 ?? $config !! "/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{$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{$profile} // $config) or + ($config{$profile} // $config) + ) { + note "\nCalling gnupg subroutine" if $verbose; + gnupg($profile, $backup_file, $config, $verbose); + } + } + when 'list' { + say "Encryption ", ($config{$profile} // + $config) ?? + "ON" !! "OFF"; + + say "GnuPG sign ", ($config{$profile} // + $config) ?? + "ON" !! "OFF"; + + say "Holding ", ( + $config{$profile} // + $config // $default_hold + ), " backups"; + + say "Keeping ", ( + $config{$profile} // + $config // $default_keep + ), " backups"; + + say "Base directory: ", + ($config{$profile} // + $config // + "/"); + + say "\nPaths: "; + say " " x 4, $_ for @($config{$profile} // + "."); + + if $config{$profile}.defined { + say "\nExclude: "; + say " " x 4, $_ for @($config{$profile}); + } + } + 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{$profile} // + $config // $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{$profile} // + $config // $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{$profile} // + $config // + "/" + ); + 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{$profile} // ".") + ) -> $path { + if $path.IO.d { + push @backup_paths, $_.Str for dir $path; + } else { + push @backup_paths, $path; + } + } + + run , @options, ( + @backup_paths (-) @($config{$profile}) + ).keys; +} + +sub gnupg ( + Str $profile, IO $backup_file, Config $config, Bool $verbose +) { + my Str @options = '--yes'; + + if ( + $config{$profile} // + $config + ) { + push @options, '--encrypt'; + with $config { + push @options, "--recipient", $_ for @($_); + } + } + + push @options, $verbose ?? '--verbose' !! '--quiet'; + push @options, '--sign' if ( + $config{$profile} // + $config + ); + push @options, '--default-key', $_ with $config; + + run , @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] [ ...] +.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 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. -- cgit 1.4.1-2-gfad0