Files
Scooby Husky 7990f1fa47 Add VPS warm-standby/backup site (Phase 0-1b)
Foundation for a DR/backup path using an always-on VPS as a second
ArgoCD-managed cluster, plus DB/backup standardization work that fell
out of it:

- vps-standby ArgoCD cluster destination + AppProject, MinIO backup
  receiver, VPS bootstrap script (k3s, Netbird, cert-manager)
- Dual-site DNS failover watcher + home-IP DDNS CronJob, Cloudflare
  token moved out of git into Vault+ExternalSecret
- Nextcloud migrated from ad-hoc MariaDB to CNPG + redis-operator
  (matches n8n/Authentik/GitLab's backup-native pattern)
- Authentik's CNPG manifests moved into the actual ArgoCD-synced
  manifests/ path (were present but never wired into the sync path)
- Vault raft-snapshot CronJob, CNPG barmanObjectStore backups
  (Authentik/n8n/Nextcloud), Nextcloud file-PVC restic sync - all
  targeting the new VPS MinIO receiver

See VPS Warm-Standby plan doc for full design rationale.
2026-08-17 14:59:26 -05:00

170 lines
6.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# vps-bootstrap.sh - Phase 0: turn a bare VPS into the vps-standby ArgoCD destination
#
# Run this ON THE VPS itself (as root, or via sudo), not against the home cluster.
# Installs k3s (single node), joins the existing self-hosted Netbird mesh, and
# installs cert-manager with the same Cloudflare DNS-01 ClusterIssuer pattern used
# at home — so TLS issuance works identically regardless of which site is "live"
# (DNS-01 only needs DNS control, not public HTTP reachability).
#
# This script does NOT register the cluster with ArgoCD — that's a one-time manual
# step run from your workstation/home cluster once this script prints the kubeconfig
# (ArgoCD can't reach the VPS until it exists, and shouldn't hold cluster-admin creds
# for a box it doesn't manage yet).
#
# Usage:
# sudo ./scripts/vps-bootstrap.sh <netbird-setup-key> <cloudflare-dns-edit-token>
#
# Prerequisites:
# - A Netbird setup key (Netbird dashboard → Settings → Setup Keys → create
# a reusable, non-ephemeral key)
# - A Cloudflare API token scoped to Zone:DNS:Edit for kube.huskypup.net only
# (create a NEW token for this — do not reuse the one from
# infrastructure/cert-manager/manifests/secret-cf-token.yaml, that one is
# being rotated/retired; see Phase 0.5)
# - Ubuntu/Debian VPS with a public IP, run as root
set -euo pipefail
NETBIRD_SETUP_KEY="${1:?Usage: $0 <netbird-setup-key> <cloudflare-dns-edit-token>}"
CLOUDFLARE_TOKEN="${2:?Usage: $0 <netbird-setup-key> <cloudflare-dns-edit-token>}"
NETBIRD_MGMT_URL="https://netbird.kube.huskypup.net"
LETSENCRYPT_EMAIL="garrettstone499@gmail.com"
DNS_ZONE="kube.huskypup.net"
CERT_MANAGER_VERSION="v1.13.2" # matches infrastructure/cert-manager chart version at home
if [ "$(id -u)" -ne 0 ]; then
echo "ERROR: run as root (sudo $0 ...)" >&2
exit 1
fi
echo "================================================="
echo "VPS Standby Bootstrap - Phase 0"
echo "================================================="
echo ""
# --- k3s -----------------------------------------------------------------
if command -v k3s >/dev/null 2>&1; then
echo "✅ k3s already installed, skipping install"
else
echo "Installing k3s (single node)..."
# Keep the built-in Traefik ingress controller — this is a lean standby box,
# not a mirror of home's Istio/Envoy-Gateway mesh. servicelb is fine too
# since this is a single node with a real public IP.
curl -sfL https://get.k3s.io | sh -s - \
--write-kubeconfig-mode 644 \
--disable metrics-server
echo "✅ k3s installed"
fi
echo "Waiting for k3s node to be Ready..."
for i in $(seq 1 30); do
if k3s kubectl get nodes 2>/dev/null | grep -q " Ready"; then
echo "✅ node is Ready"
break
fi
sleep 5
done
k3s kubectl get nodes
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
# --- Netbird ---------------------------------------------------------------
if command -v netbird >/dev/null 2>&1 && netbird status 2>/dev/null | grep -q "Management: Connected"; then
echo "✅ Netbird already connected, skipping"
else
echo "Installing Netbird client..."
curl -fsSL https://pkgs.netbird.io/install.sh | sh
echo "Joining Netbird mesh (${NETBIRD_MGMT_URL})..."
netbird up --management-url "${NETBIRD_MGMT_URL}" --setup-key "${NETBIRD_SETUP_KEY}"
echo "✅ Netbird joined"
fi
echo "Note: this Netbird session is used opportunistically for backup/sync traffic"
echo "while home is up. It is NOT the path used to reach this VPS when home is down —"
echo "that's direct SSH on this box's public IP. See plan doc, decision #2."
# --- cert-manager ------------------------------------------------------------
echo ""
echo "Installing cert-manager ${CERT_MANAGER_VERSION}..."
if ! command -v helm >/dev/null 2>&1; then
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
fi
helm repo add jetstack https://charts.jetstack.io 2>/dev/null || true
helm repo update jetstack
if helm -n cert-manager status cert-manager >/dev/null 2>&1; then
echo "✅ cert-manager already installed"
else
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version "${CERT_MANAGER_VERSION}" \
--set installCRDs=true \
--wait --timeout 300s
echo "✅ cert-manager installed"
fi
echo "Waiting for cert-manager webhook to be ready..."
k3s kubectl -n cert-manager rollout status deployment/cert-manager-webhook --timeout=120s
# --- Cloudflare DNS-01 ClusterIssuer (same pattern as home) -----------------
echo ""
echo "Applying Cloudflare token Secret + ClusterIssuer..."
cat <<EOF | k3s kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-token-secret
namespace: cert-manager
type: Opaque
stringData:
cloudflare-token: "${CLOUDFLARE_TOKEN}"
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ${LETSENCRYPT_EMAIL}
privateKeySecretRef:
name: letsencrypt-production
solvers:
- dns01:
cloudflare:
email: ${LETSENCRYPT_EMAIL}
apiTokenSecretRef:
name: cloudflare-token-secret
key: cloudflare-token
selector:
dnsZones:
- "${DNS_ZONE}"
EOF
echo "✅ ClusterIssuer letsencrypt-production ready"
# --- Output for ArgoCD registration -----------------------------------------
echo ""
echo "================================================="
echo "✅ VPS foundation bootstrap complete"
echo "================================================="
echo ""
echo "Next step (run from your workstation, NOT this VPS):"
echo ""
echo " 1. Copy this VPS's kubeconfig to your workstation, e.g.:"
echo " scp root@<vps-ip>:/etc/rancher/k3s/k3s.yaml ~/vps-standby-kubeconfig.yaml"
echo " Then edit the 'server:' line inside it to use this VPS's public IP"
echo " instead of 127.0.0.1."
echo ""
echo " 2. Register it with ArgoCD:"
echo " KUBECONFIG=~/vps-standby-kubeconfig.yaml argocd cluster add default --name vps-standby"
echo ""
echo " 3. Confirm registration:"
echo " argocd cluster list"
echo ""
echo "This box is otherwise reachable via:"
echo " - Netbird (while home's self-hosted mesh is up)"
echo " - Direct SSH on its public IP (always, break-glass path)"