Files
backup-agent/CLAUDE.md
T

3.5 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

Backup + disaster-recovery tooling for a Linux server: Borg (encrypted, offsite-synced via rclone) backing up /home/srv/files/content, including MariaDB running in Docker. Plain bash, no build system, no CI. RUNBOOK.md is the operational doc (setup, deploy, cron, day-2 ops, recovery). old/ holds the legacy scripts this replaced — reference only, not maintained.

  • borg-backup.sh — daily backup orchestrator (cron). Never stops MariaDB: dump_db.sh's --single-transaction dump is consistent on its own, and the raw data directory is excluded from the archive via a .nobackup marker file.
  • dump_db.sh — per-database mysqldump/mariadb-dump with atomic staging/swap. Deployed to /home/srv/files/content/mariadb/dump_db.sh (borg-backup.sh invokes it at that exact path).
  • restore.sh — recovery CLI: full (disaster recovery), db <name>, file <path>, --list-archives, all supporting --dry-run.

Global conventions (don't re-derive these)

  • TARGET=/home/srv/files/content, REPO=/home/srv/files/backups/borg-2025, DB_CONTAINER=mariadb
  • BORG_PASSPHRASE_FILE=/root/.borg-passphrase (chmod 600, read via BORG_PASSCOMMAND)
  • Backup DB user backup (password file /root/.mariadb-backup.pw, limited SELECT/LOCK grants) is separate from restore's root creds (MYSQL_ROOT_PASSWORD env or /root/.mariadb-root.pw) — restore needs CREATE/DROP privileges the backup user doesn't have.
  • Dumps live at mariadb/dump/*.sql plus 00-users-and-grants.sql, inside $TARGET.
  • restore.sh and borg-backup.sh share one lockfile (/var/lock/borg-backup.lock via flock -n 9) so a restore and the nightly backup can never run concurrently.
  • Destructive-op safety: full refuses a non-empty $TARGET without --force; db requires typed dbname confirmation unless --yes; every mode supports --dry-run.
  • resolve_archive() in restore.sh deliberately does not filter archives by hostname (unlike backup's prune/list, which does) — disaster recovery may run from a different host than made the backup.
  • Logs: /var/log/borg/{backup,restore}-*.log (override with BORG_BACKUP_LOGFILE / RESTORE_LOGDIR for local testing).

Testing

RESTORE_LOGDIR=/tmp/restore-test-logs bash tests/test_restore.sh

Plain bash, no framework (assert_eq/assert_contains + a $FAILURES counter in tests/test_restore.sh). tests/lib/setup_mocks.sh builds fake borg/docker/mysql/mariadb/flock executables into a temp dir.

Mock isolation requires all three of:

  • PATH="$mockdir:$PATH"
  • BASH_ENV="$(mock_bash_env "$mockdir")" — both scripts prepend a hardened PATH with system dirs (/usr/local/bin etc.) ahead of $PATH, so a real binary there would win over a PATH-only mock. BASH_ENV shell functions take priority regardless of PATH order.
  • Env overrides for LOCKFILE / BORG_PASSPHRASE_FILE / ROOT_PASSWORD_FILE pointed at throwaway paths, so acquire_lock()/preflight() never touch real /var/lock or /root.

flock(1) (util-linux) doesn't exist on macOS — that's why it's mocked too, not just for isolation.

Conventions

  • Conventional commits (feat:, fix:, docs:, chore:). Direct commits to master, no PR workflow.
  • New restore/backup logic should mirror the existing style in borg-backup.sh/restore.sh: log()/step()/die()/run_cmd() helpers, set -euo pipefail, config constants up top.