#!/usr/bin/env bash # # Install Dezky host backups: Restic + a dedicated backup SSH key/config for the # Hetzner Storage Box(es), the env file, the backup/restore scripts, and the # nightly systemd timer. Idempotent. # # sudo ./install.sh # # Storage Box uses SSH/SFTP on PORT 23 with key auth. After this runs, you must # upload the printed public key to BOTH Storage Boxes, then re-run to init the # repos (the box must trust the key before `restic init` can connect). set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' info() { echo -e "${BLUE}[INFO]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HOST_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" CONFIG_FILE="${CONFIG_FILE:-$HOST_DIR/config.env}" BACKUP_HOME="/opt/dezky-backup" SSH_DIR="$BACKUP_HOME/.ssh" KEY="$SSH_DIR/id_ed25519" if [[ $EUID -ne 0 ]]; then error "Run as root."; exit 1; fi if [[ ! -f "$CONFIG_FILE" ]]; then error "Missing $CONFIG_FILE"; exit 1; fi # shellcheck disable=SC1090 source "$CONFIG_FILE" : "${RESTIC_PASSWORD:?RESTIC_PASSWORD required (and STORE IT OFFLINE — losing it loses the backups)}" : "${BACKUP_PRIMARY_REPO:?BACKUP_PRIMARY_REPO required}" : "${BACKUP_PATHS:?BACKUP_PATHS required}" : "${BACKUP_RETENTION:=--keep-daily 7 --keep-weekly 4 --keep-monthly 6}" # ── 1) Packages ──────────────────────────────────────────────────────────── info "Installing restic + openssh client..." export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq restic curl openssh-client >/dev/null ok "restic $(restic version | awk '{print $2}') installed." # ── 2) Backup home + SSH key/config ──────────────────────────────────────── info "Setting up $BACKUP_HOME ..." install -d -m 0700 "$BACKUP_HOME" "$SSH_DIR" if [[ ! -f "$KEY" ]]; then ssh-keygen -t ed25519 -N "" -C "dezky-backup@node1" -f "$KEY" >/dev/null ok "Generated backup SSH key." fi # restic runs as root and its ssh subprocess resolves '~' from the passwd db # (NOT $HOME), so it reads /root/.ssh/config — the Storage Box block must live # there. StrictHostKeyChecking=no is safe: restic encrypts every byte before # upload, so the SFTP transport only moves opaque blobs. One wildcard block # covers BOTH Storage Boxes (same domain, port 23, key). install -d -m 0700 /root/.ssh if ! grep -q "your-storagebox.de" /root/.ssh/config 2>/dev/null; then cat >> /root/.ssh/config < "$BACKUP_HOME/restic.env" </dev/null 2>&1; then ok "$label repo already initialized." elif restic -r "$repo" init >/dev/null 2>&1; then ok "$label repo initialized." else warn "$label repo not reachable/authorized yet — upload the key, then re-run." fi } init_repo "$BACKUP_PRIMARY_REPO" "Primary" init_repo "${BACKUP_DR_REPO:-}" "DR" echo "" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ Backup install complete ║" echo "╚══════════════════════════════════════════════════════════════╝" warn "Upload this PUBLIC key to BOTH Storage Boxes, then re-run install.sh:" echo "" cat "$KEY.pub" echo "" info " ssh-copy-id -p 23 -i $KEY.pub @.your-storagebox.de" info " ssh-copy-id -p 23 -i $KEY.pub @.your-storagebox.de" info "Then test: sudo $BACKUP_HOME/backup.sh (or wait for 03:20 UTC)" info "Drill restore: sudo $BACKUP_HOME/restore.sh restore latest /tmp/restore-test" warn "STORE RESTIC_PASSWORD OFFLINE. Without it, the encrypted backups are unrecoverable."