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
codex
gemini-cli
inputs.gmcli.packages.${pkgs.stdenv.hostPlatform.system}.default
inputs.lastfm-edit.packages.${pkgs.stdenv.hostPlatform.system}.scrobble-scrubber-app
opencode
t3code

View File

@@ -2,6 +2,7 @@
pkgs,
lib,
config,
inputs,
...
}: let
gitSyncServicePath = lib.makeBinPath [pkgs.coreutils pkgs.git pkgs.openssh];
@@ -10,6 +11,66 @@
# github.com/colonelpanic8/claude-history and .../codex-history).
aiHistoryHosts = ["ryzen-shine" "railbird-sf" "jay-lenovo" "strixi-minaj"];
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: {
Service = {
Environment = lib.mkMerge [
@@ -27,35 +88,42 @@
# freedesktop name, like "password" above).
claude-history = "claude-desktop";
codex-history = "codex-desktop";
gmcli-archive = "mail-message-new";
};
in {
home-manager.users.imalison = {config, ...}: {
services.git-sync = {
enable = true;
package = pkgs.git-sync-rs;
repositories = {
org = {
path = config.home.homeDirectory + "/org";
uri = "git@github.com:IvanMalison/org.git";
interval = 30;
repositories =
{
org = {
path = config.home.homeDirectory + "/org";
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 [
@@ -75,7 +143,48 @@ in {
lib.mkForce
"${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, ...}: {