#!/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 "$!"; }