74 lines
3.3 KiB
Bash
Executable file
74 lines
3.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# =====================================================================================
|
|
# Online full-domain backup (AD database + SYSVOL + secrets) via `samba-tool domain backup`.
|
|
# Copied out of the container into ./backups/<timestamp>.tgz and checksummed.
|
|
#
|
|
# ./scripts/backup.sh # uses $AD_CONTAINER / .env
|
|
# KEEP=14 ./scripts/backup.sh # prune to the newest 14 backups
|
|
#
|
|
# A backup you have not restored is a rumour. Restore drill: ./scripts/restore.sh
|
|
# =====================================================================================
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
. "$(dirname "$0")/envfile.sh"; ad_env_load .env # parsed, never executed (see envfile.sh)
|
|
|
|
CID="${AD_CONTAINER:-ad-dc}"
|
|
REALM="${AD_REALM:?}"
|
|
HOST="${AD_HOSTNAME:-dc1}"
|
|
ZONE="${AD_DNS_DOMAIN:?}"
|
|
FQDN="${HOST}.${ZONE}"
|
|
CC=/tmp/backup_ccache
|
|
DEST="${AD_BACKUP_DIR:-backups}"
|
|
KEEP="${KEEP:-14}"
|
|
|
|
PW="${AD_ADMIN_PASSWORD:-}"
|
|
[ -z "$PW" ] && [ -r secrets/ad_admin.password ] && PW="$(tr -d '\r\n' < secrets/ad_admin.password)"
|
|
[ -n "$PW" ] || { echo "no administrator password available" >&2; exit 2; }
|
|
|
|
docker inspect -f '{{.State.Status}}' "$CID" | grep -q running || { echo "$CID is not running" >&2; exit 1; }
|
|
mkdir -p "$DEST"
|
|
# Preflight: a backup that dies after 10 minutes of work is worse than one that never starts.
|
|
# Docker creates bind-mount source dirs as root, and `docker cp` extracts to the host as the
|
|
# *invoking* user - so a root-owned backups/ fails with a cryptic "openat: permission denied".
|
|
if [ ! -w "$DEST" ]; then
|
|
echo "!! backup dir '$DEST' is not writable by $(whoami) (owned $(stat -c '%U:%G %a' "$DEST"))" >&2
|
|
echo " typically created root-owned by Docker for a bind mount. Fix with:" >&2
|
|
echo " sudo chown $(whoami) \"$DEST\"" >&2
|
|
exit 1
|
|
fi
|
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
|
|
echo ">> backing up ${FQDN} (online, consistent snapshot) -> ${DEST}/"
|
|
printf '%s\n' "$PW" | docker exec -i -e KRB5CCNAME="FILE:${CC}" -e AD_FQDN="$FQDN" "$CID" \
|
|
bash -euo pipefail -c '
|
|
kinit "${AD_ADMIN_USER:-administrator}@${AD_REALM}"
|
|
samba-tool domain backup online \
|
|
--server="$AD_FQDN" \
|
|
--targetdir=/var/backups/ad-dc \
|
|
--use-krb5-ccache="$KRB5CCNAME"
|
|
'
|
|
|
|
# Samba names the archive samba-backup-<domain>-<ts>.tar.bz2 (older builds: .tgz)
|
|
newest="$(docker exec "$CID" bash -c 'ls -1t /var/backups/ad-dc/*.tar.bz2 /var/backups/ad-dc/*.tgz 2>/dev/null | head -1')"
|
|
[ -n "$newest" ] || { echo "backup produced no archive" >&2; exit 1; }
|
|
|
|
docker cp "${CID}:${newest}" "${DEST}/${ts}-$(basename "$newest")"
|
|
out="${DEST}/${ts}-$(basename "$newest")"
|
|
( cd "$DEST" && sha256sum "$(basename "$out")" > "$(basename "$out").sha256" )
|
|
|
|
# Don't leave archives inside the container layer - they survive restarts and silently grow it.
|
|
docker exec "$CID" rm -f "$newest" 2>/dev/null || true
|
|
|
|
size="$(du -h "$out" | cut -f1)"
|
|
echo ">> wrote $out ($size)"
|
|
echo " checksum: $(cat "$out.sha256")"
|
|
|
|
# rotation
|
|
mapfile -t existing < <(ls -1t "$DEST"/*.tar.bz2 "$DEST"/*.tgz 2>/dev/null || true)
|
|
if [ "${#existing[@]}" -gt "$KEEP" ]; then
|
|
for old in "${existing[@]:$KEEP}"; do
|
|
echo " pruning $old"
|
|
rm -f "$old" "$old.sha256"
|
|
done
|
|
fi
|
|
echo ">> done. Copy $out off this host (backup of a backup) and keep the AD_DB key safe."
|