#!/usr/bin/env bash # ===================================================================================== # Disaster recovery: bring the domain back from an online backup archive. # # ./scripts/restore.sh backups/.tar.bz2 [new-ip] # # HOW SAMBA RESTORE ACTUALLY WORKS (this surprises people) # `samba-tool domain backup restore` performs a *join*: it unpacks the database into a # target dir and then ADDS the target host as a NEW domain controller. Restoring under the # same server name that the archive was taken from fails with # "Entry CN=,OU=Domain Controllers,... already exists" # So: pick a NEW hostname (and an IP nothing else is using). The old DC name stays in DNS # until you clean it up; point clients at the new DC. # # Sequence: backup .env -> stop live DC -> move data dir aside (never deleted) -> # restore into fresh tree -> rewrite .env identity -> up -> verify # ===================================================================================== set -uo pipefail cd "$(dirname "$0")/.." [ -f .env ] || { echo "no .env in $(pwd) - restore expects the production env file" >&2; exit 2; } . "$(dirname "$0")/envfile.sh"; ad_env_load .env # parsed, never executed (see envfile.sh) BACKUP="${1:-}" NEW_HOST="${2:-${AD_RESTORE_AS:-}}" NEW_IP="${3:-${AD_RESTORE_IP:-$AD_IP}}" [ -n "$BACKUP" ] && [ -r "$BACKUP" ] || { echo "usage: $0 [new-ip]" >&2; exit 2; } [ -n "$NEW_HOST" ] || { echo "A Samba restore lands as a NEW domain controller. Give it a hostname, e.g. dc3" >&2; exit 2; } if [ "${NEW_HOST,,}" = "$(tar -f "$BACKUP" -O ./backup.txt 2>/dev/null | sed -n "s/.*using DC '\([^']*\)'.*/\1/p" | head -1 | cut -d. -f1 | tr 'A-Z' 'a-z')" ]; then echo "refusing: '$NEW_HOST' is the hostname the backup was taken from" >&2 exit 2 fi src_dc="$(tar -f "$BACKUP" -O ./backup.txt 2>/dev/null | sed -n "s/.*using DC '\([^']*\)'.*/\1/p" | head -1)" echo "archive: $(basename "$BACKUP")" echo "from DC: ${src_dc:-unknown}" echo "restore as: ${NEW_HOST}.${AD_DNS_DOMAIN} @ ${NEW_IP}" read -r -p "Type RESTORE to proceed: " confirm [ "$confirm" = "RESTORE" ] || { echo "aborted"; exit 1; } if [ -f "${BACKUP}.sha256" ] && ( cd "$(dirname "$BACKUP")" && sha256sum -c "$(basename "$BACKUP").sha256" ); then echo ">> archive checksum OK" else echo ">> no checksum (or mismatch) - continuing, you were warned" fi ts="$(date -u +%Y%m%dT%H%M%SZ)" cid="${AD_CONTAINER:-ad-dc}" echo ">> stopping live DC" docker compose stop ad-dc || true echo ">> preserving current data/config so nothing is destroyed" [ -f .env ] && cp -a .env ".env.pre-restore-${ts}" if [ -d data/samba ] && [ -n "$(ls -A data/samba 2>/dev/null || true)" ]; then mv data/samba "data/samba.pre-restore-${ts}" echo " data/samba -> data/samba.pre-restore-${ts}" fi mkdir -p data/samba echo ">> rewriting identity in .env (AD_HOSTNAME=${NEW_HOST}, AD_IP=${NEW_IP})" cp -a .env .env.tmp sed -i "s/^AD_HOSTNAME=.*/AD_HOSTNAME=${NEW_HOST}/; s/^AD_IP=.*/AD_IP=${NEW_IP}/" .env.tmp mv .env.tmp .env echo ">> restoring into the fresh tree (one-shot container; does not start the DC)" docker run --rm \ --name "ad-restore-${ts}" \ --cap-drop ALL \ --cap-add SYS_ADMIN --cap-add CHOWN --cap-add FOWNER --cap-add DAC_OVERRIDE \ --cap-add SETUID --cap-add SETGID --cap-add NET_BIND_SERVICE \ -v "$PWD/$(dirname "$BACKUP"):/restore:ro" \ -v "$PWD/data/samba:/var/lib/samba" \ -v "$PWD/conf:/etc/samba" \ -v /etc/localtime:/etc/localtime:ro \ --env-file .env \ -e "AD_RESTORE_FROM=/restore/$(basename "$BACKUP")" \ -e AD_RESTORE_CONFIRM=yes \ "${AD_RESTORE_IMAGE:-local/samba-ad-dc:4.22-debian13}" || { echo "!! restore failed - roll back with: cp .env.pre-restore-${ts} .env" >&2 exit 1 } echo ">> starting the restored DC" docker compose up -d ad-dc echo ">> waiting for health (max 200s)" h=none for _ in $(seq 1 50); do h="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$cid" 2>/dev/null || echo none)" [ "$h" = healthy ] && break sleep 4 done echo " health: $h" AD_CONTAINER="$cid" ./scripts/verify.sh || { echo "verification failed. Roll back:" echo " docker compose down" echo " cp .env.pre-restore-${ts} .env" echo " rm -rf data/samba && mv data/samba.pre-restore-${ts} data/samba" exit 1 } cat < -U administrator docker exec -it $cid ldbdel -H /var/lib/samba/private/sam.ldb "" # careful 3. Take a fresh backup immediately: make backup EOF