Files
OpenBAO/scripts/backup-raft-snapshots.sh
Lutz Finsterle 14f46e1082 Add unsealer, internal CA/TLS, auto-unseal, and automated cert+backup
Build out the home-lab OpenBAO deployment beyond the basic node:

- docker-compose: add openbao-unsealer sidecar; main node now transit
  auto-unseals against it (seal config kept in gitignored config/seal.hcl)
- policies/admin.hcl: non-root admin policy; per-engine rules for
  ssh/pki/pki_int/totp/transit
- Internal two-tier CA (pki/ root + pki_int/ intermediate) issues the
  openbao.famfi.home leaf Traefik serves; root CA published under ca/
- scripts/ + systemd/: daily cert renewal and Raft snapshot backups
  (both instances), with scoped tokens stored outside the repo
- README: full runbook (auto-unseal, PKI, renewal, backups, DR/restore)

Secrets (init/unsealer keys, tokens, seal stanza) stay gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:28:49 +02:00

56 lines
2.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Take Raft snapshots of BOTH OpenBAO instances (main + unsealer) and prune old
# ones. Run as root (reads the root-only backup tokens, writes /var/backups).
#
# DR note: a restored MAIN snapshot can only be unsealed with the unsealer's
# transit key — so the unsealer snapshot (+ its unseal key in unsealer-init.json
# + the recovery keys in init-output.json) are part of the same recovery set.
#
# Installed as a systemd timer (openbao-backup.timer). Manual run:
# sudo /home/lutz/Projects/OpenBAO/scripts/backup-raft-snapshots.sh
set -euo pipefail
PROJECT_DIR="/home/lutz/Projects/OpenBAO"
BACKUP_ROOT="${BACKUP_ROOT:-/var/backups/openbao}"
KEEP="${KEEP:-14}" # how many snapshots to retain per instance
STAMP="$(date '+%Y%m%d-%H%M%S')"
log() { printf '%s [backup] %s\n' "$(date '+%F %T')" "$*"; }
die() { log "ERROR: $*"; exit 1; }
cd "$PROJECT_DIR" || die "cannot cd to $PROJECT_DIR"
# snapshot <service> <token-file> <label>
snapshot() {
local svc="$1" tokfile="$2" label="$3"
[ -r "$tokfile" ] || die "token file $tokfile not readable (run as root?)"
local tok dir out incontainer="/tmp/${label}-${STAMP}.snap"
tok="$(cat "$tokfile")"
dir="${BACKUP_ROOT}/${label}"
out="${dir}/openbao-${label}-${STAMP}.snap"
install -d -o root -g root -m 0700 "$dir"
docker compose exec -T -e BAO_TOKEN="$tok" "$svc" \
bao operator raft snapshot save "$incontainer" >/dev/null \
|| die "snapshot save failed for $svc"
docker compose cp "${svc}:${incontainer}" "$out" >/dev/null \
|| die "copy-out failed for $svc"
docker compose exec -T "$svc" rm -f "$incontainer" >/dev/null 2>&1 || true
chmod 0600 "$out"
# Validate: non-trivial size + gzip integrity (a raft snapshot is a gzip archive)
local size; size=$(stat -c %s "$out")
[ "$size" -ge 1024 ] || die "snapshot $out suspiciously small (${size}B)"
gzip -t "$out" 2>/dev/null && log "OK ${label}: ${out} (${size}B, gzip-valid)" \
|| die "snapshot $out failed gzip integrity check"
# Retention: keep newest $KEEP, delete the rest
ls -1t "${dir}"/openbao-${label}-*.snap 2>/dev/null | tail -n +$((KEEP + 1)) | while read -r old; do
rm -f -- "$old"; log "pruned old snapshot $(basename "$old")"
done
}
snapshot "openbao" "/etc/openbao-backup.token" "main"
snapshot "openbao-unsealer" "/etc/openbao-unsealer-backup.token" "unsealer"
log "done; retained up to ${KEEP} snapshots per instance under ${BACKUP_ROOT}"