From 511d0c53093362e77eba647edca3a1d70f003847 Mon Sep 17 00:00:00 2001 From: ltadeu6 Date: Thu, 28 May 2026 19:47:19 -0300 Subject: [PATCH 1/3] Add local game save backups --- AGENTS.md | 21 +++++++++++++++++++++ home/ltadeu6.nix | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 00e7efb..68ee130 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -341,6 +341,7 @@ Pacotes de sessao e desktop usados diretamente pelos configs: - `slurp` - `wl-clipboard` - `networkmanager` +- `ludusavi` - `hyprpicker` - `papirus-icon-theme` - `mpv` @@ -574,6 +575,26 @@ Estado atual: - aponta o provider Ollama para `http://127.0.0.1:11434` - usa modelo `ollama/gemma4:e4b` +### Backups locais de saves de jogos + +Fonte principal: + +- `home/ltadeu6.nix` + +Estado atual: + +- O Home Manager instala `ludusavi`. +- O timer de usuario `game-save-ludusavi-backup.timer` roda de hora em hora. +- O servico `game-save-ludusavi-backup.service` executa Ludusavi em modo CLI. +- Os backups locais ficam em `~/Backups/game-saves/ludusavi`. +- O formato usado e `simple`, com retencao de 14 backups completos e 14 diferenciais por jogo. + +Cuidados: + +- Este fluxo e apenas local; nao ha Restic, OCI ou outro destino remoto para saves de jogos no estado atual. +- Se adicionar backup remoto depois, mantenha segredos fora do repo e prefira um repo separado dos backups do VPS. +- Nao faca backup bruto de `~/.local/share/Steam/steamapps`; prefira Ludusavi e excecoes pontuais para jogos nao detectados. + ## Secrets e variaveis de ambiente ### Segredos versionados hoje diff --git a/home/ltadeu6.nix b/home/ltadeu6.nix index c0a472e..9ff8cfe 100644 --- a/home/ltadeu6.nix +++ b/home/ltadeu6.nix @@ -1,6 +1,22 @@ { config, pkgs, lib, ... }: let + gameSaveBackupRoot = "${config.home.homeDirectory}/Backups/game-saves"; + gameSaveLudusaviPath = "${gameSaveBackupRoot}/ludusavi"; + gameSaveLudusaviBackup = pkgs.writeShellScript "game-save-ludusavi-backup" '' + set -eu + + ${pkgs.coreutils}/bin/mkdir -p ${lib.escapeShellArg gameSaveLudusaviPath} + + exec ${pkgs.ludusavi}/bin/ludusavi \ + --try-manifest-update \ + backup \ + --path ${lib.escapeShellArg gameSaveLudusaviPath} \ + --format simple \ + --full-limit 14 \ + --differential-limit 14 \ + --force + ''; opencodeWithLibstdcxx = pkgs.symlinkJoin { name = "opencode"; paths = [ pkgs.unstable.opencode ]; @@ -247,6 +263,7 @@ in man hyprpicker networkmanager + ludusavi prismlauncher gimp inkscape @@ -375,6 +392,24 @@ in ".config/wofi/menu.css".source = ../configs/wofi/menu.css; }; + systemd.user.services.game-save-ludusavi-backup = { + Unit.Description = "Back up game saves with Ludusavi"; + Service = { + Type = "oneshot"; + ExecStart = "${gameSaveLudusaviBackup}"; + }; + }; + + systemd.user.timers.game-save-ludusavi-backup = { + Unit.Description = "Run Ludusavi game-save backup"; + Timer = { + OnCalendar = "hourly"; + Persistent = true; + RandomizedDelaySec = "10min"; + }; + Install.WantedBy = [ "timers.target" ]; + }; + home.activation.ociCredentials = lib.hm.dag.entryAfter [ "writeBoundary" ] '' set -euo pipefail key_file="/run/agenix/oci_key" From 3a42d3a7c09948624fc45abb26876f5d9494967d Mon Sep 17 00:00:00 2001 From: ltadeu6 Date: Thu, 28 May 2026 20:33:59 -0300 Subject: [PATCH 2/3] Include Lutris saves in local backups --- AGENTS.md | 2 ++ home/ltadeu6.nix | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 68ee130..8ad98ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -588,12 +588,14 @@ Estado atual: - O servico `game-save-ludusavi-backup.service` executa Ludusavi em modo CLI. - Os backups locais ficam em `~/Backups/game-saves/ludusavi`. - O formato usado e `simple`, com retencao de 14 backups completos e 14 diferenciais por jogo. +- O backup roda uma segunda passada do Ludusavi com o Wine prefix `~/Games/none` apenas para jogos selecionados do Lutris: `Cloudpunk`, `Cyberpunk 2077`, `Dreamcore` e `Outer Wilds`. Cuidados: - Este fluxo e apenas local; nao ha Restic, OCI ou outro destino remoto para saves de jogos no estado atual. - Se adicionar backup remoto depois, mantenha segredos fora do repo e prefira um repo separado dos backups do VPS. - Nao faca backup bruto de `~/.local/share/Steam/steamapps`; prefira Ludusavi e excecoes pontuais para jogos nao detectados. +- Nao use `--wine-prefix ~/Games/none` em uma varredura geral sem nomes de jogos; isso gera muitos falsos positivos por causa dos arquivos de registro do prefix. ## Secrets e variaveis de ambiente diff --git a/home/ltadeu6.nix b/home/ltadeu6.nix index 9ff8cfe..6845a19 100644 --- a/home/ltadeu6.nix +++ b/home/ltadeu6.nix @@ -3,12 +3,18 @@ let gameSaveBackupRoot = "${config.home.homeDirectory}/Backups/game-saves"; gameSaveLudusaviPath = "${gameSaveBackupRoot}/ludusavi"; + gameSaveLutrisGames = [ + "Cloudpunk" + "Cyberpunk 2077" + "Dreamcore" + "Outer Wilds" + ]; gameSaveLudusaviBackup = pkgs.writeShellScript "game-save-ludusavi-backup" '' set -eu ${pkgs.coreutils}/bin/mkdir -p ${lib.escapeShellArg gameSaveLudusaviPath} - exec ${pkgs.ludusavi}/bin/ludusavi \ + ${pkgs.ludusavi}/bin/ludusavi \ --try-manifest-update \ backup \ --path ${lib.escapeShellArg gameSaveLudusaviPath} \ @@ -16,6 +22,17 @@ let --full-limit 14 \ --differential-limit 14 \ --force + + exec ${pkgs.ludusavi}/bin/ludusavi \ + --try-manifest-update \ + backup \ + --path ${lib.escapeShellArg gameSaveLudusaviPath} \ + --wine-prefix ${lib.escapeShellArg "${config.home.homeDirectory}/Games/none"} \ + --format simple \ + --full-limit 14 \ + --differential-limit 14 \ + --force \ + ${lib.concatMapStringsSep " " lib.escapeShellArg gameSaveLutrisGames} ''; opencodeWithLibstdcxx = pkgs.symlinkJoin { name = "opencode"; From d02cc4ab4f829b4289da8b92ce9186ce8780726d Mon Sep 17 00:00:00 2001 From: ltadeu6 Date: Thu, 28 May 2026 20:38:06 -0300 Subject: [PATCH 3/3] Snapshot Lutris Wine saves locally --- AGENTS.md | 7 +++++-- home/ltadeu6.nix | 50 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8ad98ea..ec51064 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -588,14 +588,17 @@ Estado atual: - O servico `game-save-ludusavi-backup.service` executa Ludusavi em modo CLI. - Os backups locais ficam em `~/Backups/game-saves/ludusavi`. - O formato usado e `simple`, com retencao de 14 backups completos e 14 diferenciais por jogo. -- O backup roda uma segunda passada do Ludusavi com o Wine prefix `~/Games/none` apenas para jogos selecionados do Lutris: `Cloudpunk`, `Cyberpunk 2077`, `Dreamcore` e `Outer Wilds`. +- Para jogos do Lutris no prefix `~/Games/none`, o servico tambem cria snapshots locais de `~/Games/none/drive_c/users/steamuser`. +- Esses snapshots ficam em `~/Backups/game-saves/lutris-wine-userdata/snapshots`, com `latest` apontando para o snapshot mais recente. +- Os snapshots do Lutris usam hardlinks via `rsync --link-dest` e mantem os 14 mais recentes. Cuidados: - Este fluxo e apenas local; nao ha Restic, OCI ou outro destino remoto para saves de jogos no estado atual. - Se adicionar backup remoto depois, mantenha segredos fora do repo e prefira um repo separado dos backups do VPS. - Nao faca backup bruto de `~/.local/share/Steam/steamapps`; prefira Ludusavi e excecoes pontuais para jogos nao detectados. -- Nao use `--wine-prefix ~/Games/none` em uma varredura geral sem nomes de jogos; isso gera muitos falsos positivos por causa dos arquivos de registro do prefix. +- Nao use `--wine-prefix ~/Games/none` em uma varredura geral do Ludusavi sem nomes de jogos; isso gera muitos falsos positivos por causa dos arquivos de registro do prefix. +- Se adicionar jogos novos ao Lutris usando o mesmo prefix `~/Games/none`, eles entram automaticamente no snapshot local do Lutris, mesmo que o Ludusavi nao conheca o jogo. ## Secrets e variaveis de ambiente diff --git a/home/ltadeu6.nix b/home/ltadeu6.nix index 6845a19..fbb294d 100644 --- a/home/ltadeu6.nix +++ b/home/ltadeu6.nix @@ -3,12 +3,9 @@ let gameSaveBackupRoot = "${config.home.homeDirectory}/Backups/game-saves"; gameSaveLudusaviPath = "${gameSaveBackupRoot}/ludusavi"; - gameSaveLutrisGames = [ - "Cloudpunk" - "Cyberpunk 2077" - "Dreamcore" - "Outer Wilds" - ]; + gameSaveLutrisWinePrefix = "${config.home.homeDirectory}/Games/none"; + gameSaveLutrisUserPath = "${gameSaveLutrisWinePrefix}/drive_c/users/steamuser"; + gameSaveLutrisBackupPath = "${gameSaveBackupRoot}/lutris-wine-userdata"; gameSaveLudusaviBackup = pkgs.writeShellScript "game-save-ludusavi-backup" '' set -eu @@ -23,16 +20,37 @@ let --differential-limit 14 \ --force - exec ${pkgs.ludusavi}/bin/ludusavi \ - --try-manifest-update \ - backup \ - --path ${lib.escapeShellArg gameSaveLudusaviPath} \ - --wine-prefix ${lib.escapeShellArg "${config.home.homeDirectory}/Games/none"} \ - --format simple \ - --full-limit 14 \ - --differential-limit 14 \ - --force \ - ${lib.concatMapStringsSep " " lib.escapeShellArg gameSaveLutrisGames} + if [ ! -d ${lib.escapeShellArg gameSaveLutrisUserPath} ]; then + echo "Skipping Lutris Wine user-data snapshot: missing ${gameSaveLutrisUserPath}" >&2 + exit 0 + fi + + snapshot_root=${lib.escapeShellArg "${gameSaveLutrisBackupPath}/snapshots"} + snapshot="$snapshot_root/backup-$(${pkgs.coreutils}/bin/date -u +%Y%m%dT%H%M%SZ)" + latest_link=${lib.escapeShellArg "${gameSaveLutrisBackupPath}/latest"} + link_dest_args=() + + ${pkgs.coreutils}/bin/mkdir -p "$snapshot_root" + + if [ -L "$latest_link" ]; then + latest_target="$(${pkgs.coreutils}/bin/readlink -f "$latest_link" || true)" + if [ -n "$latest_target" ] && [ -d "$latest_target" ]; then + link_dest_args=(--link-dest "$latest_target") + fi + fi + + ${pkgs.rsync}/bin/rsync -a --delete \ + "''${link_dest_args[@]}" \ + ${lib.escapeShellArg "${gameSaveLutrisUserPath}/"} \ + "$snapshot/" + ${pkgs.coreutils}/bin/ln -sfn "$snapshot" "$latest_link" + + ${pkgs.findutils}/bin/find "$snapshot_root" -mindepth 1 -maxdepth 1 -type d \ + -printf '%T@ %p\n' \ + | ${pkgs.coreutils}/bin/sort -rn \ + | ${pkgs.coreutils}/bin/tail -n +15 \ + | ${pkgs.gnused}/bin/sed 's/^[^ ]* //' \ + | ${pkgs.findutils}/bin/xargs -r ${pkgs.coreutils}/bin/rm -rf ''; opencodeWithLibstdcxx = pkgs.symlinkJoin { name = "opencode";