Panduan ini untuk operator Hermes Agent yang dihosting sendiri dan dapat mengelola akun Linux serta repositori GitHub privat. Hasilnya khusus: arsip lengkap data pengguna Hermes, dienkripsi sebelum meninggalkan host, dikonfirmasi di repositori jarak jauh privat yang dituju, lalu diambil dan diimpor pada mesin uji terpisah yang dapat dibuang. Prosedur terdokumentasi ini mengacu pada Hermes CLI di commit 86fa9e6, diperiksa pada 19 Oktober 2026. BIG CHANGE memeriksa sintaks shell dan alur kontrol menggunakan bahan uji lokal inert; tidak menjalankan cadangan Hermes nyata, enkripsi age, pengiriman GitHub, pemicu terjadwal, atau pemulihan. Anggap cadangan terbukti untuk instalasi Anda hanya setelah Anda melakukan semua pemeriksaan berikut.

Perubahan besar

  • Apa yang berubah: Perintah cadangan lengkap Hermes dapat mengumpulkan data kerja ke ZIP, sedangkan mode cron tanpa agen dapat menjalankan skrip yang ditinjau. Bersama-sama, operator dapat membuat salinan terenkripsi terjadwal tanpa meminta model AI melakukan pencadangan.
  • Mengapa penting: Operator memeriksa tiga fakta terpisah: Hermes menyelesaikan arsip, data terenkripsi tiba di repositori privat yang dituju, dan instalasi baru dapat mengimpornya. Tiap pemeriksaan menangkap kegagalan berbeda. Arsip tetap mengecualikan aplikasi dan sebagian data lokal mesin.
  • Yang perlu diperhatikan: Keputusan praktisnya adalah apakah arsip dan riwayat Git yang terus tumbuh tetap cukup kecil untuk jalur terbatas ini. Latihan pemulihan yang gagal atau salah satu batas ukuran mengharuskan rencana penyimpanan lain sebelum mengandalkan jadwal.

1. Siapkan host, kunci, dan repositori

Gunakan Linux dengan Bash, perangkat GNU stat -c dan du -sb, ditambah Hermes CLI terpasang, Git, GitHub CLI (gh), age, unzip, sha256sum, SSH, dan gateway Hermes aktif untuk cron. Pastikan perintah terpasang hermes backup, hermes import dan hermes cron create --help sesuai dengan referensi CLI pada commit tetap, tanya jawab dan manual cron. Tentukan pengguna OS dan HERMES_HOME pemilik gateway. Jalankan semua pengaturan host dan cadangan manual pertama sebagai pengguna tersebut. Contoh mengasumsikan klona Git khusus yang bersih dan cabang main.

Cadangan lengkap Hermes mencakup konfigurasi, kredensial, memori, keterampilan, sesi, dan profil di bawah akar data. Tidak mencakup kode Hermes, unduhan dependensi atau runtime, cadangan sebelumnya, titik pemeriksaan, profil kredensial peramban, berkas pendamping SQLite, dan beberapa berkas lokal. Tidak mengumpulkan berkas lain yang tidak terkait di host. Jangan gunakan --quick untuk hasil cadangan lengkap ini. Tinjau materi penting di luar cakupan tersebut dan lindungi terpisah. ZIP memuat rahasia meskipun repositori tujuan privat.

Buat identitas age, kunci privat untuk mendekripsi arsip, pada lokasi terlindungi di luar HERMES_HOME, direktori persiapan sementara, dan klona Git. Simpan salinan pemulihan identitas secara independen. Penerima yang dicetak age1… bersifat publik dan masuk konfigurasi; identitas privat tidak. Ikuti referensi penggunaan 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

Buat repositori privat khusus cadangan ini, periksa pemilik akses, dan atur pembacaan noninteraktif melalui gh serta penulisan Git SSH untuk pengguna OS yang sama dengan Hermes cron. Simpan token dan kunci SSH privat di luar data Hermes, persiapan, dan Git. Login interaktif dengan akun lain tidak membuktikan akses terjadwal. Ganti penanda huruf besar sebelum menjalankan contoh. Jika pembuatan repositori sudah membuat README atau cabang main, periksa dan lewati perintah inisialisasi yang bertentangan.

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 dapat memilih HTTPS dari preferensi lokal; alamat SSH eksplisit di atas menentukan tujuan resep ini. Lanjutkan hanya jika isPrivate mengembalikan true, kolaborator sesuai rencana, URL pengambilan dan pengiriman efektif masing-masing tepat sama dengan satu URL SSH yang ditinjau, dan akses jarak jauh berhasil sebagai pengguna terjadwal. Aturan remote Git mengizinkan penulisan ulang URL, tujuan tambahan, dan pushurl terpisah; skrip memeriksa nilai mentah dan efektif setiap kali berjalan.

