#!/usr/bin/env bash
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║  BackupBloc — Disaster Recovery (DR) Node Installer                          ║
# ║                                                                              ║
# ║  Turns a blank server into a BackupBloc DR node: installs restic, rclone,    ║
# ║  and a restic rest-server (append-only), authorises the manager's SSH key   ║
# ║  so it can push append-only repo mirrors, then self-registers with the      ║
# ║  panel so it shows up in the SAME manager UI — no second login.             ║
# ║                                                                              ║
# ║  Usage (from the panel's "Add DR server → installer" command):              ║
# ║    bash <(curl -fsSL https://backupbloc.com/downloads/agent/install-dr.sh) \║
# ║         --manager https://backups.backupbloc.com --token <enroll-token>     ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
set -euo pipefail

# ── Args ──────────────────────────────────────────────────────────────────────
MANAGER_URL=""
ENROLL_TOKEN=""
REPO_BASE="/var/lib/restic-repos"
REST_PORT="8000"
SSH_PORT="22"
ADVERTISE_IP=""
WITH_PANEL=1          # install the standby recovery panel by default
PANEL_UI_PORT="443"
CDN_BASE="https://backupbloc.com/downloads"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --manager)    MANAGER_URL="${2:-}"; shift 2 ;;
    --token)      ENROLL_TOKEN="${2:-}"; shift 2 ;;
    --repo-base)  REPO_BASE="${2:-}"; shift 2 ;;
    --rest-port)  REST_PORT="${2:-}"; shift 2 ;;
    --ssh-port)   SSH_PORT="${2:-}"; shift 2 ;;
    --ip)         ADVERTISE_IP="${2:-}"; shift 2 ;;
    --no-panel)   WITH_PANEL=0; shift ;;
    --panel-port) PANEL_UI_PORT="${2:-}"; shift 2 ;;
    *) echo "Unknown argument: $1" >&2; exit 1 ;;
  esac
done

GREEN=$'\e[32m'; CYAN=$'\e[36m'; YELLOW=$'\e[33m'; RED=$'\e[31m'; BOLD=$'\e[1m'; NC=$'\e[0m'
step() { echo -e "\n${BOLD}${CYAN}==>${NC} ${BOLD}$*${NC}"; }
ok()   { echo -e "  ${GREEN}✔${NC} $*"; }
warn() { echo -e "  ${YELLOW}⚠${NC} $*"; }
err()  { echo -e "  ${RED}ERROR:${NC} $*" >&2; exit 1; }

[[ $EUID -eq 0 ]] || err "Please run as root (sudo)."
[[ -n "$MANAGER_URL" ]] || err "Missing --manager <url>. Get the full command from the panel: DR → Add server."
[[ -n "$ENROLL_TOKEN" ]] || err "Missing --token <enrollment-token>. Get the full command from the panel."
MANAGER_URL="${MANAGER_URL%/}"   # strip trailing slash

echo -e "${BOLD}BackupBloc DR Node Installer${NC}"
echo -e "  Manager:   ${CYAN}${MANAGER_URL}${NC}"
echo -e "  Repo base: ${CYAN}${REPO_BASE}${NC}"
echo -e "  REST port: ${CYAN}${REST_PORT}${NC}"

# ── Detect package manager ────────────────────────────────────────────────────
if   command -v apt-get &>/dev/null; then PKG=apt
elif command -v dnf     &>/dev/null; then PKG=dnf
elif command -v yum     &>/dev/null; then PKG=yum
else err "No supported package manager (apt/dnf/yum) found."; fi

