110 lines
3.2 KiB
Bash
Executable File
110 lines
3.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage:
|
|
hermes-emergency-bundle-create.sh [output-dir]
|
|
|
|
Creates a GPG-encrypted emergency bundle containing sensitive recovery files
|
|
that are intentionally excluded from the normal GitHub Hermes backups.
|
|
|
|
Default output-dir:
|
|
/root/hermes-emergency-bundles
|
|
|
|
Passphrase:
|
|
Interactive GPG prompt by default.
|
|
Or set BUNDLE_PASSPHRASE_FILE=/root/path/to/passphrase-file for unattended use.
|
|
|
|
Safety:
|
|
- Does not print secret values.
|
|
- Uses an allow-list of sensitive recovery files.
|
|
- Does not include logs, caches, locks, PIDs, or sandboxes.
|
|
USAGE
|
|
}
|
|
|
|
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
|
|
OUT_DIR="${1:-/root/hermes-emergency-bundles}"
|
|
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
HOST="$(hostname -s 2>/dev/null || hostname)"
|
|
WORK_DIR="$(mktemp -d)"
|
|
PAYLOAD_DIR="$WORK_DIR/payload"
|
|
ARCHIVE="$WORK_DIR/hermes-emergency-bundle-${HOST}-${STAMP}.tar.zst"
|
|
OUT_FILE="$OUT_DIR/hermes-emergency-bundle-${HOST}-${STAMP}.tar.zst.gpg"
|
|
|
|
cleanup() {
|
|
rm -rf "$WORK_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
install -d -m 700 "$OUT_DIR"
|
|
install -d -m 700 "$PAYLOAD_DIR"
|
|
|
|
copy_if_exists() {
|
|
src="$1"
|
|
dest="$PAYLOAD_DIR/${src#/}"
|
|
if [ -e "$src" ]; then
|
|
install -d -m 700 "$(dirname "$dest")"
|
|
cp -a "$src" "$dest"
|
|
printf '%s\n' "${src#/}" >> "$PAYLOAD_DIR/MANIFEST.paths"
|
|
fi
|
|
}
|
|
|
|
# Root Hermes sensitive state.
|
|
copy_if_exists /root/.hermes/.env
|
|
copy_if_exists /root/.hermes/auth.json
|
|
copy_if_exists /root/.hermes/state.db
|
|
copy_if_exists /root/.hermes/state.db-shm
|
|
copy_if_exists /root/.hermes/state.db-wal
|
|
|
|
# Uma Hermes sensitive state.
|
|
copy_if_exists /home/uma/.hermes/.env
|
|
copy_if_exists /home/uma/.hermes/auth.json
|
|
copy_if_exists /home/uma/.hermes/state.db
|
|
copy_if_exists /home/uma/.hermes/state.db-shm
|
|
copy_if_exists /home/uma/.hermes/state.db-wal
|
|
|
|
# Git and local registry credentials used for recovery operations.
|
|
copy_if_exists /root/.git-credentials
|
|
copy_if_exists /root/.gitea_admin_password
|
|
copy_if_exists /root/.gitea_npm_token
|
|
copy_if_exists /root/.gitea_npm_token_home
|
|
|
|
# Tailscale machine state is sensitive. Restoring it is optional; a fresh
|
|
# `tailscale up` login is often cleaner, but this preserves a break-glass copy.
|
|
copy_if_exists /var/lib/tailscale/tailscaled.state
|
|
|
|
if [ ! -s "$PAYLOAD_DIR/MANIFEST.paths" ]; then
|
|
echo "No emergency files found to bundle." >&2
|
|
exit 1
|
|
fi
|
|
|
|
cat > "$PAYLOAD_DIR/README.txt" <<README
|
|
Hermes emergency bundle for ${HOST}
|
|
Created UTC: ${STAMP}
|
|
|
|
This encrypted bundle contains sensitive files excluded from normal GitHub
|
|
backups, such as .env files, provider auth state, Git credentials, local Gitea
|
|
tokens, optional Tailscale state, and Hermes state.db files.
|
|
|
|
Decrypt only into a staging directory first. Inspect paths before copying
|
|
anything into a live VM.
|
|
README
|
|
|
|
tar -C "$PAYLOAD_DIR" -I zstd -cf "$ARCHIVE" .
|
|
|
|
gpg_args=(--symmetric --cipher-algo AES256 --output "$OUT_FILE")
|
|
if [ -n "${BUNDLE_PASSPHRASE_FILE:-}" ]; then
|
|
gpg_args=(--batch --yes --pinentry-mode loopback --passphrase-file "$BUNDLE_PASSPHRASE_FILE" "${gpg_args[@]}")
|
|
fi
|
|
|
|
gpg "${gpg_args[@]}" "$ARCHIVE"
|
|
chmod 600 "$OUT_FILE"
|
|
|
|
echo "Encrypted emergency bundle created: $OUT_FILE"
|
|
echo "Included path list is encrypted inside the bundle; no secret values printed."
|