Route the ad-hoc images I introduced through harbor.bitasylum.org (the standing rule): backup.sh uses harbor.bitasylum.org/dockerhub/library/alpine:3.20 (via an overridable ALPINE_IMAGE); the DEPLOY.md volume-chown fix + the goacme/lego prose use the Harbor paths too. Compose (influxdb/traefik) + Dockerfile bases are already harbor-mirrored; git.bitasylum.org/audiobuzz/* stays (CI-built).
60 lines
2.3 KiB
Bash
Executable File
60 lines
2.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# backup.sh — snapshot the fleet-manager durable state to timestamped tarballs.
|
|
#
|
|
# Two docker named volumes hold everything that isn't reproducible from the image:
|
|
# - fleet-data : inventory.json + admins.json + enroll.key (CRITICAL — losing
|
|
# admins.json/enroll.key locks every admin out of the UI)
|
|
# - influx-data : the metrics time-series (history; regenerates going forward)
|
|
#
|
|
# Runs a throwaway alpine container to tar each volume read-only into $DEST. Safe
|
|
# to run live. Schedule it (systemd timer / cron); copy $DEST offsite.
|
|
#
|
|
# Usage:
|
|
# ./backup.sh [dest-dir] # default dest: ./backups
|
|
# COMPOSE_PROJECT_NAME=foo ./backup.sh # if you run compose under another project
|
|
#
|
|
# Restore (manager stopped):
|
|
# docker run --rm -v <project>_fleet-data:/dest -v "$PWD/backups":/src \
|
|
# harbor.bitasylum.org/dockerhub/library/alpine:3.20 \
|
|
# sh -c 'rm -rf /dest/* && tar xzf /src/fleet-data-YYYYmmdd-HHMMSS.tar.gz -C /dest'
|
|
set -euo pipefail
|
|
|
|
DEST=${1:-./backups}
|
|
PROJECT=${COMPOSE_PROJECT_NAME:-fleet-manager} # compose derives volume names as <project>_<key>
|
|
STAMP=$(date -u +%Y%m%d-%H%M%S)
|
|
VOLUMES=${BACKUP_VOLUMES:-"fleet-data influx-data"}
|
|
# Non-CI images pull through the Harbor mirror; overridable for other registries.
|
|
ALPINE_IMAGE=${ALPINE_IMAGE:-harbor.bitasylum.org/dockerhub/library/alpine:3.20}
|
|
|
|
mkdir -p "$DEST"
|
|
DEST_ABS=$(cd "$DEST" && pwd)
|
|
|
|
for key in $VOLUMES; do
|
|
vol="${PROJECT}_${key}"
|
|
if ! docker volume inspect "$vol" >/dev/null 2>&1; then
|
|
echo "backup: SKIP $vol (no such volume — wrong COMPOSE_PROJECT_NAME?)"
|
|
continue
|
|
fi
|
|
out="${key}-${STAMP}.tar.gz"
|
|
echo "backup: $vol -> $DEST_ABS/$out"
|
|
docker run --rm \
|
|
-v "${vol}:/src:ro" \
|
|
-v "${DEST_ABS}:/backup" \
|
|
"$ALPINE_IMAGE" tar czf "/backup/${out}" -C /src .
|
|
done
|
|
|
|
# Retention: keep the newest $KEEP of each volume's tarballs (0 = keep all).
|
|
KEEP=${BACKUP_KEEP:-14}
|
|
if [ "$KEEP" -gt 0 ]; then
|
|
for key in $VOLUMES; do
|
|
# ls -t for mtime order; our filenames are safe (key + UTC timestamp).
|
|
# shellcheck disable=SC2012
|
|
ls -1t "$DEST_ABS/${key}-"*.tar.gz 2>/dev/null | tail -n +"$((KEEP + 1))" | while read -r old; do
|
|
echo "backup: prune $old"
|
|
rm -f "$old"
|
|
done
|
|
done
|
|
fi
|
|
|
|
echo "backup: done ($STAMP) — copy $DEST_ABS offsite."
|