96 lines
3.5 KiB
Bash
96 lines
3.5 KiB
Bash
#!/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
|