# Zorruno Docker Backup Scripts Modular backup scripts for Docker Compose servers such as Panda and Beaver. Each server has its own untracked `.env` and `containers.txt`, while all script logic remains common and can be updated through Git. ## Pipeline `backup.sh` runs these stages in order: 1. `stages/10-containers.sh` — stop, archive, pull, start and verify projects. 2. `stages/20-rclone.sh` — copy archives to an rclone destination when enabled. 3. `stages/30-smb.sh` — copy archives to a mounted SMB share when enabled. 4. `stages/90-prune.sh` — retain the newest configured number of `.zst` archives locally, including legacy `.zst` and current `.tar.zst` names. Every stage can also be run directly. The old `docker-backup.sh` name invokes the container archive stage, while `dockerbackup_prune.sh` invokes the local prune stage. Use the new `backup.sh` for the complete pipeline. ## Requirements - Bash 4 or later - Docker with `docker compose`, or legacy `docker-compose` - GNU `tar` with zstd support - `flock` - `rclone` only when the rclone method is enabled - `rsync` and `mountpoint` only when the SMB method is enabled The account running the scripts must be allowed to control Docker and read all files under `/dockervolumes`. ## Installation on Beaver ```bash sudo git clone https://home.fox.co.nz/gitea/zorruno/zorruno-backupdockerscript.git /backupdockerscripts cd /backupdockerscripts sudo cp .env.example .env sudo cp containers.txt containers.local.txt sudo chmod 600 .env sudo chmod +x backup.sh stages/*.sh ``` Edit `.env`. Beaver's Docker source is already represented by: ```bash SOURCE_BASE_DIR="/dockervolumes" BACKUP_HOSTNAME="beaver" ``` Choose and create a local staging path for `BACKUP_ROOT`. `/dockerbackups` is the default, but it should be on a filesystem with enough free space to hold at least `KEEP_COUNT` copies of every listed project. Edit Beaver's untracked `containers.local.txt`. Each active line can be either a running container name or a directory name beneath `/dockervolumes`. Blank lines and text after `#` are ignored. If several entries resolve to the same Compose project directory, that project is backed up only once. Keeping this host-local file out of Git prevents a pull on Panda or Beaver from replacing its list. ## Configuration Copy `.env.example` to `.env`; `.env` is ignored by Git. Important values: | Variable | Purpose | |---|---| | `SOURCE_BASE_DIR` | Parent of Compose project directories | | `BACKUP_ROOT` | Local archive staging and retention tree | | `CONTAINERS_FILE` | Absolute path, or path relative to this repository | | `BACKUP_HOSTNAME` | Host folder/name embedded in archive filenames | | `BACKUP_METHODS` | Ordered method names, default `rclone smb` | | `ZSTD_EXTRA_OPTS` | Options passed to `zstd`, default `-T0 -12` | | `KEEP_COUNT` | Local archives retained per Compose project | | `PULL_IMAGES` | Pull images before restarting (`1` or `0`) | ### Rclone ```bash RCLONE_ENABLED=1 RCLONE_REMOTE="gdrive:dockerbackups" ``` Archives are copied beneath `//`. The method uses `rclone copy`, so it does not delete older destination files. Set `RCLONE_HOST_SUBDIR=0` when `RCLONE_REMOTE` already names the final host-specific destination; otherwise the host name is appended automatically. ### SMB Mount the SMB share through `/etc/fstab` or a systemd mount unit; do not put SMB credentials in this repository. Then configure: ```bash SMB_ENABLED=1 SMB_MOUNT="/mnt/dockerbackups" SMB_HOST_SUBDIR=1 ``` The script refuses to copy unless `SMB_MOUNT` is an actual mount point. This prevents a failed network mount from silently filling the server's root disk. ## Running Run the complete pipeline: ```bash sudo /backupdockerscripts/backup.sh ``` Preview all stages without stopping containers, copying, or deleting: ```bash sudo /backupdockerscripts/backup.sh --dry-run ``` Select a different container list for one complete pipeline run: ```bash sudo /backupdockerscripts/backup.sh --containers containers-special.txt ``` Relative container-list paths are resolved from the repository directory; absolute paths are also accepted. The selected list affects only the container archive stage, after which configured transfer methods and pruning continue. Run individual parts: ```bash sudo stages/10-containers.sh --dry-run sudo stages/20-rclone.sh --dry-run --force sudo stages/30-smb.sh --dry-run --force sudo stages/90-prune.sh --dry-run ``` `--force` runs a transfer method even when its `*_ENABLED` value is false. It still requires the method's destination to be configured and available. ## MQTT reporting and Home Assistant trigger Enable retained run results in the host's `.env`: ```bash MQTT_REPORT_ENABLED=1 MQTT_HOST="beaver" MQTT_PORT=1883 MQTT_USERNAME="dockerbackup" MQTT_PASSWORD_FILE="/etc/zorruno-docker-backup/mqtt-password" MQTT_STATUS_TOPIC="viewroad-status/dockerbackup/panda" MQTT_COMMAND_TOPIC="viewroad-commands/dockerbackup/panda/run" ``` The retained status JSON includes overall status, timestamps, duration, exit code, container totals, successful backups, dry-run counts, and error messages. Final reporting runs from an exit trap, so failures in any earlier stage are reported when MQTT remains available. Install the optional root-run command listener with: ```bash cp systemd/zorruno-docker-backup-mqtt.service /etc/systemd/system/ systemctl daemon-reload systemctl enable --now zorruno-docker-backup-mqtt.service ``` It accepts only JSON containing the exact action `run` on the configured command topic: ```json {"action":"run"} ``` Publish commands without MQTT retain. The listener also ignores retained messages, preventing an old command from starting another backup after a listener or broker restart. Restrict publish access to the command topic with broker ACLs. The listener runs as root because the backup stage controls Docker containers. The existing pipeline lock prevents concurrent runs. ## Adding another backup method Add an executable script named `stages/NN-name.sh`, for example `stages/40-restic.sh`. It should load `lib/common.sh`, accept `--dry-run`, and return a non-zero status on failure. Add its short name to `.env`: ```bash BACKUP_METHODS="rclone smb restic" ``` The main pipeline discovers the numbered file from the short name and runs it after the earlier methods. A failed method stops the pipeline, so pruning will not run after a failed transfer. ## Archive and restore format Archives are stored as: ```text //...tar.zst ``` Each archive contains the project's top-level directory rather than absolute paths. Example restore into an empty temporary directory: ```bash mkdir -p /tmp/docker-restore tar --zstd -xf backup.tar.zst -C /tmp/docker-restore ``` Inspect and verify restored content before replacing a live project.