Enhances backup wrapper functionality
• Adds comprehensive logging and colored output for better user feedback • Introduces robust CLI argument parsing with usage help • Implements a monitoring loop to trigger backups on file changes • Updates wrapper invocations to use new flags for improved configuration
This commit is contained in:
parent
5ad86d90df
commit
0657e04abf
2 changed files with 319 additions and 58 deletions
|
@ -1,84 +1,345 @@
|
||||||
{
|
{ pkgs, ... }:
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
pkgs.writeScript "backup-wrapper" ''
|
pkgs.writeScript "backup-wrapper" ''
|
||||||
#!/usr/bin/env fish
|
#!/usr/bin/env fish
|
||||||
|
|
||||||
## Helpers ##
|
#==========================================================#
|
||||||
function backup
|
# Function definitions #
|
||||||
# Uses $src, $dest, $max_backups from outer scope
|
#==========================================================#
|
||||||
set timestamp (date +%Y%m%d-%H%M%S)
|
|
||||||
set outfile "$dest/backup-$timestamp.tar.zst"
|
|
||||||
|
|
||||||
mkdir -p $dest
|
# Set up colors for prettier output
|
||||||
echo "→ Creating backup: $outfile"
|
set -l blue (set_color blue)
|
||||||
|
set -l green (set_color green)
|
||||||
|
set -l yellow (set_color yellow)
|
||||||
|
set -l red (set_color red)
|
||||||
|
set -l cyan (set_color cyan)
|
||||||
|
set -l magenta (set_color magenta)
|
||||||
|
set -l bold (set_color --bold)
|
||||||
|
set -l normal (set_color normal)
|
||||||
|
|
||||||
tar cf - $src |
|
# Define log file path
|
||||||
${pkgs.zstd}/bin/zstd -c -T5 -15 -v > $outfile
|
set -g log_file ""
|
||||||
|
|
||||||
# Rotate: keep only the newest $max_backups
|
function setup_logging
|
||||||
set files (ls -1t $dest/backup-*.tar.zst)
|
set -g log_file "$argv[1]/backup.log"
|
||||||
if test (count $files) -gt $max_backups
|
echo "# Backup Wrapper Log - Started at "(date) > $log_file
|
||||||
for f in $files[(math "$max_backups + 1")..-1]
|
echo "# =====================================================" >> $log_file
|
||||||
rm $f
|
end
|
||||||
echo " • Removed old backup: $f"
|
|
||||||
|
# Use conditional tee: if log_file is set, tee output; otherwise echo normally.
|
||||||
|
function print_header
|
||||||
|
set -l header "$blue═══════════════[ $bold$argv[1]$normal$blue ]═══════════════$normal"
|
||||||
|
if test -n "$log_file"
|
||||||
|
echo $header | tee -a $log_file
|
||||||
|
else
|
||||||
|
echo $header
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function print_step
|
||||||
|
set -l msg "$green→ $bold$argv[1]$normal"
|
||||||
|
if test -n "$log_file"
|
||||||
|
echo $msg | tee -a $log_file
|
||||||
|
else
|
||||||
|
echo $msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function print_info
|
||||||
|
set -l msg "$cyan•$normal $argv[1]"
|
||||||
|
if test -n "$log_file"
|
||||||
|
echo $msg | tee -a $log_file
|
||||||
|
else
|
||||||
|
echo $msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function print_warning
|
||||||
|
set -l msg "$yellow⚠$normal $argv[1]"
|
||||||
|
if test -n "$log_file"
|
||||||
|
echo $msg | tee -a $log_file >&2
|
||||||
|
else
|
||||||
|
echo $msg >&2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function print_error
|
||||||
|
set -l msg "$red✖$normal $argv[1]"
|
||||||
|
if test -n "$log_file"
|
||||||
|
echo $msg | tee -a $log_file >&2
|
||||||
|
else
|
||||||
|
echo $msg >&2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function print_success
|
||||||
|
set -l msg "$green✓$normal $argv[1]"
|
||||||
|
if test -n "$log_file"
|
||||||
|
echo $msg | tee -a $log_file
|
||||||
|
else
|
||||||
|
echo $msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function print_usage
|
||||||
|
print_header "Backup Wrapper Usage"
|
||||||
|
if test -n "$log_file"
|
||||||
|
echo "Usage: backup_wrapper [OPTIONS] -- COMMAND [ARGS...]" | tee -a $log_file
|
||||||
|
echo "Options:" | tee -a $log_file
|
||||||
|
echo " -p, --path PATH Path to backup" | tee -a $log_file
|
||||||
|
echo " -o, --output PATH Output directory for backups" | tee -a $log_file
|
||||||
|
echo " -m, --max NUMBER Maximum number of backups to keep (default: 5)" | tee -a $log_file
|
||||||
|
echo " -d, --delay SECONDS Delay before backup after changes (default: 5)" | tee -a $log_file
|
||||||
|
echo " -h, --help Show this help message" | tee -a $log_file
|
||||||
|
else
|
||||||
|
echo "Usage: backup_wrapper [OPTIONS] -- COMMAND [ARGS...]"
|
||||||
|
echo "Options:"
|
||||||
|
echo " -p, --path PATH Path to backup"
|
||||||
|
echo " -o, --output PATH Output directory for backups"
|
||||||
|
echo " -m, --max NUMBER Maximum number of backups to keep (default: 5)"
|
||||||
|
echo " -d, --delay SECONDS Delay before backup after changes (default: 5)"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is the critical function - needs to return *only* the backup file path
|
||||||
|
function backup_path
|
||||||
|
set -l src $argv[1]
|
||||||
|
set -l out $argv[2]
|
||||||
|
set -l timestamp (date +"%Y%m%d-%H%M%S")
|
||||||
|
set -l backup_file "$out/backup-$timestamp.tar.zst"
|
||||||
|
|
||||||
|
# Log messages to stderr so they don't interfere with the function output
|
||||||
|
echo "$green→$normal Backing up to $yellow$backup_file$normal" >&2 | tee -a $log_file
|
||||||
|
pushd (dirname "$src") >/dev/null
|
||||||
|
tar cf - (basename "$src") | ${pkgs.zstd}/bin/zstd -c -T5 -15 > "$backup_file" 2>> $log_file
|
||||||
|
set -l exit_status $status
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
if test $exit_status -eq 0
|
||||||
|
# IMPORTANT: Only output the backup file path, nothing else
|
||||||
|
echo $backup_file
|
||||||
|
else
|
||||||
|
echo "$red✖$normal Backup operation failed!" >&2 | tee -a $log_file
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function rotate_backups
|
||||||
|
set -l output_dir $argv[1]
|
||||||
|
set -l max_backups $argv[2]
|
||||||
|
|
||||||
|
set -l backups (ls -t "$output_dir"/backup-*.tar.zst 2>/dev/null)
|
||||||
|
set -l num_backups (count $backups)
|
||||||
|
|
||||||
|
if test $num_backups -gt $max_backups
|
||||||
|
print_step "Rotating backups, keeping $max_backups of $num_backups"
|
||||||
|
for i in (seq (math "$max_backups + 1") $num_backups)
|
||||||
|
print_info "Removing old backup: $yellow$backups[$i]$normal"
|
||||||
|
rm -f "$backups[$i]"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function periodic_backups --argument-names pid
|
#==========================================================#
|
||||||
while true
|
# Argument parsing #
|
||||||
# sleep in 1s increments so we can detect process exit early
|
#==========================================================#
|
||||||
for i in (seq 1 $interval)
|
|
||||||
if not test -d /proc/$pid
|
|
||||||
return
|
|
||||||
end
|
|
||||||
sleep 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# If we're still here, do the interval backup
|
# Parse arguments
|
||||||
echo "Interval backup at "(date)
|
set -l backup_path ""
|
||||||
backup
|
set -l output_dir ""
|
||||||
|
set -l max_backups 5
|
||||||
|
set -l delay 5
|
||||||
|
set -l cmd ""
|
||||||
|
|
||||||
|
while count $argv > 0
|
||||||
|
switch $argv[1]
|
||||||
|
case -h --help
|
||||||
|
print_usage
|
||||||
|
exit 0
|
||||||
|
case -p --path
|
||||||
|
set -e argv[1]
|
||||||
|
set backup_path $argv[1]
|
||||||
|
set -e argv[1]
|
||||||
|
case -o --output
|
||||||
|
set -e argv[1]
|
||||||
|
set output_dir $argv[1]
|
||||||
|
set -e argv[1]
|
||||||
|
case -m --max
|
||||||
|
set -e argv[1]
|
||||||
|
set max_backups $argv[1]
|
||||||
|
set -e argv[1]
|
||||||
|
case -d --delay
|
||||||
|
set -e argv[1]
|
||||||
|
set delay $argv[1]
|
||||||
|
set -e argv[1]
|
||||||
|
case --
|
||||||
|
set -e argv[1]
|
||||||
|
set cmd $argv
|
||||||
|
break
|
||||||
|
case '*'
|
||||||
|
print_error "Unknown option $argv[1]"
|
||||||
|
print_usage
|
||||||
|
exit 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#==========================================================#
|
||||||
|
# Validation & Setup #
|
||||||
|
#==========================================================#
|
||||||
|
|
||||||
## Arg parsing ##
|
# Ensure the output directory exists
|
||||||
if test (count $argv) -lt 5
|
mkdir -p "$output_dir" 2>/dev/null
|
||||||
echo "Usage: $argv[0] <src> <dest> <interval_s> <max_backups> -- <program> [args...]"
|
|
||||||
|
# Set up logging
|
||||||
|
setup_logging "$output_dir"
|
||||||
|
|
||||||
|
print_header "Backup Wrapper Starting"
|
||||||
|
|
||||||
|
# Log the original command
|
||||||
|
echo "# Original command: $argv" >> $log_file
|
||||||
|
|
||||||
|
# Validate arguments
|
||||||
|
if test -z "$backup_path" -o -z "$output_dir" -o -z "$cmd"
|
||||||
|
print_error "Missing required arguments"
|
||||||
|
print_usage
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
set src $argv[1]
|
# Display configuration
|
||||||
set dest $argv[2]
|
print_info "Backup path: $yellow$backup_path$normal"
|
||||||
set interval $argv[3]
|
print_info "Output path: $yellow$output_dir$normal"
|
||||||
set max_backups $argv[4]
|
print_info "Max backups: $yellow$max_backups$normal"
|
||||||
|
print_info "Backup delay: $yellow$delay seconds$normal"
|
||||||
|
print_info "Command: $yellow$cmd$normal"
|
||||||
|
print_info "Log file: $yellow$log_file$normal"
|
||||||
|
|
||||||
# strip leading “--” if present
|
# Validate the backup path exists
|
||||||
set rest $argv[5..-1]
|
if not test -e "$backup_path"
|
||||||
if test $rest[1] = "--"
|
print_error "Backup path '$backup_path' does not exist"
|
||||||
set rest $rest[2..-1]
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
## Workflow ##
|
#==========================================================#
|
||||||
echo "BACKUP: Initial backup of '$src' → '$dest'"
|
# Initial backup #
|
||||||
backup
|
#==========================================================#
|
||||||
|
|
||||||
echo "BACKUP: Launching your program: $rest"
|
print_header "Creating Initial Backup"
|
||||||
# fish will expand $rest as command + args
|
|
||||||
$rest &; set pid $last_pid
|
|
||||||
echo " → PID is $pid"
|
|
||||||
|
|
||||||
echo "BACKUP: Starting periodic backups every $interval seconds…"
|
# Using command substitution to capture only the path output
|
||||||
periodic_backups $pid &
|
set -l initial_backup (backup_path "$backup_path" "$output_dir")
|
||||||
|
set -l status_code $status
|
||||||
|
|
||||||
echo "BACKUP: Waiting for PID $pid to exit…"
|
if test $status_code -ne 0
|
||||||
|
print_error "Initial backup failed"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
print_success "Initial backup created: $yellow$initial_backup$normal"
|
||||||
|
|
||||||
|
#==========================================================#
|
||||||
|
# Start wrapped process #
|
||||||
|
#==========================================================#
|
||||||
|
|
||||||
|
print_header "Starting Wrapped Process"
|
||||||
|
|
||||||
|
# Start the wrapped process in the background
|
||||||
|
print_step "Starting wrapped process: $yellow$cmd$normal"
|
||||||
|
|
||||||
|
# Using exactly the same execution method as the original working script
|
||||||
|
$cmd >> $log_file 2>&1 &
|
||||||
|
set -l pid $last_pid
|
||||||
|
print_success "Process started with PID: $yellow$pid$normal"
|
||||||
|
|
||||||
|
# Set up cleanup function
|
||||||
|
function cleanup --on-signal INT --on-signal TERM
|
||||||
|
print_warning "Caught signal, cleaning up..."
|
||||||
|
kill $pid 2>/dev/null
|
||||||
|
wait $pid 2>/dev/null
|
||||||
|
echo "# Script terminated by signal at "(date) >> $log_file
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
|
||||||
|
#==========================================================#
|
||||||
|
# Monitoring loop #
|
||||||
|
#==========================================================#
|
||||||
|
|
||||||
|
print_header "Monitoring for Changes"
|
||||||
|
|
||||||
|
# Monitor for changes and create backups
|
||||||
|
set -l change_detected 0
|
||||||
|
set -l last_backup_time (date +%s)
|
||||||
|
|
||||||
|
print_step "Monitoring $yellow$backup_path$normal for changes..."
|
||||||
|
|
||||||
|
while true
|
||||||
|
# Check if the process is still running
|
||||||
|
if not kill -0 $pid 2>/dev/null
|
||||||
|
print_warning "Wrapped process exited, stopping monitor"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
# Using inotifywait to detect changes
|
||||||
|
${pkgs.inotify-tools}/bin/inotifywait -r -q -e modify,create,delete,move "$backup_path" -t 1
|
||||||
|
set -l inotify_status $status
|
||||||
|
|
||||||
|
if test $inotify_status -eq 0
|
||||||
|
# Change detected
|
||||||
|
set change_detected 1
|
||||||
|
set -l current_time (date +%s)
|
||||||
|
set -l time_since_last (math "$current_time - $last_backup_time")
|
||||||
|
|
||||||
|
if test $time_since_last -ge $delay
|
||||||
|
print_step "Changes detected, creating backup"
|
||||||
|
set -l new_backup (backup_path "$backup_path" "$output_dir")
|
||||||
|
set -l backup_status $status
|
||||||
|
|
||||||
|
if test $backup_status -eq 0
|
||||||
|
print_success "Backup created: $yellow$new_backup$normal"
|
||||||
|
rotate_backups "$output_dir" "$max_backups"
|
||||||
|
set last_backup_time (date +%s)
|
||||||
|
set change_detected 0
|
||||||
|
else
|
||||||
|
print_error "Backup failed"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print_info "Change detected, batching with other changes ($yellow$delay$normal seconds delay)"
|
||||||
|
end
|
||||||
|
else if test $change_detected -eq 1
|
||||||
|
# No new changes but we had some changes before
|
||||||
|
set -l current_time (date +%s)
|
||||||
|
set -l time_since_last (math "$current_time - $last_backup_time")
|
||||||
|
|
||||||
|
if test $time_since_last -ge $delay
|
||||||
|
print_step "Creating backup after batching changes"
|
||||||
|
set -l new_backup (backup_path "$backup_path" "$output_dir")
|
||||||
|
set -l backup_status $status
|
||||||
|
|
||||||
|
if test $backup_status -eq 0
|
||||||
|
print_success "Backup created: $yellow$new_backup$normal"
|
||||||
|
rotate_backups "$output_dir" "$max_backups"
|
||||||
|
set last_backup_time (date +%s)
|
||||||
|
set change_detected 0
|
||||||
|
else
|
||||||
|
print_error "Backup failed"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#==========================================================#
|
||||||
|
# Cleanup & Exit #
|
||||||
|
#==========================================================#
|
||||||
|
|
||||||
|
print_header "Finishing Up"
|
||||||
|
|
||||||
|
# Wait for the wrapped process to finish
|
||||||
|
print_step "Waiting for process to finish..."
|
||||||
wait $pid
|
wait $pid
|
||||||
|
set -l exit_code $status
|
||||||
|
print_success "Process finished with exit code: $yellow$exit_code$normal"
|
||||||
|
|
||||||
echo "BACKUP: Program exited at "(date)"; doing final backup."
|
# Add final log entry
|
||||||
backup
|
echo "# Script completed at "(date)" with exit code $exit_code" >> $log_file
|
||||||
|
|
||||||
exit 0
|
exit $exit_code
|
||||||
''
|
''
|
||||||
|
|
|
@ -27,7 +27,7 @@ in
|
||||||
Ryujinx = {
|
Ryujinx = {
|
||||||
name = "Ryubing w/ Backups";
|
name = "Ryubing w/ Backups";
|
||||||
comment = "Ryubing Emulator with Save Backups";
|
comment = "Ryubing Emulator with Save Backups";
|
||||||
exec = ''fish ${backup-wrapper} /home/${user}/.config/Ryujinx/bis/user/save /pool/Backups/Switch/RyubingSaves 960 30 -- ryujinx''; # Should amount to be ~8 hours of playtime in 30 backups
|
exec = ''fish ${backup-wrapper} -p /home/${user}/.config/Ryujinx/bis/user/save -o /pool/Backups/Switch/RyubingSaves -m 30 -d 120 -- ryujinx'';
|
||||||
icon = "Ryujinx";
|
icon = "Ryujinx";
|
||||||
type = "Application";
|
type = "Application";
|
||||||
terminal = false;
|
terminal = false;
|
||||||
|
@ -52,7 +52,7 @@ in
|
||||||
citron-emu = {
|
citron-emu = {
|
||||||
name = "Citron w/ Backups";
|
name = "Citron w/ Backups";
|
||||||
comment = "Citron Emulator with Save Backups";
|
comment = "Citron Emulator with Save Backups";
|
||||||
exec = ''fish ${backup-wrapper} /home/${user}/.local/share/citron/nand/user/save /pool/Backups/Switch/CitronSaves 960 30 -- citron-emu''; # Should amount to be ~8 hours of playtime in 30 backups
|
exec = ''fish ${backup-wrapper} -p /home/${user}/.local/share/citron/nand/user/save -o /pool/Backups/Switch/CitronSaves -m 30 -d 120 -- citron-emu'';
|
||||||
icon = "applications-games";
|
icon = "applications-games";
|
||||||
type = "Application";
|
type = "Application";
|
||||||
terminal = false;
|
terminal = false;
|
||||||
|
|
Loading…
Add table
Reference in a new issue