provisioning-catalog/components/backup_manager/cluster/install-backup_manager.sh

115 lines
6 KiB
Bash
Executable file

#!/usr/bin/env bash
# Install/update backup-manager (cluster mode + on-host runners).
#
# Operating model:
# 1. Receives bootstrap secrets via env (decrypted by the wrapper from
# SOPS+Age — only at deploy time; runtime keys live in vault per
# ADR-017).
# 2. Creates the bootstrap K8s Secret in $NAMESPACE.
# 3. Renders and applies Deployment + Service + ServiceMonitor +
# PrometheusRule for the daemon (cluster mode).
# 4. For every entry in system-backups.ncl: SSH to the selected hosts and
# install the prvng-backup binary + systemd timer or cron.d entry.
#
# Required env (typically populated by `provisioning component install …`):
# VAULT_BOOTSTRAP_TOKEN
# NATS_BOOTSTRAP_NKEY_SEED
# BACKUP_AGE_KEY bootstrap age key (NOT in vault — ADR-011)
# BACKUP_S3_PRIMARY_ACCESS_KEY
# BACKUP_S3_PRIMARY_SECRET_KEY
# BACKUP_B2_REPLICA_KEY_ID
# BACKUP_B2_REPLICA_APPLICATION_KEY
# NAMESPACE (default: backup-system)
# COMPONENT_PATH (path to instance .ncl rendered to JSON for Jinja2)
set -euo pipefail
NAMESPACE="${NAMESPACE:-backup-system}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMPLATES="$SCRIPT_DIR/templates"
# ── Pre-flight ──────────────────────────────────────────────────────────────
require_var() {
local name="$1"
if [ -z "${!name:-}" ]; then
echo "ERROR: required env var $name is not set" >&2
exit 1
fi
}
require_var VAULT_BOOTSTRAP_TOKEN
require_var NATS_BOOTSTRAP_NKEY_SEED
require_var BACKUP_AGE_KEY
require_var BACKUP_S3_PRIMARY_ACCESS_KEY
require_var BACKUP_S3_PRIMARY_SECRET_KEY
require_var BACKUP_B2_REPLICA_KEY_ID
require_var BACKUP_B2_REPLICA_APPLICATION_KEY
command -v kubectl >/dev/null || { echo "kubectl is required" >&2; exit 1; }
command -v jq >/dev/null || { echo "jq is required" >&2; exit 1; }
# ── Namespace ────────────────────────────────────────────────────────────────
kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -
# ── Bootstrap Secret ─────────────────────────────────────────────────────────
# Pattern mirrors zot S3 install (provisioning/extensions/components/zot/cluster/install-zot.sh):
# wrapper passes secrets as env vars; we materialise them as a K8s Secret.
# Daemon reads on first run and promotes runtime credentials into vault.
kubectl create secret generic backup-manager-bootstrap \
--namespace="$NAMESPACE" \
--from-literal=vault_token="$VAULT_BOOTSTRAP_TOKEN" \
--from-literal=nats_nkey_seed="$NATS_BOOTSTRAP_NKEY_SEED" \
--from-literal=age_key="$BACKUP_AGE_KEY" \
--from-literal=s3_primary_access_key="$BACKUP_S3_PRIMARY_ACCESS_KEY" \
--from-literal=s3_primary_secret_key="$BACKUP_S3_PRIMARY_SECRET_KEY" \
--from-literal=b2_replica_key_id="$BACKUP_B2_REPLICA_KEY_ID" \
--from-literal=b2_replica_application_key="$BACKUP_B2_REPLICA_APPLICATION_KEY" \
--dry-run=client -o yaml | kubectl apply -f -
# ── Deployment + Service ─────────────────────────────────────────────────────
# Templates are Jinja2; rendered by `nu render-template.nu` (provisioning helper)
# with the JSON dump of the component config bound as $component.
# Here we leave a render hook the wrapper invokes; if invoked directly without
# the wrapper, fall back to substituting NAMESPACE and DAEMON_IMAGE only.
RENDER_BIN="${RENDER_BIN:-render-template.nu}"
RENDER_INPUT="${COMPONENT_PATH:-}"
render_template() {
local template="$1"
if command -v "$RENDER_BIN" >/dev/null && [ -n "$RENDER_INPUT" ]; then
"$RENDER_BIN" "$template" --json "$RENDER_INPUT"
else
# Minimal substitution fallback — sufficient for development.
sed -e "s|{{ namespace }}|$NAMESPACE|g" \
-e "s|{{ daemon_image }}|${DAEMON_IMAGE:-ghcr.io/jesusperezlorenzo/backup-manager-runner:0.1.0}|g" \
-e "s|{{ daemon_http_port }}|${DAEMON_HTTP_PORT:-9099}|g" \
-e "s|{{ daemon_metrics_port }}|${DAEMON_METRICS_PORT:-9100}|g" \
"$template"
fi
}
for tpl in deployment.yaml.j2 service.yaml.j2 servicemonitor.yaml.j2; do
if [ -f "$TEMPLATES/$tpl" ]; then
render_template "$TEMPLATES/$tpl" | kubectl apply -f -
fi
done
# ── PrometheusRule (alerts from prometheus.ncl) ─────────────────────────────
# The alert rules are declared in the prometheus component; install-prometheus.sh
# is responsible for rendering them. backup-manager only emits the metrics; the
# rules live with the observability stack.
# ── On-host runners ──────────────────────────────────────────────────────────
# For each SystemBackupDef in the workspace, install /usr/local/bin/prvng-backup
# and a systemd timer or cron.d entry on the selected hosts. This step is
# delegated to the provisioning host runner (Nushell) when available; here we
# emit the list to stdout so the caller can act.
SYSTEM_BACKUPS_PATH="${SYSTEM_BACKUPS_PATH:-infra/libre-wuji/system-backups.ncl}"
if command -v nickel >/dev/null && [ -f "$SYSTEM_BACKUPS_PATH" ]; then
echo "==> SystemBackupDef entries requiring on-host runner installation:"
nickel export "$SYSTEM_BACKUPS_PATH" 2>/dev/null \
| jq -r 'to_entries[] | " \(.value.name) -> host_selector=\(.value.host_selector.kind), members=\(.value.host_selector.members | join(","))"' || true
echo "==> Run 'provisioning component install backup_manager --target-hosts' to deploy binaries."
fi
echo "==> backup-manager cluster install complete in namespace $NAMESPACE"