Руководство предназначено оператору самостоятельно размещённого Hermes Agent с правами управления учётной записью Linux и закрытым репозиторием GitHub. Результат конкретен: полный архив пользовательских данных Hermes, зашифрованный до выхода с хоста, подтверждённый в нужном закрытом удалённом репозитории и полученный и импортированный на отдельной временной машине. Процедура основана на Hermes CLI в коммите 86fa9e6, проверенном 19 октября 2026 года. BIG CHANGE проверила синтаксис и поток управления на инертных локальных тестовых данных; реальное копирование Hermes, шифрование age, отправка GitHub, запуск по расписанию и восстановление не выполнялись. Считайте копию доказанной для своей установки только после всех перечисленных проверок.

Главное изменение

  • Что изменилось: Команда полного копирования Hermes собирает рабочие данные в ZIP, а режим cron без агента запускает проверенный скрипт. Вместе они позволяют планировать зашифрованную копию, не поручая резервирование модели ИИ.
  • Почему это важно: Оператор проверяет три отдельных факта: Hermes завершил архив, шифротекст дошёл до нужного закрытого репозитория и новая установка может его импортировать. Проверки выявляют разные отказы. Приложение и часть локальных данных машины по-прежнему исключены.
  • На что обратить внимание: Практический выбор зависит от того, остаются ли архив и растущая история Git достаточно малыми для этого ограниченного пути. Неудачная учебная процедура восстановления или любой предел размера требуют иного плана хранения до использования расписания.

1. Подготовьте хост, ключ и репозиторий

Используйте Linux с Bash, GNU stat -c и du -sb, установленными Hermes CLI, Git, GitHub CLI (gh), age, unzip, sha256sum, SSH и работающим шлюзом Hermes для cron. Убедитесь, что установленные hermes backup, hermes import и hermes cron create --help соответствуют справочнику CLI закреплённого коммита, ответам на частые вопросы и руководству cron. Определите пользователя ОС и HERMES_HOME шлюза. Всю настройку и первую ручную копию выполняйте этим пользователем. Пример предполагает отдельный чистый клон Git и ветку main.

Полная копия Hermes включает конфигурацию, учётные данные, память, навыки, сеансы и профили в корне данных. Исключены код Hermes, загрузки зависимостей и среды выполнения, прежние копии, контрольные точки, профили браузерных учётных данных, сопутствующие файлы SQLite и некоторые локальные файлы. Посторонние файлы в других местах хоста не собираются. Не используйте --quick для этой полной копии данных. Важные материалы за пределами области проверьте и защитите отдельно. ZIP содержит секреты даже при закрытом репозитории назначения.

Создайте идентификатор age — закрытый ключ для расшифровки — в защищённом месте вне HERMES_HOME, промежуточного каталога и клона Git. Храните независимую копию идентификатора для восстановления. Напечатанный получатель age1… является открытым и указывается в конфигурации; закрытый идентификатор — нет. Следуйте справочнику использования age.

Terminal
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.txt

Создайте закрытый репозиторий только для этой копии, проверьте доступ и настройте неинтерактивное чтение через gh и запись Git SSH для того же пользователя ОС, который запускает Hermes cron. Токены и закрытые SSH-ключи держите вне данных Hermes, промежуточного каталога и Git. Интерактивный вход другой учётной записью не доказывает доступ по расписанию. Замените заглавные заполнители до запуска примеров. Если при создании репозитория уже появились README или ветка main, проверьте их и пропустите конфликтующие команды инициализации.

Terminal
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 main

gh repo create --clone может выбрать HTTPS из локальных настроек; явно указанный SSH remote выше задаёт цель этого рецепта. Продолжайте только когда isPrivate возвращает true, участники ожидаемые, действующие URL чтения и отправки каждый точно равны единственному проверенному SSH URL, а доступ работает от пользователя расписания. Правила удалённых репозиториев Git допускают переписывание URL, дополнительные назначения и отдельный pushurl; скрипт при каждом запуске проверяет исходные и действующие значения.

Выйдите из клона до сохранения следующих двух блоков кода. Создайте закрытый каталог настройки вне корня данных Hermes, клона Git, промежуточного каталога и хранилища ключей. Сохраните hermes-github-backup.sh и hermes-github-backup.conf там; команды установки скопируют их в профиль владельца. Выделенный клон Git должен оставаться чистым.

Terminal
install -d -m 700 /ABSOLUTE/PRIVATE-SETUP
cd /ABSOLUTE/PRIVATE-SETUP

2. Установите проверенный скрипт копирования

Сохраните весь следующий блок Bash как hermes-github-backup.sh в этом закрытом каталоге. Второй блок сохраните как hermes-github-backup.conf рядом, заменив все заполнители абсолютными путями или открытым получателем. Это локально проверяемая конфигурация shell: изучите её до загрузки и дайте доступ только владельцу. Пароль, токен или закрытый идентификатор age недопустимы. Это полный предлагаемый пример; синтаксис и инертный поток проверены, но адаптация и проверка в вашей установке необходимы. Получаемый latest.zip.age — зашифрованный архив.

Terminal
#!/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"
Terminal
# 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=main

