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-transactiondump is consistent on its own, and the raw data directory is excluded from the archive via a.nobackupmarker file.dump_db.sh— per-databasemysqldump/mariadb-dumpwith 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=mariadbBORG_PASSPHRASE_FILE=/root/.borg-passphrase(chmod 600, read viaBORG_PASSCOMMAND)- Backup DB user
backup(password file/root/.mariadb-backup.pw, limited SELECT/LOCK grants) is separate from restore'srootcreds (MYSQL_ROOT_PASSWORDenv or/root/.mariadb-root.pw) — restore needs CREATE/DROP privileges the backup user doesn't have. - Dumps live at
mariadb/dump/*.sqlplus00-users-and-grants.sql, inside$TARGET. restore.shandborg-backup.shshare one lockfile (/var/lock/borg-backup.lockviaflock -n 9) so a restore and the nightly backup can never run concurrently.- Destructive-op safety:
fullrefuses a non-empty$TARGETwithout--force;dbrequires typed dbname confirmation unless--yes; every mode supports--dry-run. resolve_archive()inrestore.shdeliberately 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 withBORG_BACKUP_LOGFILE/RESTORE_LOGDIRfor 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 hardenedPATHwith system dirs (/usr/local/binetc.) ahead of$PATH, so a real binary there would win over a PATH-only mock.BASH_ENVshell functions take priority regardless of PATH order.- Env overrides for
LOCKFILE/BORG_PASSPHRASE_FILE/ROOT_PASSWORD_FILEpointed at throwaway paths, soacquire_lock()/preflight()never touch real/var/lockor/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 tomaster, 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.