Back up Google Messages with gmcli

This commit is contained in:
2026-07-12 12:46:59 -07:00
parent 1070efe5f1
commit 5ef237e965
2 changed files with 132 additions and 22 deletions

View File

@@ -114,6 +114,7 @@ in
claude-code claude-code
codex codex
gemini-cli gemini-cli
inputs.gmcli.packages.${pkgs.stdenv.hostPlatform.system}.default
inputs.lastfm-edit.packages.${pkgs.stdenv.hostPlatform.system}.scrobble-scrubber-app inputs.lastfm-edit.packages.${pkgs.stdenv.hostPlatform.system}.scrobble-scrubber-app
opencode opencode
t3code t3code

View File

@@ -2,6 +2,7 @@
pkgs, pkgs,
lib, lib,
config, config,
inputs,
... ...
}: let }: let
gitSyncServicePath = lib.makeBinPath [pkgs.coreutils pkgs.git pkgs.openssh]; gitSyncServicePath = lib.makeBinPath [pkgs.coreutils pkgs.git pkgs.openssh];
@@ -10,6 +11,66 @@
# github.com/colonelpanic8/claude-history and .../codex-history). # github.com/colonelpanic8/claude-history and .../codex-history).
aiHistoryHosts = ["ryzen-shine" "railbird-sf" "jay-lenovo" "strixi-minaj"]; aiHistoryHosts = ["ryzen-shine" "railbird-sf" "jay-lenovo" "strixi-minaj"];
syncAiHistory = builtins.elem config.networking.hostName aiHistoryHosts; syncAiHistory = builtins.elem config.networking.hostName aiHistoryHosts;
gmcliPackage = inputs.gmcli.packages.${pkgs.stdenv.hostPlatform.system}.default;
gmcliArchiveRoot = "/home/imalison/Backups/gmcli/git-sync";
gmcliArchiveOutput = "${gmcliArchiveRoot}/archive";
gmcliBackupLock = "/home/imalison/.local/state/gmcli/backup.lock";
exportGmcliArchive = pkgs.writeShellScript "export-gmcli-archive" ''
set -euo pipefail
${gmcliPackage}/bin/gmcli export jsonl --out ${lib.escapeShellArg gmcliArchiveOutput} --force
${gmcliPackage}/bin/gmcli export verify --dir ${lib.escapeShellArg gmcliArchiveOutput}
'';
refreshGmcliArchiveUnlocked = pkgs.writeShellScript "refresh-gmcli-archive-unlocked" ''
set -uo pipefail
status=0
${gmcliPackage}/bin/gmcli sync --include-spam=false --include-archive=false || status=1
${exportGmcliArchive} || status=1
exit "$status"
'';
backfillGmcliArchiveUnlocked = pkgs.writeShellScript "backfill-gmcli-archive-unlocked" ''
set -uo pipefail
status=0
${gmcliPackage}/bin/gmcli sync || status=1
exhausted=0
pass=1
while ((pass <= 20)); do
echo "Starting gmcli deep-history pass $pass/20"
if result="$(${gmcliPackage}/bin/gmcli --json history backfill-all --requests 20 --count 100)"; then
added="$(${pkgs.jq}/bin/jq -er '.messages_added' <<<"$result")" || {
echo "Unable to read messages_added from backfill result" >&2
status=1
break
}
echo "Deep-history pass $pass added $added message(s)"
if ((added == 0)); then
exhausted=1
break
fi
else
status=1
break
fi
((pass++))
done
if ((status == 0 && exhausted == 0)); then
echo "Deep-history backfill hit the 20-pass safety cap before exhaustion" >&2
status=1
fi
${exportGmcliArchive} || status=1
exit "$status"
'';
withGmcliBackupLock = name: command:
pkgs.writeShellScript name ''
set -euo pipefail
exec 9>${lib.escapeShellArg gmcliBackupLock}
if ! ${pkgs.util-linux}/bin/flock --nonblock 9; then
echo "Another gmcli backup job is already running; skipping."
exit 0
fi
exec ${command}
'';
refreshGmcliArchive = withGmcliBackupLock "refresh-gmcli-archive" refreshGmcliArchiveUnlocked;
backfillGmcliArchive = withGmcliBackupLock "backfill-gmcli-archive" backfillGmcliArchiveUnlocked;
mkGitSyncTrayOverrides = icon: { mkGitSyncTrayOverrides = icon: {
Service = { Service = {
Environment = lib.mkMerge [ Environment = lib.mkMerge [
@@ -27,35 +88,42 @@
# freedesktop name, like "password" above). # freedesktop name, like "password" above).
claude-history = "claude-desktop"; claude-history = "claude-desktop";
codex-history = "codex-desktop"; codex-history = "codex-desktop";
gmcli-archive = "mail-message-new";
}; };
in { in {
home-manager.users.imalison = {config, ...}: { home-manager.users.imalison = {config, ...}: {
services.git-sync = { services.git-sync = {
enable = true; enable = true;
package = pkgs.git-sync-rs; package = pkgs.git-sync-rs;
repositories = { repositories =
org = { {
path = config.home.homeDirectory + "/org"; org = {
uri = "git@github.com:IvanMalison/org.git"; path = config.home.homeDirectory + "/org";
interval = 30; uri = "git@github.com:IvanMalison/org.git";
interval = 30;
};
password-store = {
path = config.home.homeDirectory + "/.password-store";
uri = "git@github.com:IvanMalison/.password-store.git";
};
gmcli-archive = {
path = gmcliArchiveRoot;
uri = "git@github.com:colonelpanic8/gmcli-archive.git";
interval = 300;
};
}
// lib.optionalAttrs syncAiHistory {
claude-history = {
path = config.home.homeDirectory + "/.claude";
uri = "git@github.com:colonelpanic8/claude-history.git";
interval = 600;
};
codex-history = {
path = config.home.homeDirectory + "/.codex";
uri = "git@github.com:colonelpanic8/codex-history.git";
interval = 600;
};
}; };
password-store = {
path = config.home.homeDirectory + "/.password-store";
uri = "git@github.com:IvanMalison/.password-store.git";
};
}
// lib.optionalAttrs syncAiHistory {
claude-history = {
path = config.home.homeDirectory + "/.claude";
uri = "git@github.com:colonelpanic8/claude-history.git";
interval = 600;
};
codex-history = {
path = config.home.homeDirectory + "/.codex";
uri = "git@github.com:colonelpanic8/codex-history.git";
interval = 600;
};
};
}; };
systemd.user.services = lib.mkMerge [ systemd.user.services = lib.mkMerge [
@@ -75,7 +143,48 @@ in {
lib.mkForce lib.mkForce
"${pkgs.git-sync-rs}/bin/git-sync-rs watch --new-files true --min-interval 300"; "${pkgs.git-sync-rs}/bin/git-sync-rs watch --new-files true --min-interval 300";
}) })
{
git-sync-gmcli-archive.Service.ExecStart =
lib.mkForce
"${pkgs.git-sync-rs}/bin/git-sync-rs -d ${lib.escapeShellArg gmcliArchiveRoot} watch --new-files true --min-interval 30 --interval 300";
gmcli-archive-refresh = {
Unit.Description = "Sync Google Messages and refresh the JSONL archive";
Service = {
Type = "oneshot";
ExecStart = refreshGmcliArchive;
TimeoutStartSec = "20min";
};
};
gmcli-archive-backfill = {
Unit.Description = "Deep-backfill Google Messages and refresh the JSONL archive";
Service = {
Type = "oneshot";
ExecStart = backfillGmcliArchive;
TimeoutStartSec = "3h";
};
};
}
]; ];
systemd.user.timers.gmcli-archive-refresh = {
Unit.Description = "Hourly Google Messages JSONL backup";
Timer = {
OnCalendar = "hourly";
Persistent = true;
RandomizedDelaySec = "5min";
};
Install.WantedBy = ["timers.target"];
};
systemd.user.timers.gmcli-archive-backfill = {
Unit.Description = "Weekly deep Google Messages history backfill";
Timer = {
OnCalendar = "Sun *-*-* 04:00:00";
Persistent = true;
RandomizedDelaySec = "2h";
};
Install.WantedBy = ["timers.target"];
};
}; };
home-manager.users.kat = {config, ...}: { home-manager.users.kat = {config, ...}: {