Keluar dari klona sebelum menyimpan dua blok berikut. Buat direktori pengaturan privat di luar akar data Hermes, klona Git, persiapan, dan penyimpanan kunci. Simpan hermes-github-backup.sh dan hermes-github-backup.conf di sana; perintah instalasi menyalinnya ke profil pemilik. Klona Git khusus harus tetap bersih.

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

2. Pasang skrip cadangan yang ditinjau

Simpan seluruh blok Bash berikut sebagai hermes-github-backup.sh dalam direktori pengaturan privat tersebut. Simpan blok kedua sebagai hermes-github-backup.conf di sebelahnya setelah mengganti semua penanda dengan jalur absolut atau penerima publik. Konfigurasi shell ini harus ditinjau secara lokal sebelum dimuat dan hanya dapat diakses pemilik. Tidak boleh memuat kata sandi, token, atau identitas privat age. Skrip adalah contoh usulan lengkap; sintaks dan alur inert diperiksa, tetapi sesuaikan dan uji pada instalasi Anda. Berkas latest.zip.age yang dihasilkan adalah arsip terenkripsi.

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

Buat persiapan di luar akar data Hermes dan klona Git. Letakkan berkas yang ditinjau dalam direktori scripts/ pada profil pemilik. Sebelum tiap proses, klona harus bersih dan sejajar dengan main di remote. Skrip mengunci seluruh alur sampai pengiriman dan pembacaan kembali; tidak merebut kunci yang dipegang atau lama. Hanya menerima latest.zip.age dan latest.sha256 sebagai jalur cadangan baru yang dipersiapkan untuk Git. Berhenti jika data terenkripsi melebihi 40.000.000 byte atau .git lokal ditambah ruang cadangan mendekati 500.000.000 byte. Ini batas konservatif contoh, bukan jaminan penyimpanan 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"

Proses manual seharusnya keluar dengan kode nol dan mencetak commit yang dibuat oleh proses itu sendiri setelah pembacaan remote baru. Pastikan gh repo view tetap melaporkan privat, periksa jalur yang dilacak klona, dan verifikasi commit persis pada remote yang dituju. Arsip atau commit lokal bukan hasil di luar host. Jangan catat proses terhenti sebagai hasil tersebut. Menurut Hermes, cadangan lengkap mengembalikan 0 hanya ketika semua berkas terpilih ditambahkan, 1 untuk arsip tidak lengkap meskipun ZIP ada, dan 2 ketika cadangan lain berjalan. Skrip menghapus ZIP sementara dan log saat keluar; penghapusan biasa bukan penghapusan aman. Untuk berkas yang dilewati, selidiki dengan cadangan manual diagnostik terpisah yang terkendali hermes backup -o /PRIVATE-DIAGNOSTIC/hermes.zip, periksa status keluar dan laporannya, lalu tentukan perlindungan materi yang terlewat.

3. Jadwalkan dan periksa proses pertama

Hanya setelah hasil manual, gunakan mode Hermes terdokumentasi --no-agent --script dari profil yang sama. Mode ini menjalankan berkas tanpa giliran LLM. Pilih jadwal secara sengaja dan kirim status lokal tanpa rahasia. Periksa identitas OS gateway, profil, dan waktu berikutnya. Menurut Hermes, batas waktu skrip berasal dari HERMES_CRON_SCRIPT_TIMEOUT, lalu cron.script_timeout_seconds, lalu nilai bawaan 3.600 detik. Tetapkan dan periksa nilai yang sesuai dengan proses yang diamati; kehabisan waktu adalah kegagalan. Lingkungan skrip yang dibersihkan tidak otomatis menerima kredensial penyedia yang dikelola Hermes, sehingga verifikasi akses gh dan SSH tanpa pengawasan sebagai pengguna terjadwal sebenarnya sebelum mengandalkannya.

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

