#!/usr/bin/env bash # scripts/k3d-knoe-up.sh # Bring up the k3d-knoe local dev cluster: CNPG operator + single-replica DB + KDC + schema. # # Usage: # ./scripts/k3d-knoe-up.sh # # Prerequisites: # k3d, kubectl, docker (running) # # What it does: # 1. Creates k3d cluster "k3d-knoe" (if not present) # 2. Installs CNPG operator (v1.29.0) # 3. Applies knoe-db CNPG Cluster (single-replica, local-path storage) # 4. Waits for CNPG cluster to be healthy # 5. Applies KDC manifests (configmap, pvc, deployment, service) # 6. Runs etc/init_knoe_auth.sh schema --mode k3d to seed knoe.* tables # 7. Generates a persistent RS256 keypair for Phase 2 OIDC signing # (etc/gen_oidc_signing_key.sh — idempotent; keeps existing key if present) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" K8S_KNOE="$REPO_ROOT/k8s/knoe" CLUSTER_NAME="${K3D_CLUSTER_NAME:-k3d-knoe}" CTX="k3d-${CLUSTER_NAME}" DB_NS="knoe-db-0" SYS_NS="knoe-system" CNPG_VERSION="${CNPG_OPERATOR_VERSION:-1.29.0}" CNPG_NS="cnpg-system" log() { echo "[k3d-knoe-up] $*"; } die() { echo "[k3d-knoe-up] ERROR: $*" >&2; exit 1; } kube() { kubectl --context="$CTX" "$@"; } # ── 1. Cluster ──────────────────────────────────────────────────────────────── if k3d cluster list 2>/dev/null | awk '{print $1}' | grep -qx "${CLUSTER_NAME#k3d-}"; then log "k3d cluster '${CLUSTER_NAME#k3d-}' already exists — skipping create." else log "Creating k3d cluster '${CLUSTER_NAME#k3d-}'..." k3d cluster create "${CLUSTER_NAME#k3d-}" \ --agents 0 \ --k3s-arg "--disable=traefik@server:0" \ --wait log "Cluster created." fi # Ensure kubeconfig is merged k3d kubeconfig merge "${CLUSTER_NAME#k3d-}" --kubeconfig-merge-default >/dev/null 2>&1 || true # ── 2. CNPG operator ────────────────────────────────────────────────────────── log "Installing CNPG operator v${CNPG_VERSION}..." kube apply --server-side -f \ "https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-${CNPG_VERSION%.*}/releases/cnpg-${CNPG_VERSION}.yaml" \ 2>/dev/null || \ kube apply -f \ "https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-${CNPG_VERSION%.*}/releases/cnpg-${CNPG_VERSION}.yaml" log "Waiting for CNPG operator to be ready..." kube -n "$CNPG_NS" rollout status deployment/cnpg-controller-manager --timeout=120s # ── 3. knoe-db namespace + secret + cluster ─────────────────────────────────── kube get namespace "$DB_NS" >/dev/null 2>&1 || kube create namespace "$DB_NS" # Create the knoe-db-user secret (CNPG bootstrap owner secret) if ! kube -n "$DB_NS" get secret knoe-db-user >/dev/null 2>&1; then log "Creating knoe-db-user secret..." kube -n "$DB_NS" create secret generic knoe-db-user \ --from-literal=username=knoe \ --from-literal=password=knoe-dev-password fi # Render the knoe-db manifest for k3d: # - instances: 1 (single-replica) # - remove synology-iscsi storageClassName + selector blocks # - remove affinity with kubernetes.io/hostname topology # - remove plugins block (no barman in k3d) # - remove certificates block (no pre-issued TLS secrets in k3d) # - remove env block referencing AWS/Garage (not needed locally) # - substitute image registry placeholder with a public postgres image log "Rendering knoe-db manifest for k3d..." RENDERED=$(python3 - <<'PYEOF' import re, sys with open("k8s/knoe/knoe-db.yaml") as f: text = f.read() # instances: 3 -> 1 text = re.sub(r'(\binstances:\s*)3\b', r'\g<1>1', text) # Remove plugins block (barman) text = re.sub(r'\n plugins:\n( - .*\n( .*\n)*)+', '\n', text) # Remove certificates block text = re.sub(r'\n certificates:\n( .*\n)+', '\n', text) # Remove env block (AWS/Garage refs) text = re.sub(r'\n env:\n( - .*\n( .*\n)*)+', '\n', text) # Remove walStorage block (not needed for single-replica dev) text = re.sub(r'\n walStorage:\n( .*\n)+', '\n', text) # Replace synology-iscsi storageClassName text = re.sub(r'storageClassName: synology-iscsi', '', text) # Remove selector block with synology.storage labels text = re.sub(r'\n selector:\n matchLabels:\n synology\.storage/role: \w+\n', '\n', text) # Remove affinity block (contains kubernetes.io/hostname) text = re.sub(r'\n affinity:\n( .*\n)+', '\n', text) # Replace image placeholder with standard postgres (CNPG uses its own image) text = re.sub( r'imageName: \$\{KNOE_IMAGE_REGISTRY\}/knoe-db:\$\{KNOE_DB_IMAGE_TAG\}', 'imageName: ghcr.io/cloudnative-pg/postgresql:17', text ) # Remove maxSyncReplicas (irrelevant for single instance) text = re.sub(r'\n maxSyncReplicas: \d+\n', '\n', text) print(text) PYEOF ) echo "$RENDERED" | kube apply -f - log "Waiting for CNPG cluster 'knoe-db' to be healthy (up to 5 min)..." for i in $(seq 1 60); do phase=$(kube -n "$DB_NS" get cluster knoe-db \ -o jsonpath='{.status.phase}' 2>/dev/null || echo "") if [[ "$phase" == "Cluster in healthy state" ]]; then log "CNPG cluster is healthy." break fi if [[ $i -eq 60 ]]; then die "CNPG cluster did not become healthy in time. Phase: $phase" fi echo " [$i/60] phase='$phase' — waiting 5s..." sleep 5 done # ── 4. KDC ──────────────────────────────────────────────────────────────────── kube get namespace "$SYS_NS" >/dev/null 2>&1 || kube create namespace "$SYS_NS" log "Provisioning KDC (realm KNOE.LOCAL)..." KNOE_MODE=k3d APP_CLUSTER_KUBECONTEXT="$CTX" \ "$REPO_ROOT/etc/init_knoe_auth.sh" initialize --mode k3d --context "$CTX" # ── 7. Phase 2 OIDC signing key (laptop-side) ──────────────────────────────── log "Generating RS256 signing key for OIDC (idempotent) ..." bash "$REPO_ROOT/etc/gen_oidc_signing_key.sh" || die "OIDC keypair generation failed" log "" log "=== k3d-knoe is up ===" log " CNPG cluster: kubectl --context=$CTX -n $DB_NS get cluster knoe-db" log " KDC: kubectl --context=$CTX -n $SYS_NS get deploy knoe-kdc" log "" log "Next: make k3d-knoe-pf (opens port-forwards + prints env vars to export)" log "Then: see docs/local-dev-knoe-auth.md for the daily loop"