Back up Google Messages with gmcli
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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, ...}: {
|
||||||
|
|||||||
Reference in New Issue
Block a user