Ce guide s’adresse à l’opérateur d’un Hermes Agent auto-hébergé capable d’administrer le compte Linux et le dépôt GitHub privé. Le résultat est précis : une archive complète des données utilisateur Hermes, chiffrée avant de quitter la machine, confirmée sur le dépôt distant privé prévu, puis récupérée et importée sur une machine temporaire distincte. La procédure repose sur la CLI Hermes au commit 86fa9e6, vérifiée le 19 octobre 2026. BIG CHANGE a vérifié la syntaxe du script et son déroulement avec des jeux d’essai locaux inertes ; aucune véritable sauvegarde Hermes, opération de chiffrement age, transmission GitHub, exécution programmée ou restauration n’a été réalisée. Ne considérez la sauvegarde comme éprouvée pour votre installation qu’après avoir effectué tous les contrôles ci-dessous.
Le grand changement
- Ce qui change : La commande de sauvegarde complète Hermes peut réunir ses données de travail dans un ZIP, et son mode cron sans agent peut exécuter un script vérifié. Ensemble, ils permettent de programmer une copie chiffrée sans confier la sauvegarde à un modèle d’IA.
- Pourquoi c’est important : L’opérateur peut vérifier trois faits distincts : Hermes a terminé l’archive, les données chiffrées ont atteint le dépôt privé prévu et une installation neuve peut les importer. Chaque contrôle détecte un type de panne différent. L’archive exclut toujours l’application et certaines données propres à la machine.
- À surveiller : Il faut déterminer si l’archive et l’historique Git grandissant restent assez petits pour cette méthode limitée. Un exercice de récupération échoué ou l’un des seuils de taille impose un autre plan de stockage avant de compter sur la programmation.
1. Préparer la machine, la clé et le dépôt
Utilisez Linux avec Bash, les outils GNU stat -c et du -sb, ainsi qu’une CLI Hermes installée, Git, GitHub CLI (gh), age, unzip, sha256sum, SSH et une passerelle Hermes active pour cron. Vérifiez que les commandes installées hermes backup, hermes import et hermes cron create --help correspondent à la référence CLI épinglée, à la FAQ et au manuel cron. Identifiez l’utilisateur du système et le HERMES_HOME qui exécutent la passerelle. Effectuez toute la configuration et la première sauvegarde manuelle avec cet utilisateur. L’exemple suppose un clone Git dédié et propre ainsi qu’une branche main.
La sauvegarde complète Hermes comprend configuration, identifiants, mémoires, compétences, sessions et profils sous sa racine de données. Elle omet le code Hermes, les dépendances et environnements téléchargés, les sauvegardes précédentes, les points de contrôle, les profils d’identification du navigateur, les fichiers annexes SQLite et certains fichiers locaux. Elle ne collecte pas les fichiers indépendants ailleurs sur la machine. N’utilisez pas --quick pour cette sauvegarde complète des données. Examinez les éléments importants hors de ce périmètre et protégez-les séparément. Le ZIP contient des secrets même si le dépôt de destination est privé.
Générez une identité age, c’est-à-dire la clé privée nécessaire au déchiffrement, dans un emplacement protégé hors de HERMES_HOME, du répertoire de préparation et du clone Git. Gardez une copie de récupération de cette identité dans un stockage indépendant. Le destinataire affiché age1… est public et va dans la configuration ; pas l’identité privée. Suivez la référence d’utilisation d’age.
umask 077
install -d -m 700 /PRIVATE-KEY-STORE
age-keygen -o /PRIVATE-KEY-STORE/hermes-backup-identity.txt
age-keygen -y /PRIVATE-KEY-STORE/hermes-backup-identity.txtCréez un dépôt privé réservé à cette sauvegarde, vérifiez qui y a accès et configurez un accès en lecture non interactif avec gh ainsi qu’un accès Git SSH en écriture pour le même utilisateur système que Hermes cron. Gardez jetons et clés SSH privées hors des données Hermes, de la préparation et de Git. Une connexion interactive sous un autre compte ne prouve pas l’accès programmé. Remplacez les paramètres fictifs en majuscules avant d’exécuter ces exemples. Si la création du dépôt a déjà ajouté un README ou une branche main, examinez-les et sautez les commandes d’initialisation incompatibles.
gh repo create ACCOUNT/PRIVATE-REPOSITORY --private --clone
gh repo view ACCOUNT/PRIVATE-REPOSITORY --json isPrivate --jq .isPrivate
cd PRIVATE-REPOSITORY
git remote set-url origin git@github.com:ACCOUNT/PRIVATE-REPOSITORY.git
git remote get-url --all origin
git remote get-url --push --all origin
git branch -M main
printf '%s\n' 'Encrypted Hermes backup storage; ciphertext and checksum only.' > README.md
git add README.md
git commit -m 'Initialize private backup repository'
git push -u origin HEAD:main
git ls-remote --heads origin maingh repo create --clone peut choisir HTTPS selon les préférences locales ; le dépôt SSH explicite ci-dessus définit la destination de cette recette. Continuez uniquement lorsque isPrivate renvoie true, que les collaborateurs sont ceux prévus, que les URL effectives de récupération et d’envoi correspondent chacune à l’unique URL SSH vérifiée, et que l’accès distant fonctionne avec l’utilisateur de la tâche programmée. Les règles des dépôts distants Git autorisent réécritures d’URL, destinations supplémentaires et un pushurl distinct ; le script ci-dessous vérifie les valeurs brutes et effectives à chaque exécution.
Quittez le clone avant d’enregistrer les deux blocs de code suivants. Créez un répertoire de configuration privé hors de la racine Hermes, du clone Git, du répertoire de préparation et du stockage des clés. Enregistrez hermes-github-backup.sh et hermes-github-backup.conf à cet endroit ; les commandes d’installation les copieront dans le profil propriétaire. Le clone Git dédié doit rester propre.
install -d -m 700 /ABSOLUTE/PRIVATE-SETUP
cd /ABSOLUTE/PRIVATE-SETUP2. Installer le script de sauvegarde vérifié
Enregistrez le bloc Bash complet ci-dessous sous hermes-github-backup.sh dans ce répertoire de configuration privé. Enregistrez le second bloc sous hermes-github-backup.conf à côté, après avoir remplacé tous les paramètres fictifs par des chemins absolus ou le destinataire public. Ce fichier de configuration shell doit être vérifié localement avant son chargement et accessible uniquement à son propriétaire. Il ne doit contenir ni mot de passe, ni jeton, ni identité privée age. Le script est l’exemple proposé complet ; sa syntaxe et son déroulement inerte ont été vérifiés, mais adaptez-le et testez-le sur votre installation. Le fichier chiffré latest.zip.age qu’il produit est l’archive chiffrée.
#!/usr/bin/env bash
# Documentation-based Linux sample for an operator to review and adapt.
# Place with hermes-github-backup.conf under the owning HERMES_HOME/scripts/.
# Requires GNU stat -c and du -sb. Git Bash was used only for inert fixtures.
set -Eeuo pipefail
umask 077
script_dir=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
owning_home=$(CDPATH= cd -- "$script_dir/.." && pwd -P)
if [[ -n ${HERMES_HOME:-} && $(CDPATH= cd -- "$HERMES_HOME" && pwd -P) != "$owning_home" ]]; then
printf 'Backup stopped: cron profile and script directory differ.\n' >&2
exit 1
fi
export HERMES_HOME="$owning_home"
config="$script_dir/hermes-github-backup.conf"
[[ -f "$config" ]] || { printf 'Backup stopped: reviewed configuration missing.\n' >&2; exit 1; }
# This file is local, owner-reviewed shell configuration. It must contain no secret.
# shellcheck source=/dev/null
source "$config"
: "${HERMES_BIN:?set an absolute Hermes executable path}"
: "${REPO_DIR:?set an absolute private clone path}"
: "${STAGING_PARENT:?set an absolute private staging path}"
: "${AGE_RECIPIENT:?set an age public recipient}"
: "${EXPECTED_REMOTE:?set the exact private GitHub SSH remote}"
: "${BRANCH:=main}"
fail() { printf 'Backup stopped: %s.\n' "$1" >&2; exit 1; }
for path in "$HERMES_BIN" "$REPO_DIR" "$STAGING_PARENT"; do
[[ $path == /* ]] || fail 'a configured path is not absolute'
done
[[ -x "$HERMES_BIN" ]] || fail 'Hermes executable unavailable'
[[ $AGE_RECIPIENT == age1* ]] || fail 'age public recipient invalid'
[[ $EXPECTED_REMOTE == git@github.com:*/*.git ]] || fail 'remote is not the reviewed GitHub SSH URL'
[[ $BRANCH =~ ^[a-zA-Z0-9._/-]+$ ]] || fail 'branch name invalid'
[[ -d "$STAGING_PARENT" ]] || fail 'staging directory unavailable'
stage_parent=$(CDPATH= cd -- "$STAGING_PARENT" && pwd -P)
[[ $stage_parent != "$owning_home" && $stage_parent != "$owning_home"/* ]] || fail 'staging is inside Hermes data'
# Atomic, nonblocking whole-flow lock: keep it through push and remote readback.
# A stale directory after a crash requires operator inspection; never steal it.
lockdir="$stage_parent/.hermes-github-backup-flow.lock"
mkdir -- "$lockdir" 2>/dev/null || fail 'another backup flow holds the lock or a stale lock needs review'
work=''
cleanup() {
if [[ -n $work ]]; then
rm -f -- "$work/hermes.zip" "$work/latest.zip.age" "$work/latest.sha256" "$work/backup.log"
rmdir -- "$work" 2>/dev/null || true
fi
rmdir -- "$lockdir" 2>/dev/null || true
}
trap cleanup EXIT
[[ -d "$REPO_DIR/.git" ]] || fail 'Git repository unavailable'
repo=$(CDPATH= cd -- "$REPO_DIR" && pwd -P)
[[ $repo != "$owning_home" && $repo != "$owning_home"/* ]] || fail 'repository is inside Hermes data'
[[ $stage_parent != "$repo" && $stage_parent != "$repo"/* ]] || fail 'staging is inside Git repository'
[[ $repo != "$stage_parent"/* ]] || fail 'Git repository is inside staging'
command -v age >/dev/null || fail 'age unavailable'
command -v gh >/dev/null || fail 'GitHub CLI unavailable for private visibility check'
command -v unzip >/dev/null || fail 'zip verifier unavailable'
command -v git >/dev/null || fail 'Git unavailable'
remote_repo=${EXPECTED_REMOTE#git@github.com:}
remote_repo=${remote_repo%.git}
visibility=$(gh repo view "$remote_repo" --json isPrivate --jq .isPrivate 2>/dev/null) || fail 'GitHub repository visibility unreadable'
[[ $visibility == true ]] || fail 'GitHub repository is not confirmed private'
raw_fetch_urls=$(git -C "$repo" config --get-all remote.origin.url) || fail 'Git fetch URL missing'
[[ $raw_fetch_urls == "$EXPECTED_REMOTE" ]] || fail 'Git has multiple or unexpected raw fetch URLs'
raw_push_urls=$(git -C "$repo" config --get-all remote.origin.pushurl || true)
[[ -z $raw_push_urls ]] || fail 'separate Git pushurl requires independent review'
effective_fetch_urls=$(git -C "$repo" remote get-url --all origin) || fail 'effective fetch URL unreadable'
effective_push_urls=$(git -C "$repo" remote get-url --push --all origin) || fail 'effective push URL unreadable'
[[ $effective_fetch_urls == "$EXPECTED_REMOTE" ]] || fail 'Git fetch URL rewrite or extra destination'
[[ $effective_push_urls == "$EXPECTED_REMOTE" ]] || fail 'Git push URL rewrite or extra destination'
[[ $(git -C "$repo" branch --show-current) == "$BRANCH" ]] || fail 'local branch changed'
[[ -z $(git -C "$repo" status --porcelain --untracked-files=all) ]] || fail 'repository has unrelated changes'
# No interactive prompt is possible in an unattended Hermes cron tick.
export GIT_TERMINAL_PROMPT=0
export GIT_SSH_COMMAND='ssh -o BatchMode=yes'
remote_head=$(git -C "$repo" ls-remote --heads origin "$BRANCH" | awk '{print $1}')
[[ -n $remote_head && $remote_head == "$(git -C "$repo" rev-parse HEAD)" ]] || fail 'remote unreadable or branch has changed'
work=$(mktemp -d "$stage_parent/hermes-backup.XXXXXXXX") || fail 'private staging creation failed'
if "$HERMES_BIN" backup -o "$work/hermes.zip" >"$work/backup.log" 2>&1; then
:
else
backup_rc=$?
fail "Hermes full backup returned $backup_rc; temporary zip/log will be removed, so diagnose with a separate controlled manual run"
fi
[[ -s "$work/hermes.zip" ]] || fail 'Hermes archive absent or empty'
unzip -tq "$work/hermes.zip" >"$work/backup.log" 2>&1 || fail 'zip integrity check failed'
age -r "$AGE_RECIPIENT" -o "$work/latest.zip.age" "$work/hermes.zip" >"$work/backup.log" 2>&1 || fail 'archive encryption failed'
[[ -s "$work/latest.zip.age" ]] || fail 'encrypted archive absent or empty'
# Deliberately below GitHub's 50 MiB warning. Git history retains every version.
cipher_bytes=$(stat -c %s "$work/latest.zip.age")
(( cipher_bytes <= 40000000 )) || fail 'encrypted archive exceeds this small-repository recipe'
git_bytes=$(du -sb "$repo/.git" | awk '{print $1}')
(( git_bytes + cipher_bytes + 10000000 <= 500000000 )) || fail 'repository history approaches the review limit'
hash=$(sha256sum "$work/latest.zip.age" | awk '{print $1}')
printf '%s latest.zip.age\n' "$hash" >"$work/latest.sha256"
cp -- "$work/latest.zip.age" "$repo/latest.zip.age"
cp -- "$work/latest.sha256" "$repo/latest.sha256"
git -C "$repo" add -- latest.zip.age latest.sha256
staged=$(git -C "$repo" diff --cached --name-only)
[[ $staged == $'latest.sha256\nlatest.zip.age' ]] || fail 'staged paths are not the two reviewed files'
git -C "$repo" diff --cached --check >/dev/null || fail 'staged content check failed'
git -C "$repo" commit -m "Encrypted Hermes backup $(date -u +%Y-%m-%dT%H:%M:%SZ)" >/dev/null || fail 'local backup commit failed'
run_head=$(git -C "$repo" rev-parse HEAD)
(( $(du -sb "$repo/.git" | awk '{print $1}') <= 500000000 )) || fail 'Git history exceeds review limit before push'
[[ $(git -C "$repo" rev-parse HEAD) == "$run_head" ]] || fail 'local HEAD changed before push'
git -C "$repo" push origin "$run_head:refs/heads/$BRANCH" >/dev/null || fail 'remote push failed; inspect exact remote before retrying'
remote_head=$(git -C "$repo" ls-remote --heads origin "$BRANCH" | awk '{print $1}')
[[ $remote_head == "$run_head" ]] || fail 'this run commit not confirmed at remote'
printf 'Encrypted backup verified at private remote commit %s.\n' "$run_head"# No passwords, tokens, age identities or private keys belong here.
HERMES_BIN=/absolute/path/to/hermes
REPO_DIR=/absolute/path/to/dedicated-private-github-clone
STAGING_PARENT=/absolute/path/to/private-staging-outside-hermes-and-git
AGE_RECIPIENT=age1replace_with_operator_public_recipient
EXPECTED_REMOTE=git@github.com:ACCOUNT/PRIVATE-REPOSITORY.git
BRANCH=mainCréez le répertoire de préparation hors de la racine Hermes et du clone Git. Placez les fichiers vérifiés dans le répertoire scripts/ du profil propriétaire. Avant chaque exécution, le clone doit être propre et aligné sur la branche distante main. Le script verrouille tout le déroulement, jusqu’à l’envoi et sa vérification ; il ne vole jamais un verrou détenu ou ancien. Il n’accepte que latest.zip.age et latest.sha256 comme nouveaux chemins de sauvegarde préparés pour Git. Il s’arrête lorsque le fichier chiffré dépasse 40 000 000 octets ou que le .git local et la marge nécessaire approchent 500 000 000 octets. Ce sont des limites prudentes de cet exemple, pas des garanties de stockage GitHub.
export HERMES_HOME=/absolute/path/to/owning-hermes-data-root
install -d -m 700 /ABSOLUTE/PRIVATE-STAGING
install -d -m 700 "$HERMES_HOME/scripts"
install -m 700 hermes-github-backup.sh "$HERMES_HOME/scripts/hermes-github-backup.sh"
install -m 600 hermes-github-backup.conf "$HERMES_HOME/scripts/hermes-github-backup.conf"
"$HERMES_HOME/scripts/hermes-github-backup.sh"L’exécution manuelle doit se terminer avec le code zéro et afficher le commit créé par cette exécution après une nouvelle lecture du dépôt distant. Confirmez que gh repo view indique toujours un dépôt privé, inspectez les chemins suivis du clone et vérifiez le commit exact sur le dépôt distant prévu. Une archive ou un commit local ne prouve pas un stockage externe. Une exécution interrompue ne doit pas être enregistrée comme réussie. Selon Hermes, la sauvegarde complète renvoie 0 uniquement si tous les fichiers sélectionnés ont été ajoutés, 1 pour une archive incomplète, même si un ZIP existe, et 2 lorsqu’une autre sauvegarde est en cours. Le script supprime son ZIP temporaire et son journal en quittant ; une suppression ordinaire n’est pas un effacement sécurisé. Si des fichiers ont été ignorés, enquêtez avec une sauvegarde manuelle de diagnostic distincte et contrôlée hermes backup -o /PRIVATE-DIAGNOSTIC/hermes.zip, inspectez son code de sortie et son rapport, puis décidez comment protéger les éléments omis.
3. Programmer et inspecter la première exécution prévue
Après seulement le résultat manuel, utilisez le mode documenté --no-agent --script de Hermes depuis le même profil. Il exécute un fichier sans sollicitation d’un LLM. Choisissez une cadence délibérée et une remise locale d’état sans secrets. Vérifiez l’identité système de la passerelle, le profil et la prochaine échéance. Selon Hermes, le délai maximal du script vient de HERMES_CRON_SCRIPT_TIMEOUT, puis de cron.script_timeout_seconds, puis d’une valeur par défaut de 3 600 secondes. Définissez et vérifiez une valeur adaptée à une exécution observée ; un dépassement est un échec. L’environnement épuré du script ne reçoit pas automatiquement les identifiants fournisseurs gérés par Hermes : vérifiez les accès gh et SSH sans intervention sous l’utilisateur programmé réel avant de vous y fier.
hermes cron create '0 3 * * *' --no-agent --script hermes-github-backup.sh --deliver local --name 'Encrypted Hermes backup'
hermes cron list
hermes cron status
hermes cron doctorAprès la première échéance programmée, inspectez l’identifiant et l’enregistrement d’exécution exacts, la sortie locale sous ~/.hermes/cron/output/ pour le profil par défaut, tout incident et le commit de tête du dépôt prévu. Confirmez que la passerelle est restée disponible et que l’exécution a signalé le commit distant produit par ce déclenchement. Un lancement manuel ne prouve pas que cron a fonctionné. Une sortie absente impose d’inspecter cette tâche et la passerelle, pas de créer une deuxième programmation. Les messages d’état ne doivent contenir ni contenu ZIP, ni secrets, ni identifiants.
4. Prouver la récupération et l’import sur une machine vierge
Utilisez une machine Linux temporaire distincte, sans profil ni passerelle Hermes de production. Installez-y d’abord une version compatible de Hermes : l’archive contient des données utilisateur, pas une image d’application ou d’environnement. Donnez à la machine d’essai un accès en lecture indépendant et autorisé au dépôt privé prévu ainsi qu’à l’identité age conservée indépendamment. Vérifiez le dépôt distant du clone et le commit choisi. N’exécutez pas hermes import sur la machine source ou un profil existant. Laissez toutes les programmations et actions externes récupérées inactives jusqu’à ce qu’une personne examine leurs destinations et décide de leur activation.
umask 077
unset HERMES_HOME
git clone git@github.com:ACCOUNT/PRIVATE-REPOSITORY.git /PRIVATE-RECOVERY/hermes-backup-clone
cd /PRIVATE-RECOVERY/hermes-backup-clone
sha256sum -c latest.sha256
age --decrypt -i /PRIVATE-KEY-STORE/hermes-backup-identity.txt -o /PRIVATE-RECOVERY/hermes.zip latest.zip.age
unzip -tq /PRIVATE-RECOVERY/hermes.zip
hermes import /PRIVATE-RECOVERY/hermes.zip
hermes setup
hermes cron listVérifiez que profils, compétences, mémoires, sessions et définitions cron importés correspondent aux éléments à protéger. hermes setup vérifie la configuration des fournisseurs, mais examinez les identifiants ou chemins propres à la machine à ajuster. Notez les résultats réels. Une somme de contrôle correcte et un ZIP déchiffrable ne prouvent pas seuls une restauration utilisable. Après l’exercice, supprimez le ZIP d’essai en clair selon votre politique locale ; une suppression ordinaire ne garantit pas l’effacement sécurisé.
Si les vérifications s’arrêtent
Une sauvegarde Hermes incomplète, une erreur ZIP ou de chiffrement, une URL Git effective inattendue, un dépôt non privé ou illisible, un verrou détenu, un seuil de taille, un envoi échoué, un commit distant différent ou un import échoué laisse cette exécution non confirmée. Résolvez la cause précise et revérifiez l’état actuel avant une nouvelle tentative. Si l’issue d’un envoi est incertaine, inspectez d’abord la branche distante et le commit exacts. Si le verrou reste après un plantage, recherchez un processus vivant et examinez l’état local et distant ; ne supprimez jamais un verrou actif. Si l’identité privée age est perdue, une copie indépendante valide est indispensable au déchiffrement. Si des secrets en clair entrent dans Git, remplacez d’abord les identifiants exposés ; supprimer un fichier ultérieur n’efface ni l’historique ni les autres clones.
Les recommandations GitHub pour les gros fichiers avertissent à 50 MiB, bloquent les fichiers Git ordinaires au-delà de 100 MiB et précisent que Git n’est pas conçu comme outil de sauvegarde. Cet exemple s’arrête volontairement sous ces limites. Chaque version du fichier chiffré demeure dans l’historique Git, même lorsque latest.zip.age est remplacé. N’augmentez pas discrètement les seuils de 40 MB/500 MB, ne réécrivez pas l’historique et ne confondez pas accès privé et chiffrement. Réévaluez un stockage dédié aux sauvegardes ou un plan LFS vérifié séparément si les contrôles de taille ou de récupération échouent. Les coûts du compte, du stockage, du réseau et de la machine restent applicables ; le script sans agent ne sollicite pas lui-même de LLM. Aucune durée, aucun taux de réussite ni aucune économie n’ont été mesurés.
Pour une tâche Hermes de programmation distincte et plus restreinte, consultez comment créer une tâche Hermes récurrente appuyée sur une compétence et vérifier sa première exécution. Le contrôle du rapport de cette tâche ne remplace pas une archive chiffrée et un exercice de récupération.
Sources et lectures complémentaires
- Commandes CLI Hermes Agent, commit épinglé
86fa9e6— Commande de sauvegarde complète, périmètre, exclusions et codes de sortie. Source épinglée vérifiée le 19 octobre 2026 ; elle ne prouve pas que cet exemple a été exécuté sur un vrai profil. - FAQ Hermes Agent, même commit épinglé — Transfert de sauvegarde complète,
hermes importethermes setup; l’archive contient des données, pas une image d’environnement. - Manuel des tâches programmées Hermes, même commit épinglé — Mode script sans agent, remise des résultats, environnement épuré, passerelle et délai maximal. Vérifiez l’aide de la version installée avant de programmer.
- README d’age — Chiffrement avec destinataire et déchiffrement avec identité. La page évolutive
maina été vérifiée le 19 octobre 2026 ; le jeu d’essai inerte n’a pas testé le vrai chiffrement. - Confidentialité des dépôts GitHub, Création de dépôt et Commande repo view de GitHub CLI — Accès privé et vérification de visibilité. L’accès privé dépend toujours des collaborateurs et identifiants vérifiés.
- Git remote et Git push — Destinations effectives et références envoyées ; le script ajoute sa propre vérification du commit de tête de cette exécution.
- Recommandations GitHub pour les gros fichiers et Suppression des données sensibles — Limites de taille et d’historique Git et nécessité de remplacer les identifiants exposés plutôt que de simplement supprimer un fichier. Manuels évolutifs vérifiés le 19 octobre 2026.