install_deps() {
  step "Installing dependencies (restic, rclone, curl, openssl)"
  case "$PKG" in
    apt) export DEBIAN_FRONTEND=noninteractive
         apt-get update -qq
         apt-get install -y -qq restic rclone curl ca-certificates openssl coreutils tar gzip >/dev/null ;;
    dnf|yum) $PKG install -y -q epel-release >/dev/null 2>&1 || true
         $PKG install -y -q restic rclone curl ca-certificates openssl coreutils tar gzip >/dev/null 2>&1 || true ;;
  esac
  # Fallbacks if distro packages are missing/old
  if ! command -v restic &>/dev/null; then
    warn "restic not in repos — installing official binary"
    ARCH=$(uname -m); case "$ARCH" in x86_64) RA=amd64;; aarch64|arm64) RA=arm64;; *) err "Unsupported arch $ARCH";; esac
    RV=$(curl -fsSL https://api.github.com/repos/restic/restic/releases/latest | grep -oP '"tag_name":\s*"v\K[^"]+' | head -1)
    curl -fsSL "https://github.com/restic/restic/releases/download/v${RV}/restic_${RV}_linux_${RA}.bz2" -o /tmp/restic.bz2
    bunzip2 -f /tmp/restic.bz2 && install -m755 /tmp/restic /usr/local/bin/restic
  fi
  if ! command -v rclone &>/dev/null; then
    warn "rclone not in repos — installing official binary"
    curl -fsSL https://rclone.org/install.sh | bash >/dev/null
  fi
  command -v restic &>/dev/null || err "restic install failed"
  command -v rclone &>/dev/null || err "rclone install failed"
  ok "restic $(restic version 2>/dev/null | awk '{print $2}'), rclone $(rclone version 2>/dev/null | head -1 | awk '{print $2}')"
}

# ── rest-server (append-only) ─────────────────────────────────────────────────
install_rest_server() {
  step "Installing restic rest-server (append-only)"
  id -u restic-server &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin restic-server
  mkdir -p "$REPO_BASE" /etc/restic-server /var/log/restic-server
  chown -R restic-server:restic-server "$REPO_BASE" /var/log/restic-server

  if ! command -v rest-server &>/dev/null; then
    ARCH=$(uname -m); case "$ARCH" in x86_64) RSA=amd64;; aarch64|arm64) RSA=arm64;; *) err "Unsupported arch $ARCH";; esac
    RSV=$(curl -fsSL https://api.github.com/repos/restic/rest-server/releases/latest | grep -oP '"tag_name":\s*"v\K[^"]+' | head -1)
    curl -fsSL "https://github.com/restic/rest-server/releases/download/v${RSV}/rest-server_${RSV}_linux_${RSA}.tar.gz" -o /tmp/rs.tgz
    tar -xzf /tmp/rs.tgz -C /tmp
    install -m755 "/tmp/rest-server_${RSV}_linux_${RSA}/rest-server" /usr/local/bin/rest-server
  fi

  # Generate REST transport credentials the manager will use to restore from us
  REST_USER="bbdr"
  REST_PASS=$(openssl rand -base64 18 | tr -d '/+=' | cut -c1-22)
  if command -v python3 &>/dev/null && python3 -c 'import bcrypt' 2>/dev/null; then
    HASH=$(python3 -c "import bcrypt,sys; print(bcrypt.hashpw(sys.argv[1].encode(),bcrypt.gensalt()).decode())" "$REST_PASS")
  elif command -v htpasswd &>/dev/null; then
    HASH=$(htpasswd -nbB "$REST_USER" "$REST_PASS" | cut -d: -f2)
  else
    HASH=$(openssl passwd -apr1 "$REST_PASS")
  fi
  echo "${REST_USER}:${HASH}" > /etc/restic-server/users
  chmod 640 /etc/restic-server/users; chown root:restic-server /etc/restic-server/users

  cat > /etc/systemd/system/restic-rest-server.service <<EOF
[Unit]
Description=Restic REST Server (BackupBloc DR)
Documentation=https://github.com/restic/rest-server
After=network.target

[Service]
Type=simple
User=restic-server
Group=restic-server
ExecStart=/usr/local/bin/rest-server \\
  --path ${REPO_BASE} \\
  --listen 0.0.0.0:${REST_PORT} \\
  --htpasswd-file /etc/restic-server/users \\
  --log /var/log/restic-server/rest-server.log \\
  --max-size 0 \\
  --append-only
Restart=on-failure
RestartSec=5
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=${REPO_BASE} /var/log/restic-server

[Install]
WantedBy=multi-user.target
EOF
  systemctl daemon-reload
  systemctl enable --now restic-rest-server >/dev/null 2>&1 || systemctl restart restic-rest-server
  ok "rest-server running on :${REST_PORT} (append-only, user '${REST_USER}')"
}

