SPB Git

Repository Public

backup.sh 2.9 KB 52 lines shellscript

1#!/bin/zsh2# Nightly encrypted backup of Agent OS metadata: the PostgreSQL database + an INDEX of the artifact store.3# NOT saved, on purpose: artifact contents, the workspace (project files), .env, provider keys (keys.env).4# Output: $BACKUP_DIR/agent-os-YYYYmmdd-HHMMSS.tar.gz.enc (AES-256, PBKDF2) ; keeps the last $KEEP archives.5# Requires: AOS_DATA_DIR, and DATABASE_URL — taken from the environment, else from the DATABASE_URL line of6# $AOS_ENV_FILE (default: <app dir>/.env; only that line is read, the file is never sourced nor archived).7# Passphrase: ~/.config/agent-os/backup.pass (0600, created on first run).8#9# Archive members: db.dump (pg_dump custom format), artifacts-index.tsv ("<bytes>\t<sha256[0:2]>/<sha256>" per10# stored artifact, sorted), artifacts-summary (count and total bytes), created_at.11set -euo pipefail12export PATH=/opt/homebrew/bin:/opt/homebrew/opt/postgresql@17/bin:/usr/bin:/bin:/usr/sbin:/sbin13APP=agent-os14HERE="$(cd "$(dirname "$0")" && pwd)"15ENV_FILE="${AOS_ENV_FILE:-$HERE/../.env}"16if [[ -z "${DATABASE_URL:-}" && -r "$ENV_FILE" ]]; then17  DATABASE_URL="$(sed -n 's/^DATABASE_URL=//p' "$ENV_FILE" | head -1)"18  DATABASE_URL="${DATABASE_URL%\"}"; DATABASE_URL="${DATABASE_URL#\"}"19  DATABASE_URL="${DATABASE_URL%\'}"; DATABASE_URL="${DATABASE_URL#\'}"20fi21: "${DATABASE_URL:?DATABASE_URL required (environment or $ENV_FILE)}"22: "${AOS_DATA_DIR:?AOS_DATA_DIR required}"23BACKUP_DIR="${BACKUP_DIR:-$HOME/backups/$APP}"24KEEP="${KEEP:-14}"25PASS_FILE="$HOME/.config/$APP/backup.pass"26umask 07727mkdir -p "$BACKUP_DIR" "$(dirname "$PASS_FILE")"28if [[ ! -s "$PASS_FILE" ]]; then29  openssl rand -base64 48 > "$PASS_FILE"30  chmod 600 "$PASS_FILE"31  echo "created backup passphrase at $PASS_FILE — copy it somewhere safe; without it backups cannot be restored"32fi33STAMP=$(date +%Y%m%d-%H%M%S)34WORK=$(mktemp -d)35trap 'rm -rf "$WORK"' EXIT36pg_dump --format=custom --no-owner --no-privileges "$DATABASE_URL" > "$WORK/db.dump"37# Artifact index only (sizes + content-addressed names), never the bytes: lets a restore tell which artifacts38# the database references and whether the store on disk still has them.39: > "$WORK/artifacts-index.tsv"40if [[ -d "$AOS_DATA_DIR/artifacts" ]]; then41  ( cd "$AOS_DATA_DIR/artifacts" && find . -type f -exec stat -f '%z%t%N' {} + 2>/dev/null \42      | sed 's|	\./|	|' | LC_ALL=C sort -t '	' -k2 ) > "$WORK/artifacts-index.tsv" || true43fi44awk -F '\t' '{ n++; b += $1 } END { printf "artifacts=%d bytes=%d\n", n, b }' "$WORK/artifacts-index.tsv" > "$WORK/artifacts-summary"45date -u +%Y-%m-%dT%H:%M:%SZ > "$WORK/created_at"46OUT="$BACKUP_DIR/$APP-$STAMP.tar.gz.enc"47tar -czf - -C "$WORK" . | openssl enc -aes-256-cbc -pbkdf2 -iter 200000 -salt -pass "file:$PASS_FILE" -out "$OUT"48chmod 600 "$OUT"49find "$BACKUP_DIR" -maxdepth 1 -type f -name "$APP-*.tar.gz.enc" | LC_ALL=C sort -r | tail -n +$((KEEP + 1)) \50  | while read -r f; do rm -f "$f"; done51echo "backup ok: $OUT ($(stat -f %z "$OUT") bytes, $(cat "$WORK/artifacts-summary"))"52
Files main
Menu

Theme