Setelah waktu terjadwal pertama, periksa ID tugas dan catatan proses yang tepat, keluaran lokal di ~/.hermes/cron/output/ untuk profil bawaan, catatan insiden, dan commit teratas pada remote yang dituju. Pastikan gateway tetap tersedia dan proses melaporkan commit remote yang dibuat pemicu tersebut. Pemanggilan manual tidak membuktikan cron berjalan. Keluaran hilang harus ditangani dengan memeriksa tugas dan gateway, bukan membuat jadwal kedua. Jangan masukkan isi ZIP, rahasia, atau kredensial dalam pesan status.

4. Buktikan pengambilan dan impor pada mesin baru

Gunakan mesin Linux uji sekali pakai yang terpisah, tanpa profil atau gateway Hermes produksi. Pasang rilis Hermes yang kompatibel terlebih dahulu: arsip berisi data pengguna, bukan citra aplikasi atau runtime. Beri mesin uji akses baca independen yang diizinkan ke repositori privat tujuan dan identitas age yang disimpan independen. Pastikan remote klona dan commit yang dipilih. Jangan jalankan hermes import pada host sumber atau profil yang sudah ada. Biarkan semua jadwal dan tindakan eksternal yang dipulihkan nonaktif sampai seseorang meninjau tujuan dan memutuskan mengaktifkannya.

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

Bandingkan profil, keterampilan, memori, sesi, dan definisi cron hasil impor dengan yang hendak dilindungi. hermes setup memeriksa konfigurasi penyedia, tetapi periksa juga kredensial dan jalur khusus mesin yang perlu disesuaikan. Catat hasil sebenarnya. Checksum cocok dan ZIP yang dapat didekripsi saja tidak membuktikan pemulihan dapat digunakan. Hapus ZIP uji berteks jelas setelah latihan sesuai kebijakan lokal; penghapusan biasa tidak menjamin penghapusan aman.

Jika pemeriksaan berhenti

Cadangan Hermes tidak lengkap, kesalahan ZIP atau enkripsi, URL Git efektif tidak terduga, repositori tidak privat atau tidak terbaca, kunci alur dipegang, batas ukuran, pengiriman gagal, commit remote tidak cocok, atau impor gagal membuat proses ini belum terkonfirmasi. Selesaikan penyebab spesifik dan periksa keadaan saat ini sebelum mencoba lagi. Jika hasil pengiriman tidak pasti, periksa dahulu cabang dan commit remote persisnya. Jika kunci tertinggal setelah gangguan, periksa proses aktif dan keadaan lokal/remote; jangan hapus kunci aktif. Jika identitas age privat hilang, diperlukan salinan independen yang valid untuk dekripsi. Ganti kredensial bocor terlebih dahulu jika rahasia teks jelas masuk Git; menghapus berkas kemudian tidak menghapus riwayat lama atau klona lain.

Panduan berkas besar GitHub memperingatkan pada 50 MiB, memblokir berkas Git biasa di atas 100 MiB, dan menyatakan Git tidak dirancang sebagai alat cadangan. Contoh sengaja berhenti di bawah batas itu. Setiap perubahan data terenkripsi tetap ada dalam riwayat Git meskipun latest.zip.age diganti. Jangan diam-diam menaikkan batas 40 MB/500 MB, menulis ulang riwayat, atau menganggap privat berarti enkripsi. Pertimbangkan penyimpanan khusus cadangan atau rencana LFS yang ditinjau terpisah jika pemeriksaan ukuran atau pemulihan gagal. Biaya akun, penyimpanan, jaringan, dan host tetap berlaku; skrip tanpa agen sendiri tidak memerlukan giliran LLM. Tidak ada pengukuran durasi, tingkat keberhasilan, atau penghematan.

Untuk tugas penjadwalan Hermes lain yang lebih sempit, lihat cara menyiapkan tugas Hermes berulang berbasis keterampilan dan memverifikasi proses pertama. Pemeriksaan laporannya tidak menggantikan arsip terenkripsi dan latihan pemulihan.

Sumber dan bacaan lanjutan