236 lines
6.3 KiB
Nix
236 lines
6.3 KiB
Nix
# switch.nix
|
|
{
|
|
pkgs,
|
|
config,
|
|
lib,
|
|
hostSpec,
|
|
...
|
|
}:
|
|
|
|
let
|
|
# citron-emu = pkgs.callPackage (lib.custom.relativeToRoot "pkgs/common/citron-emu/package.nix") {
|
|
# inherit pkgs;
|
|
# };
|
|
# borgtui = pkgs.callPackage (lib.custom.relativeToRoot "pkgs/common/borgtui/package.nix") {
|
|
# inherit pkgs;
|
|
# };
|
|
|
|
homeDir = hostSpec.home;
|
|
|
|
borg-wrapper = pkgs.writeScript "borg-wrapper" ''
|
|
#!${lib.getExe pkgs.fish}
|
|
|
|
# Parse arguments using argparse
|
|
function parse_args
|
|
argparse 'p/path=' 'o/output=' 'm/max=' 'h/help' -- $argv
|
|
or return 1
|
|
|
|
if set -ql _flag_help
|
|
echo "Usage: borg-wrapper -p|--path PATH -o|--output REPO -m|--max MAX_BACKUPS -- COMMAND..."
|
|
echo " -p, --path PATH Path to backup"
|
|
echo " -o, --output REPO Path to store backups"
|
|
echo " -m, --max MAX Maximum number of backups to keep"
|
|
echo " -h, --help Show this help"
|
|
exit 0
|
|
end
|
|
|
|
# Check required arguments
|
|
if not set -ql _flag_path
|
|
echo "Error: --path is required" >&2
|
|
return 1
|
|
end
|
|
if not set -ql _flag_output
|
|
echo "Error: --output is required" >&2
|
|
return 1
|
|
end
|
|
|
|
# Set defaults
|
|
set -g BACKUP_PATH $_flag_path
|
|
set -g BORG_REPO $_flag_output
|
|
|
|
if set -ql _flag_max
|
|
set -g MAX_BACKUPS $_flag_max
|
|
else
|
|
set -g MAX_BACKUPS 30
|
|
end
|
|
|
|
# Everything remaining is the command to execute
|
|
set -g CMD $argv
|
|
|
|
# Verify we have a command
|
|
if test (count $CMD) -eq 0
|
|
echo "Error: No command specified after --" >&2
|
|
return 1
|
|
end
|
|
end
|
|
|
|
# Parse the arguments
|
|
parse_args $argv
|
|
or exit 1
|
|
|
|
# Initialize Borg repository
|
|
mkdir -p "$BORG_REPO"
|
|
if not ${pkgs.borgbackup}/bin/borg list "$BORG_REPO" &>/dev/null
|
|
echo "Initializing new Borg repository at $BORG_REPO"
|
|
${pkgs.borgbackup}/bin/borg init --encryption=none "$BORG_REPO"
|
|
end
|
|
|
|
# Backup functions with error suppression
|
|
function create_backup
|
|
set -l tag $argv[1]
|
|
set -l timestamp (date +%Y%m%d-%H%M%S)
|
|
echo "Creating $tag backup: $timestamp"
|
|
|
|
# Push to parent directory, backup the basename only, then pop back
|
|
pushd (dirname "$BACKUP_PATH") >/dev/null
|
|
${pkgs.borgbackup}/bin/borg create --stats --compression zstd,15 \
|
|
--files-cache=mtime,size \
|
|
--lock-wait 5 \
|
|
"$BORG_REPO::$tag-$timestamp" (basename "$BACKUP_PATH") || true
|
|
popd >/dev/null
|
|
end
|
|
|
|
function prune_backups
|
|
echo "Pruning old backups"
|
|
${pkgs.borgbackup}/bin/borg prune --keep-last "$MAX_BACKUPS" --stats "$BORG_REPO" || true
|
|
end
|
|
|
|
# Initial backup
|
|
create_backup "initial"
|
|
prune_backups
|
|
|
|
# Start emulator in a subprocess group
|
|
fish -c "
|
|
function on_exit
|
|
exit 0
|
|
end
|
|
|
|
trap on_exit INT TERM
|
|
exec $CMD
|
|
" &
|
|
set PID (jobs -lp | tail -n1)
|
|
|
|
# Cleanup function
|
|
function cleanup
|
|
# Send TERM to process group
|
|
kill -TERM -$PID 2>/dev/null || true
|
|
wait $PID 2>/dev/null || true
|
|
create_backup "final"
|
|
prune_backups
|
|
end
|
|
|
|
function on_exit --on-signal INT --on-signal TERM
|
|
cleanup
|
|
end
|
|
|
|
# Debounced backup trigger
|
|
set last_backup (date +%s)
|
|
set backup_cooldown 30 # Minimum seconds between backups
|
|
|
|
# Watch loop with timeout
|
|
while kill -0 $PID 2>/dev/null
|
|
# Wait for changes with 5-second timeout
|
|
if ${pkgs.inotify-tools}/bin/inotifywait \
|
|
-r \
|
|
-qq \
|
|
-e close_write,delete,moved_to \
|
|
-t 5 \
|
|
"$BACKUP_PATH"
|
|
|
|
set current_time (date +%s)
|
|
if test (math "$current_time - $last_backup") -ge $backup_cooldown
|
|
create_backup "auto"
|
|
prune_backups
|
|
set last_backup $current_time
|
|
else
|
|
echo "Skipping backup:" + (math "$backup_cooldown - ($current_time - $last_backup)") + "s cooldown remaining"
|
|
end
|
|
end
|
|
end
|
|
|
|
cleanup
|
|
exit 0
|
|
'';
|
|
|
|
# Generic function to create launcher scripts
|
|
mkLaunchCommand =
|
|
{
|
|
savePath, # Path to the save directory
|
|
backupPath, # Path where backups should be stored
|
|
maxBackups ? 30, # Maximum number of backups to keep
|
|
command, # Command to execute
|
|
}:
|
|
"${borg-wrapper} -p \"${savePath}\" -o \"${backupPath}\" -m ${toString maxBackups} -- ${command}";
|
|
|
|
in
|
|
{
|
|
home.packages = with pkgs; [
|
|
citron-emu
|
|
ryubing
|
|
borgbackup
|
|
borgtui
|
|
inotify-tools
|
|
];
|
|
|
|
xdg.desktopEntries = {
|
|
Ryujinx = {
|
|
name = "Ryujinx w/ Borg Backups";
|
|
comment = "Ryujinx Emulator with Borg Backups";
|
|
exec = mkLaunchCommand {
|
|
savePath = "${homeDir}/.config/Ryujinx/bis/user/save";
|
|
backupPath = "/pool/Backups/Switch/RyubingSaves";
|
|
maxBackups = 30;
|
|
command = "ryujinx";
|
|
};
|
|
icon = "Ryujinx";
|
|
type = "Application";
|
|
terminal = false;
|
|
categories = [
|
|
"Game"
|
|
"Emulator"
|
|
];
|
|
mimeType = [
|
|
"application/x-nx-nca"
|
|
"application/x-nx-nro"
|
|
"application/x-nx-nso"
|
|
"application/x-nx-nsp"
|
|
"application/x-nx-xci"
|
|
];
|
|
prefersNonDefaultGPU = true;
|
|
settings = {
|
|
StartupWMClass = "Ryujinx";
|
|
GenericName = "Nintendo Switch Emulator";
|
|
};
|
|
};
|
|
|
|
citron-emu = {
|
|
name = "Citron w/ Borg Backups";
|
|
comment = "Citron Emulator with Borg Backups";
|
|
exec = mkLaunchCommand {
|
|
savePath = "${homeDir}/.local/share/citron/nand/user/save";
|
|
backupPath = "/pool/Backups/Switch/CitronSaves";
|
|
maxBackups = 30;
|
|
command = "citron-emu";
|
|
};
|
|
icon = "applications-games";
|
|
type = "Application";
|
|
terminal = false;
|
|
categories = [
|
|
"Game"
|
|
"Emulator"
|
|
];
|
|
mimeType = [
|
|
"application/x-nx-nca"
|
|
"application/x-nx-nro"
|
|
"application/x-nx-nso"
|
|
"application/x-nx-nsp"
|
|
"application/x-nx-xci"
|
|
];
|
|
prefersNonDefaultGPU = true;
|
|
settings = {
|
|
StartupWMClass = "Citron";
|
|
GenericName = "Nintendo Switch Emulator";
|
|
};
|
|
};
|
|
};
|
|
}
|