Создайте промежуточный каталог вне корня данных Hermes и клона Git. Проверенные файлы положите в каталог scripts/ профиля владельца. Перед каждым запуском клон должен быть чистым и согласованным с удалённой main. Скрипт блокирует весь поток до отправки и обратного чтения; он не захватывает удерживаемую или старую блокировку. Как новые пути копии, подготовленные к Git, разрешены только latest.zip.age и latest.sha256. Он останавливается, если шифротекст превышает 40 000 000 байт или локальный .git с запасом приближается к 500 000 000 байт. Это консервативные пределы примера, не гарантии хранения GitHub.

Terminal
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"

Ручной запуск должен завершиться кодом ноль и после нового чтения remote вывести коммит, созданный именно этим запуском. Подтвердите, что gh repo view всё ещё сообщает закрытый доступ, проверьте отслеживаемые пути клона и точный коммит в нужном remote. Локальный архив или коммит не является внешней копией. Остановленный запуск нельзя так записывать. По данным Hermes, полное копирование возвращает 0 только когда добавлены все выбранные файлы, 1 при неполном архиве, даже если ZIP существует, и 2 при другом работающем копировании. Скрипт удаляет временный ZIP и журнал при выходе; обычное удаление не является безопасным стиранием. Пропущенные файлы исследуйте отдельным контролируемым ручным hermes backup -o /PRIVATE-DIAGNOSTIC/hermes.zip, проверьте код выхода и отчёт и решите, как защитить пропущенное.

3. Запланируйте и проверьте первый запуск

Только после ручного результата используйте документированный режим Hermes --no-agent --script из того же профиля. Он запускает файл без обращения к LLM. Осознанно выберите частоту и локальную доставку статуса без секретов. Проверьте пользователя шлюза, профиль и следующий срок. По данным Hermes, тайм-аут берётся из HERMES_CRON_SCRIPT_TIMEOUT, затем cron.script_timeout_seconds, затем применяется значение по умолчанию 3 600 секунд. Установите и проверьте значение под наблюдаемый запуск; превышение времени — отказ. Очищенное окружение скрипта не получает автоматически управляемые Hermes учётные данные провайдера, поэтому заранее проверьте автономный gh и SSH-доступ от реального пользователя расписания.

Terminal
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 doctor

После первого запланированного срока проверьте точный ID задания, запись запуска, локальный вывод в ~/.hermes/cron/output/ для стандартного профиля, записи инцидентов и головной коммит нужного remote. Убедитесь, что шлюз оставался доступным, а запуск сообщил удалённый коммит именно этого срабатывания. Ручной вызов не доказывает работу cron. При отсутствии вывода проверяйте это задание и шлюз, не создавайте второе расписание. В сообщениях статуса не должно быть содержимого ZIP, секретов или учётных данных.

4. Докажите получение и импорт на новой машине

Используйте отдельную временную машину Linux без рабочего профиля или шлюза Hermes. Сначала установите совместимую версию Hermes: архив содержит пользовательские данные, не образ приложения или среды. Дайте тестовой машине независимый разрешённый доступ на чтение нужного закрытого репозитория и независимо сохранённого идентификатора age. Проверьте remote клона и выбранный коммит. Не запускайте hermes import на исходном хосте или существующем профиле. Все восстановленные расписания и внешние действия должны оставаться выключенными до проверки их адресатов человеком и решения об активации.

Terminal
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 list

Сравните импортированные профили, навыки, память, сеансы и определения cron с тем, что хотели защитить. hermes setup проверяет конфигурацию провайдера, но осмотрите также учётные данные и машинные пути, требующие изменений. Запишите фактические результаты. Совпадение контрольной суммы и расшифровываемый ZIP сами по себе не доказывают пригодное восстановление. После учения удалите открытый тестовый ZIP по местным правилам обращения; обычное удаление не гарантирует безопасное стирание.

Когда проверки останавливаются

Неполная копия Hermes, ошибка ZIP или шифрования, неожиданная действующая Git URL, незакрытый или нечитаемый репозиторий, занятая блокировка потока, предел размера, неудачная отправка, несовпадение удалённого коммита или ошибка импорта оставляют запуск неподтверждённым. Устраните конкретную причину и проверьте текущее состояние до повторения. При неопределённой отправке сначала проверьте точную удалённую ветку и коммит. Если после сбоя осталась блокировка, исследуйте живой процесс и локальное/удалённое состояние; никогда не удаляйте активную блокировку. Потеря закрытого идентификатора age требует независимой действительной копии для расшифровки. Если секреты в открытом виде попали в Git, сначала смените раскрытые учётные данные; позднейшее удаление файла не убирает старую историю или другие клоны.

Рекомендации GitHub по большим файлам предупреждают при 50 MiB, блокируют обычные файлы Git свыше 100 MiB и говорят, что Git не предназначен для резервирования. Пример намеренно останавливается ниже этих пределов. Каждый изменённый шифротекст остаётся в истории Git, даже когда latest.zip.age заменяется. Не повышайте молча пределы 40 MB/500 MB, не переписывайте историю и не считайте закрытый доступ шифрованием. При провале проверки размера или восстановления пересмотрите специальное хранилище копий либо отдельно проверенный план LFS. Расходы на аккаунт, хранение, сеть и хост сохраняются; сам скрипт без агента не требует вызова LLM. Длительность, доля успехов и экономия не измерялись.

Отдельную более узкую задачу Hermes описывает настройка повторяющегося задания Hermes на основе навыка с проверкой первого запуска. Проверка его отчёта не заменяет зашифрованный архив и учение по восстановлению.

Источники и дополнительное чтение