# ── Authorise the manager's SSH key (so it can push mirrors) ───────────────────
authorize_manager_key() {
  step "Authorising manager SSH key"
  PUB=$(curl -fsSL "${MANAGER_URL}/api/trigger-pubkey" 2>/dev/null \
        | python3 -c 'import sys,json; print(json.load(sys.stdin).get("pubkey",""))' 2>/dev/null || echo "")
  [[ -n "$PUB" ]] || err "Could not fetch manager pubkey from ${MANAGER_URL}/api/trigger-pubkey"
  mkdir -p /root/.ssh && chmod 700 /root/.ssh
  touch /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys
  if ! grep -qF "$PUB" /root/.ssh/authorized_keys; then
    echo "$PUB" >> /root/.ssh/authorized_keys
    ok "Manager key added to /root/.ssh/authorized_keys"
  else
    ok "Manager key already authorised"
  fi
}

# ── Self-register with the panel ──────────────────────────────────────────────
register_with_panel() {
  step "Registering with the manager panel"
  mkdir -p /etc/backupbloc-dr
  echo "1.0.0" > /etc/backupbloc-dr/version
  [[ -n "$ADVERTISE_IP" ]] || ADVERTISE_IP=$(curl -fsSL https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}')
  FREE_GB=$(df -BG --output=avail "$REPO_BASE" 2>/dev/null | tail -1 | tr -dc 0-9)

  PAYLOAD=$(python3 -c '
import json,sys
print(json.dumps({
  "token":     sys.argv[1],
  "ip":        sys.argv[2],
  "ssh_port":  int(sys.argv[3]),
  "repo_base": sys.argv[4],
  "rest_port": int(sys.argv[5]),
  "version":   "1.0.0",
  "rclone":    True,
  "restic":    True,
  "rest_server": "active",
  "free_gb":   int(sys.argv[6] or 0),
  "rest_user": sys.argv[7],
  "rest_pass": sys.argv[8],
}))' "$ENROLL_TOKEN" "$ADVERTISE_IP" "$SSH_PORT" "$REPO_BASE" "$REST_PORT" "${FREE_GB:-0}" "$REST_USER" "$REST_PASS")

  RESP=$(curl -sk -X POST "${MANAGER_URL}/api/dr/register" \
         -H 'Content-Type: application/json' --data "$PAYLOAD" 2>/dev/null || echo '{}')
  if echo "$RESP" | grep -q '"ok": *true\|"ok":true'; then
    ok "Registered with panel — this DR node now appears in the manager UI"
  else
    warn "Registration response: $RESP"
    warn "You can still add this server manually in the panel (DR → Add server) using IP ${ADVERTISE_IP}."
  fi
}

# ── Firewall (best effort) ────────────────────────────────────────────────────
open_firewall() {
  if command -v ufw &>/dev/null; then
    ufw allow "${REST_PORT}/tcp" >/dev/null 2>&1 || true
    ufw allow "${SSH_PORT}/tcp"  >/dev/null 2>&1 || true
  elif command -v firewall-cmd &>/dev/null; then
    firewall-cmd --permanent --add-port="${REST_PORT}/tcp" >/dev/null 2>&1 || true
    firewall-cmd --reload >/dev/null 2>&1 || true
  fi
}

# ── Standby recovery panel ────────────────────────────────────────────────────
# Installs the BackupBloc panel (API + admin UI + nginx) on this DR node in
# STANDBY mode: read/restore-only, no replication, no upstream-license lockout.
# When the primary data centre is down, the operator browses to this node and
# restores from the local mirror — using the recovery bundle (repo passwords +
# config) that the manager replicates here alongside the backup data.
install_standby_panel() {
  step "Installing standby recovery panel"
  ADVERTISE_IP="${ADVERTISE_IP:-$(curl -fsSL https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}')}"

  # Python + deps
  case "$PKG" in
    apt) apt-get install -y -qq python3 python3-venv python3-pip nginx openssl >/dev/null 2>&1 ;;
    dnf|yum) $PKG install -y -q python3 python3-pip nginx openssl >/dev/null 2>&1 ;;
  esac

  # Fetch the server package to get the panel code (api + admin UI)
  TMP=$(mktemp -d)
  if ! curl -fsSL "${CDN_BASE}/backupbloc-server.tar.gz" -o "$TMP/srv.tgz"; then
    warn "Could not download server package — skipping standby panel."; rm -rf "$TMP"; return 0
  fi
  tar -xzf "$TMP/srv.tgz" -C "$TMP"
  SRC="$TMP/backupbloc-server"
  mkdir -p /opt/restic-manager/api /opt/restic-manager/admin
  cp "$SRC"/server/backup-api.py "$SRC"/server/control_client.py "$SRC"/server/updater.py /opt/restic-manager/api/ 2>/dev/null || true
  cp "$SRC"/server/update-pubkey.pem /opt/restic-manager/api/ 2>/dev/null || true
  cp -r "$SRC"/admin/. /opt/restic-manager/admin/ 2>/dev/null || true

  # venv
  python3 -m venv /opt/restic-manager/venv 2>/dev/null || true
  /opt/restic-manager/venv/bin/pip install --quiet flask flask-cors flask-limiter psutil requests pyjwt python-dotenv cryptography bcrypt >/dev/null 2>&1 || \
    warn "Some Python deps may have failed to install — check /opt/restic-manager/venv"

  # REST creds for reading the LOCAL rest-server (already running on this node)
  ST_REST_USER="bbdr"
  ST_REST_PASS="$(openssl rand -base64 18 | tr -dc 'A-Za-z0-9' | cut -c1-22)"

  mkdir -p /etc/restic-manager
  cat > /etc/restic-manager/config.env <<EOF
