Files
zorruno-backupdockerscript/README.md
T

214 lines
7.0 KiB
Markdown

# Zorruno Docker Backup Scripts
Modular backup scripts for Docker Compose servers.
Each server has its own untracked `.env` and `containers.local.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 a server
```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`. A typical Docker source and example host name are represented by:
```bash
SOURCE_BASE_DIR="/dockervolumes"
BACKUP_HOSTNAME="examplehostname"
```
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 the server'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 any server from replacing its local list.
When a listed directory contains other running Compose projects, the archive
stage also stops those nested projects before archiving and starts them again
afterward. Intentionally stopped nested projects are left stopped.
## 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 `<remote>/<BACKUP_HOSTNAME>/`. 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="examplehostname"
MQTT_PORT=1883
MQTT_USERNAME="dockerbackup"
MQTT_PASSWORD_FILE="/etc/zorruno-docker-backup/mqtt-password"
MQTT_STATUS_TOPIC="viewroad-status/dockerbackup/examplehostname"
MQTT_COMMAND_TOPIC="viewroad-commands/dockerbackup/examplehostname/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
<BACKUP_ROOT>/<project>/<host>.<project>.<date_time>.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.