Episode 7: nightly restic backup to S3 with consistent dumps, heartbeat, and a restore script for a fresh box
Claude-Session: https://claude.ai/code/session_01Y1BTehJjJ4Chr6YscVRXmn
This commit is contained in:
parent
80d5b00b67
commit
cf374770c4
7 changed files with 444 additions and 0 deletions
96
backup/restore.sh
Normal file
96
backup/restore.sh
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env bash
|
||||
# ~$ selfhostedops - bring the whole stack back on an empty Debian box (episode 7)
|
||||
#
|
||||
# Usage on a fresh Debian 12/13 server, as root:
|
||||
# mkdir -p /opt/selfhostedops-stack/backup
|
||||
# <put your .restic-env into /opt/selfhostedops-stack/backup/> (from the vault)
|
||||
# curl -fsSLO https://<your-git>/selfhostedops-stack/raw/branch/main/backup/restore.sh
|
||||
# bash restore.sh install # docker, compose plugin, restic
|
||||
# bash restore.sh restore # docker volumes + restic restore latest --target /
|
||||
# bash restore.sh up # docker compose up -d for the stack and NetBird
|
||||
# bash restore.sh all # the three in a row
|
||||
#
|
||||
# Each phase prints its own wall-clock time. Nothing here touches DNS: point
|
||||
# the records at the new box when the services answer (test first with
|
||||
# curl --resolve wiki.example.com:443:<new ip> https://wiki.example.com/).
|
||||
set -Eeuo pipefail
|
||||
|
||||
STACK_DIR=${STACK_DIR:-/opt/selfhostedops-stack}
|
||||
NETBIRD_DIR=${NETBIRD_DIR:-/opt/netbird}
|
||||
ENV_FILE="$STACK_DIR/backup/.restic-env"
|
||||
SNAPSHOT=${SNAPSHOT:-latest}
|
||||
|
||||
log() { printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
||||
phase_start() { PHASE_T0=$(date +%s%N); log "=== phase $1 ==="; }
|
||||
phase_end() {
|
||||
local ms=$(( ($(date +%s%N) - PHASE_T0) / 1000000 ))
|
||||
log "=== phase $1 done in $((ms / 1000)).$(printf '%03d' $((ms % 1000)))s ==="
|
||||
}
|
||||
|
||||
load_env() {
|
||||
[ -f "$ENV_FILE" ] || { echo "missing $ENV_FILE (copy it from your vault first)"; exit 1; }
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
}
|
||||
|
||||
phase_install() {
|
||||
phase_start install
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -q
|
||||
apt-get install -y -q ca-certificates curl gnupg restic
|
||||
# (coreutils install, not a function of this script)
|
||||
/usr/bin/install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $VERSION_CODENAME stable" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
apt-get update -q
|
||||
apt-get install -y -q docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
systemctl enable --now docker
|
||||
docker --version; docker compose version; restic version
|
||||
phase_end install
|
||||
}
|
||||
|
||||
phase_restore() {
|
||||
load_env
|
||||
phase_start restore
|
||||
log "snapshots in the repository:"
|
||||
restic snapshots --compact
|
||||
# Named volumes must exist in docker's registry before the files land in
|
||||
# /var/lib/docker/volumes/<name>/_data, otherwise compose creates fresh
|
||||
# empty ones on top. The names come straight out of the snapshot.
|
||||
log "creating docker volumes"
|
||||
restic ls "$SNAPSHOT" /var/lib/docker/volumes \
|
||||
| grep '^/var/lib/docker/volumes/[^/]*_' \
|
||||
| while read -r dir; do docker volume create "$(basename "$dir")"; done
|
||||
log "restic restore $SNAPSHOT --target /"
|
||||
restic restore "$SNAPSHOT" --target /
|
||||
log "restored:"
|
||||
du -sh "$STACK_DIR" "$NETBIRD_DIR" /var/lib/docker/volumes/*_*/_data
|
||||
phase_end restore
|
||||
}
|
||||
|
||||
phase_up() {
|
||||
phase_start up
|
||||
cd "$STACK_DIR"
|
||||
docker compose up -d
|
||||
# NetBird joins the stack's proxy network, so it comes second.
|
||||
if [ -d "$NETBIRD_DIR" ]; then
|
||||
cd "$NETBIRD_DIR"
|
||||
docker compose up -d
|
||||
fi
|
||||
docker ps --format 'table {{.Names}}\t{{.Status}}'
|
||||
phase_end up
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
install) phase_install ;;
|
||||
restore) phase_restore ;;
|
||||
up) phase_up ;;
|
||||
all) phase_install; phase_restore; phase_up ;;
|
||||
*) echo "usage: $0 install|restore|up|all"; exit 1 ;;
|
||||
esac
|
||||
Loading…
Add table
Add a link
Reference in a new issue