# BackupBloc STANDBY recovery panel — generated by install-dr.sh
STANDBY_MODE=1
SERVER_HOST=${ADVERTISE_IP}
REPO_BASE=${REPO_BASE}
REST_PORT=${REST_PORT}
REST_SCHEME=http
REST_USER=${ST_REST_USER}
REST_PASS=${ST_REST_PASS}
API_PORT=5000
EOF
  chmod 600 /etc/restic-manager/config.env

  # Seed the recovery bundle (passwords + users + config) into the panel's
  # config dir so it can decrypt + authenticate. Kept current by a timer below.
  if [[ -d /etc/backupbloc-recovery ]]; then
    cp -a /etc/backupbloc-recovery/. /etc/restic-manager/ 2>/dev/null || true
  else
    warn "No recovery bundle present yet (/etc/backupbloc-recovery). It will appear after the manager's next replication; the refresh timer will pick it up."
  fi
  id -u restic-server &>/dev/null && chown -R restic-server:restic-server /etc/restic-manager 2>/dev/null || true

  # Refresh timer: keep the panel's config current as the manager replicates the
  # bundle (new clients, rotated passwords).
  cat > /usr/local/bin/backupbloc-bundle-refresh <<'RFR'
#!/usr/bin/env bash
[[ -d /etc/backupbloc-recovery ]] || exit 0
cp -a /etc/backupbloc-recovery/. /etc/restic-manager/ 2>/dev/null || true
id -u restic-server &>/dev/null && chown -R restic-server:restic-server /etc/restic-manager 2>/dev/null || true
RFR
  chmod +x /usr/local/bin/backupbloc-bundle-refresh
  cat > /etc/systemd/system/backupbloc-bundle-refresh.service <<EOF
[Unit]
Description=BackupBloc standby recovery-bundle refresh
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backupbloc-bundle-refresh
EOF
  cat > /etc/systemd/system/backupbloc-bundle-refresh.timer <<EOF
[Unit]
Description=Refresh BackupBloc recovery bundle every 10 min
[Timer]
OnBootSec=2min
OnUnitActiveSec=10min
[Install]
WantedBy=timers.target
EOF

  # systemd service for the standby API
  cat > /etc/systemd/system/restic-api.service <<EOF
[Unit]
Description=BackupBloc Standby Recovery API
After=network.target restic-rest-server.service
[Service]
Type=simple
User=restic-server
Group=restic-server
WorkingDirectory=/opt/restic-manager/api
EnvironmentFile=/etc/restic-manager/config.env
ExecStart=/opt/restic-manager/venv/bin/python backup-api.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
  mkdir -p /var/log/restic-server /var/log/restic-manager/clients
  chown -R restic-server:restic-server /var/log/restic-server /var/log/restic-manager 2>/dev/null || true

  # Self-signed TLS for the recovery UI (no domain needed during a disaster)
  mkdir -p /etc/restic-server/tls
  if [[ ! -f /etc/restic-server/tls/standby.crt ]]; then
    openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
      -keyout /etc/restic-server/tls/standby.key \
      -out /etc/restic-server/tls/standby.crt \
      -subj "/CN=${ADVERTISE_IP}" >/dev/null 2>&1
  fi

  # nginx: serve admin UI + proxy /api to the standby API on :5000
  cat > /etc/nginx/conf.d/backupbloc-standby.conf <<EOF
server {
    listen ${PANEL_UI_PORT} ssl;
    server_name _;
    ssl_certificate     /etc/restic-server/tls/standby.crt;
    ssl_certificate_key /etc/restic-server/tls/standby.key;
    location / { root /opt/restic-manager/admin; index index.html; try_files \$uri \$uri/ /index.html; }
    location /api/ {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host \$host;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_read_timeout 300; proxy_send_timeout 300; client_max_body_size 0;
    }
}
EOF
  # Avoid clashing with a default nginx site on the same port
  rm -f /etc/nginx/sites-enabled/default 2>/dev/null || true

  systemctl daemon-reload
  systemctl enable --now backupbloc-bundle-refresh.timer >/dev/null 2>&1 || true
  systemctl enable --now restic-api.service >/dev/null 2>&1 || systemctl restart restic-api.service
  nginx -t >/dev/null 2>&1 && systemctl reload-or-restart nginx >/dev/null 2>&1 || warn "nginx config test failed — check /etc/nginx/conf.d/backupbloc-standby.conf"

  if command -v ufw &>/dev/null; then ufw allow "${PANEL_UI_PORT}/tcp" >/dev/null 2>&1 || true
  elif command -v firewall-cmd &>/dev/null; then firewall-cmd --permanent --add-port="${PANEL_UI_PORT}/tcp" >/dev/null 2>&1 || true; firewall-cmd --reload >/dev/null 2>&1 || true; fi

  rm -rf "$TMP"
  ok "Standby recovery panel installed — https://${ADVERTISE_IP}:${PANEL_UI_PORT}/"
}

install_deps
install_rest_server
authorize_manager_key
open_firewall
register_with_panel
[[ "$WITH_PANEL" == "1" ]] && install_standby_panel

echo
echo -e "${GREEN}${BOLD}✔ DR node ready.${NC}"
echo -e "  It will appear in your manager UI under ${BOLD}DR / Replication${NC}."
echo -e "  Next: assign which clients' repos replicate here, and pick an interval."
if [[ "$WITH_PANEL" == "1" ]]; then
  echo
  echo -e "  ${BOLD}Standby recovery panel:${NC} ${CYAN}https://${ADVERTISE_IP}:${PANEL_UI_PORT}/${NC}"
  echo -e "  Log in with your normal admin credentials. Use it only while the primary is down."
fi
