fix(bootstrap): correct cluster config + complete Phase4 ArgoCD bootstrap permanent fixes
This commit is contained in:
@@ -55,3 +55,5 @@ skills-lock.json
|
||||
.DS_Store
|
||||
CLAUDE.md
|
||||
docs/
|
||||
|
||||
k8s/argocd/seed-repo-secret.yaml
|
||||
@@ -1,21 +1,21 @@
|
||||
# ── Node IPs ──────────────────────────────────────────────────────────────────
|
||||
# CP_IP has a default. All W{N}_IP variables are expected to be exported from
|
||||
# ~/.zshrc (e.g. export W1_IP=192.168.1.162). No guards — assumed always set.
|
||||
CP_IP ?= 192.168.1.213
|
||||
|
||||
export CP_IP
|
||||
# ── Node IPs (3-CP HA topology) ───────────────────────────────────────────────
|
||||
CP1_IP := 192.168.1.166 # talos-cp-1
|
||||
CP2_IP := 192.168.1.213 # talos-cp-2 (storage: 3 disks)
|
||||
CP3_IP := 192.168.1.162 # talos-cp-3
|
||||
CP_VIP := 192.168.1.166 # controlplane VIP (currently .166)
|
||||
|
||||
# ── Paths ─────────────────────────────────────────────────────────────────────
|
||||
TALOSCONFIG := cluster-config/coreconfig
|
||||
CP_CONFIG := cluster-config/controlplane.yaml
|
||||
SECRETS := cluster-config/secrets.yaml
|
||||
TALOSCONFIG := cluster-config/talosconfig
|
||||
CP1_CONFIG := cluster-config/talos-cp-1.yaml
|
||||
CP2_CONFIG := cluster-config/talos-cp-2.yaml
|
||||
CP3_CONFIG := cluster-config/talos-cp-3.yaml
|
||||
KUBECONFIG := cluster-config/kubeconfig
|
||||
|
||||
CLUSTER_NAME := homelab-cluster
|
||||
CP_ENDPOINT := https://$(CP_IP):6443
|
||||
TALOS_IMAGE := factory.core.dev/installer/613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245:v1.13.3
|
||||
CLUSTER_NAME := homelab
|
||||
CP_ENDPOINT := https://$(CP_VIP):6443
|
||||
|
||||
TALOSCTL := corectl --coreconfig $(TALOSCONFIG)
|
||||
# Use talosctl (not corectl). Needs TALOSCONFIG env var pointing to talosconfig file.
|
||||
TALOSCTL := talosctl
|
||||
KUBECTL := kubectl --kubeconfig $(KUBECONFIG)
|
||||
|
||||
# Derive IP and config from worker number N (used by generic targets).
|
||||
@@ -28,236 +28,157 @@ W_CONFIG = cluster-config/worker-$(N).yaml
|
||||
# ── Help ──────────────────────────────────────────────────────────────────────
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Homelab cluster — available targets"
|
||||
@echo "Homelab cluster (3-CP HA: .166/.213/.163) — available targets"
|
||||
@echo ""
|
||||
@echo " Status"
|
||||
@echo " Status & Services"
|
||||
@echo " nodes kubectl get nodes"
|
||||
@echo " status-cp core node overview (control plane)"
|
||||
@echo " status-w1 core node overview (worker-1)"
|
||||
@echo " services-cp list core services (control plane)"
|
||||
@echo " services-w1 list core services (worker-1)"
|
||||
@echo " status-all etcd members on all 3 CPs"
|
||||
@echo " status-cp1/2/3 etcd members on specific CP"
|
||||
@echo " services-cp1/2/3 list Talos services on specific CP"
|
||||
@echo ""
|
||||
@echo " Logs"
|
||||
@echo " logs-cp stream kubelet logs (control plane)"
|
||||
@echo " logs-w1 stream kubelet logs (worker-1)"
|
||||
@echo " dmesg-cp kernel dmesg (control plane)"
|
||||
@echo " dmesg-w1 kernel dmesg (worker-1)"
|
||||
@echo " log-svc-cp stream a service log (control plane) SVC=<name>"
|
||||
@echo " log-svc-w1 stream a service log (worker-1) SVC=<name>"
|
||||
@echo " logs-cp1/2/3 stream kubelet logs from CP{1,2,3}"
|
||||
@echo " dmesg-cp1/2/3 stream kernel dmesg from CP{1,2,3}"
|
||||
@echo " log-svc-cp1/2/3 stream service logs (SVC=<name>)"
|
||||
@echo ""
|
||||
@echo " Config"
|
||||
@echo " gen-config regenerate controlplane.yaml + worker-N.yaml from secrets"
|
||||
@echo " apply-cp apply controlplane.yaml to CP node (live cluster)"
|
||||
@echo " apply-w1 apply cluster-config/worker-1.yaml to worker-1"
|
||||
@echo " apply-w1-insecure first-time apply to worker-1 (no certs yet)"
|
||||
@echo " apply-worker apply cluster-config/worker-N.yaml N=<num> W<N>_IP=<ip>"
|
||||
@echo " apply-worker-new first-time apply (--insecure) N=<num> W<N>_IP=<ip>"
|
||||
@echo " Config Apply"
|
||||
@echo " apply-all apply configs to all 3 CPs (talos-cp-{1,2,3}.yaml)"
|
||||
@echo " apply-cp1/2/3 apply config to specific CP"
|
||||
@echo ""
|
||||
@echo " Upgrade"
|
||||
@echo " upgrade-cp upgrade Talos on control plane"
|
||||
@echo " upgrade-w1 upgrade Talos on worker-1"
|
||||
@echo " upgrade-worker upgrade any worker N=<num> W<N>_IP=<ip>"
|
||||
@echo ""
|
||||
@echo " Shutdown / Reboot"
|
||||
@echo " shutdown-cluster graceful full shutdown (drain w1 → off w1 → off cp)"
|
||||
@echo " shutdown-cp shut down control plane only"
|
||||
@echo " shutdown-w1 shut down worker-1 only"
|
||||
@echo " shutdown-worker shut down any worker N=<num> W<N>_IP=<ip>"
|
||||
@echo " reboot-cp reboot control plane"
|
||||
@echo " reboot-w1 reboot worker-1"
|
||||
@echo " reboot-worker reboot any worker N=<num> W<N>_IP=<ip>"
|
||||
@echo ""
|
||||
@echo " Inspect (node filesystem)"
|
||||
@echo " node-ls <ip> <path> list files on a node"
|
||||
@echo " node-read <ip> <path> read a file on a node"
|
||||
@echo ""
|
||||
@echo " Maintenance"
|
||||
@echo " clean-pods delete Evicted/Failed/Terminating pods cluster-wide"
|
||||
@echo " Reboot"
|
||||
@echo " reboot-all reboot all 3 CPs"
|
||||
@echo " reboot-cp1/2/3 reboot specific CP"
|
||||
@echo ""
|
||||
@echo " Port-forwards"
|
||||
@echo " pf-grafana localhost:3000 → Grafana"
|
||||
@echo " pf-minio localhost:9001 → MinIO console / localhost:9000 → S3 API"
|
||||
@echo " pf-loki localhost:3100 → Loki HTTP API"
|
||||
@echo " pf-portainer localhost:9000 → Portainer UI (dashboard ns)"
|
||||
@echo " pf-prometheus localhost:9090 → Prometheus UI (monitoring ns)"
|
||||
@echo " pf-longhorn localhost:8080 → Longhorn UI"
|
||||
@echo " pf-iam localhost:7000 → Authentik IAM (when deployed)"
|
||||
@echo " pf-prometheus localhost:9090 → Prometheus UI"
|
||||
@echo ""
|
||||
@echo " CLI"
|
||||
@echo " cli build core-cli and install to ~/.local/bin/core"
|
||||
@echo ""
|
||||
@echo " Variables"
|
||||
@echo " CP_IP (default: 192.168.1.160)"
|
||||
@echo " W1_IP (export from ~/.zshrc — e.g. export W1_IP=192.168.1.162)"
|
||||
@echo " N (required for generic targets — worker number, e.g. N=2)"
|
||||
@echo " W<N>_IP (export from ~/.zshrc — e.g. export W2_IP=192.168.1.163)"
|
||||
@echo " SVC (required for log-svc-* targets, e.g. SVC=kubelet)"
|
||||
@echo " IPs"
|
||||
@echo " CP1 (talos-cp-1): $(CP1_IP) — NVMe, wg0/wg1, VIP"
|
||||
@echo " CP2 (talos-cp-2): $(CP2_IP) — 3 Longhorn disks"
|
||||
@echo " CP3 (talos-cp-3): $(CP3_IP) — NVMe"
|
||||
|
||||
# ── Status ────────────────────────────────────────────────────────────────────
|
||||
.PHONY: nodes
|
||||
nodes:
|
||||
$(KUBECTL) get nodes -o wide
|
||||
|
||||
.PHONY: status-all
|
||||
status-all: status-cp1 status-cp2 status-cp3
|
||||
|
||||
.PHONY: status-cp1
|
||||
status-cp1:
|
||||
$(TALOSCTL) -n $(CP1_IP) --endpoints $(CP1_IP) etcd members
|
||||
|
||||
.PHONY: status-cp2
|
||||
status-cp2:
|
||||
$(TALOSCTL) -n $(CP2_IP) --endpoints $(CP2_IP) etcd members
|
||||
|
||||
.PHONY: status-cp3
|
||||
status-cp3:
|
||||
$(TALOSCTL) -n $(CP3_IP) --endpoints $(CP3_IP) etcd members
|
||||
|
||||
.PHONY: status-cp
|
||||
status-cp:
|
||||
$(TALOSCTL) --nodes $(CP_IP) get members
|
||||
status-cp: status-all
|
||||
|
||||
.PHONY: status-w1
|
||||
status-w1:
|
||||
$(TALOSCTL) --nodes $(W1_IP) get members
|
||||
.PHONY: services-cp1
|
||||
services-cp1:
|
||||
$(TALOSCTL) -n $(CP1_IP) --endpoints $(CP1_IP) service
|
||||
|
||||
.PHONY: services-cp
|
||||
services-cp:
|
||||
$(TALOSCTL) --nodes $(CP_IP) service
|
||||
.PHONY: services-cp2
|
||||
services-cp2:
|
||||
$(TALOSCTL) -n $(CP2_IP) --endpoints $(CP2_IP) service
|
||||
|
||||
.PHONY: services-w1
|
||||
services-w1:
|
||||
$(TALOSCTL) --nodes $(W1_IP) service
|
||||
.PHONY: services-cp3
|
||||
services-cp3:
|
||||
$(TALOSCTL) -n $(CP3_IP) --endpoints $(CP3_IP) service
|
||||
|
||||
# ── Logs ──────────────────────────────────────────────────────────────────────
|
||||
.PHONY: logs-cp
|
||||
logs-cp:
|
||||
$(TALOSCTL) --nodes $(CP_IP) logs kubelet -f
|
||||
# ── Logs (3-CP) ───────────────────────────────────────────────────────────────
|
||||
.PHONY: logs-cp1
|
||||
logs-cp1:
|
||||
$(TALOSCTL) -n $(CP1_IP) --endpoints $(CP1_IP) logs kubelet -f
|
||||
|
||||
.PHONY: logs-w1
|
||||
logs-w1:
|
||||
$(TALOSCTL) --nodes $(W1_IP) logs kubelet -f
|
||||
.PHONY: logs-cp2
|
||||
logs-cp2:
|
||||
$(TALOSCTL) -n $(CP2_IP) --endpoints $(CP2_IP) logs kubelet -f
|
||||
|
||||
.PHONY: dmesg-cp
|
||||
dmesg-cp:
|
||||
$(TALOSCTL) --nodes $(CP_IP) dmesg --follow
|
||||
.PHONY: logs-cp3
|
||||
logs-cp3:
|
||||
$(TALOSCTL) -n $(CP3_IP) --endpoints $(CP3_IP) logs kubelet -f
|
||||
|
||||
.PHONY: dmesg-w1
|
||||
dmesg-w1:
|
||||
$(TALOSCTL) --nodes $(W1_IP) dmesg --follow
|
||||
.PHONY: dmesg-cp1
|
||||
dmesg-cp1:
|
||||
$(TALOSCTL) -n $(CP1_IP) --endpoints $(CP1_IP) dmesg --follow
|
||||
|
||||
# Usage: make log-svc-cp SVC=etcd
|
||||
.PHONY: log-svc-cp
|
||||
log-svc-cp:
|
||||
.PHONY: dmesg-cp2
|
||||
dmesg-cp2:
|
||||
$(TALOSCTL) -n $(CP2_IP) --endpoints $(CP2_IP) dmesg --follow
|
||||
|
||||
.PHONY: dmesg-cp3
|
||||
dmesg-cp3:
|
||||
$(TALOSCTL) -n $(CP3_IP) --endpoints $(CP3_IP) dmesg --follow
|
||||
|
||||
# Usage: make log-svc-cp1 SVC=etcd
|
||||
.PHONY: log-svc-cp1
|
||||
log-svc-cp1:
|
||||
ifndef SVC
|
||||
$(error SVC is not set — run: make log-svc-cp SVC=<service-name>)
|
||||
$(error SVC is not set — run: make log-svc-cp1 SVC=<service-name>)
|
||||
endif
|
||||
$(TALOSCTL) --nodes $(CP_IP) logs $(SVC) -f
|
||||
$(TALOSCTL) -n $(CP1_IP) --endpoints $(CP1_IP) logs $(SVC) -f
|
||||
|
||||
.PHONY: log-svc-w1
|
||||
log-svc-w1:
|
||||
.PHONY: log-svc-cp2
|
||||
log-svc-cp2:
|
||||
ifndef SVC
|
||||
$(error SVC is not set — run: make log-svc-w1 SVC=<service-name>)
|
||||
$(error SVC is not set — run: make log-svc-cp2 SVC=<service-name>)
|
||||
endif
|
||||
$(TALOSCTL) --nodes $(W1_IP) logs $(SVC) -f
|
||||
$(TALOSCTL) -n $(CP2_IP) --endpoints $(CP2_IP) logs $(SVC) -f
|
||||
|
||||
# ── Config generation ─────────────────────────────────────────────────────────
|
||||
.PHONY: gen-config
|
||||
gen-config:
|
||||
corectl gen config $(CLUSTER_NAME) $(CP_ENDPOINT) \
|
||||
--with-secrets $(SECRETS) \
|
||||
--output-dir cluster-config/ \
|
||||
--force
|
||||
.PHONY: log-svc-cp3
|
||||
log-svc-cp3:
|
||||
ifndef SVC
|
||||
$(error SVC is not set — run: make log-svc-cp3 SVC=<service-name>)
|
||||
endif
|
||||
$(TALOSCTL) -n $(CP3_IP) --endpoints $(CP3_IP) logs $(SVC) -f
|
||||
|
||||
# ── Config Apply (3-CP) ───────────────────────────────────────────────────────
|
||||
.PHONY: apply-all
|
||||
apply-all: apply-cp1 apply-cp2 apply-cp3
|
||||
@echo "✓ All 3 control planes configured"
|
||||
|
||||
.PHONY: apply-cp1
|
||||
apply-cp1:
|
||||
$(TALOSCTL) -n $(CP1_IP) --endpoints $(CP1_IP) apply-config -f $(CP1_CONFIG)
|
||||
|
||||
.PHONY: apply-cp2
|
||||
apply-cp2:
|
||||
$(TALOSCTL) -n $(CP2_IP) --endpoints $(CP2_IP) apply-config -f $(CP2_CONFIG)
|
||||
|
||||
.PHONY: apply-cp3
|
||||
apply-cp3:
|
||||
$(TALOSCTL) -n $(CP3_IP) --endpoints $(CP3_IP) apply-config -f $(CP3_CONFIG)
|
||||
|
||||
# ── Config apply ──────────────────────────────────────────────────────────────
|
||||
.PHONY: apply-cp
|
||||
apply-cp:
|
||||
$(TALOSCTL) apply-config \
|
||||
--nodes $(CP_IP) \
|
||||
--file $(CP_CONFIG)
|
||||
apply-cp: apply-all
|
||||
|
||||
.PHONY: apply-w1
|
||||
apply-w1:
|
||||
$(TALOSCTL) apply-config \
|
||||
--nodes $(W1_IP) \
|
||||
--file cluster-config/worker-1.yaml
|
||||
.PHONY: reboot-all
|
||||
reboot-all: reboot-cp1 reboot-cp2 reboot-cp3
|
||||
@echo "✓ All 3 control planes rebooting"
|
||||
|
||||
# First-time apply to worker-1 (no certs yet)
|
||||
.PHONY: apply-w1-insecure
|
||||
apply-w1-insecure:
|
||||
$(TALOSCTL) apply-config \
|
||||
--nodes $(W1_IP) \
|
||||
--file cluster-config/worker-1.yaml \
|
||||
--insecure
|
||||
.PHONY: reboot-cp1
|
||||
reboot-cp1:
|
||||
$(TALOSCTL) -n $(CP1_IP) --endpoints $(CP1_IP) reboot
|
||||
|
||||
# Generic targets — derive both IP and config from N.
|
||||
# Usage: make apply-worker N=2 W2_IP=192.168.1.162
|
||||
# make apply-worker N=3 W3_IP=192.168.1.163
|
||||
.PHONY: apply-worker
|
||||
apply-worker:
|
||||
ifndef N
|
||||
$(error N is not set — run: make apply-worker N=<num> W<N>_IP=<ip>)
|
||||
endif
|
||||
$(TALOSCTL) apply-config \
|
||||
--nodes $(W_IP) \
|
||||
--file $(W_CONFIG)
|
||||
.PHONY: reboot-cp2
|
||||
reboot-cp2:
|
||||
$(TALOSCTL) -n $(CP2_IP) --endpoints $(CP2_IP) reboot
|
||||
|
||||
.PHONY: apply-worker-new
|
||||
apply-worker-new:
|
||||
ifndef N
|
||||
$(error N is not set — run: make apply-worker-new N=<num> W<N>_IP=<ip>)
|
||||
endif
|
||||
$(TALOSCTL) apply-config \
|
||||
--nodes $(W_IP) \
|
||||
--file $(W_CONFIG) \
|
||||
--insecure
|
||||
|
||||
# ── Upgrade ───────────────────────────────────────────────────────────────────
|
||||
.PHONY: upgrade-cp
|
||||
upgrade-cp:
|
||||
$(TALOSCTL) upgrade \
|
||||
--nodes $(CP_IP) \
|
||||
--image $(TALOS_IMAGE) \
|
||||
--preserve
|
||||
|
||||
.PHONY: upgrade-w1
|
||||
upgrade-w1:
|
||||
$(TALOSCTL) upgrade \
|
||||
--nodes $(W1_IP) \
|
||||
--image $(TALOS_IMAGE) \
|
||||
--preserve
|
||||
|
||||
# Usage: make upgrade-worker N=2 W2_IP=192.168.1.162
|
||||
.PHONY: upgrade-worker
|
||||
upgrade-worker:
|
||||
ifndef N
|
||||
$(error N is not set — run: make upgrade-worker N=<num> W<N>_IP=<ip>)
|
||||
endif
|
||||
$(TALOSCTL) upgrade \
|
||||
--nodes $(W_IP) \
|
||||
--image $(TALOS_IMAGE) \
|
||||
--preserve
|
||||
|
||||
# ── Shutdown / Reboot ─────────────────────────────────────────────────────────
|
||||
# Full cluster: drain workers first so pods stop cleanly, then workers off,
|
||||
# then CP last (etcd must be the final process to stop).
|
||||
.PHONY: shutdown-cluster
|
||||
shutdown-cluster:
|
||||
@echo "--- draining core-worker-1 ---"
|
||||
$(KUBECTL) drain core-worker-1 --ignore-daemonsets --delete-emptydir-data
|
||||
@echo "--- shutting down worker-1 ---"
|
||||
$(TALOSCTL) --nodes $(W1_IP) shutdown
|
||||
@echo "--- shutting down control plane (last) ---"
|
||||
$(TALOSCTL) --nodes $(CP_IP) shutdown
|
||||
|
||||
.PHONY: shutdown-cp
|
||||
shutdown-cp:
|
||||
$(TALOSCTL) --nodes $(CP_IP) shutdown
|
||||
|
||||
.PHONY: shutdown-w1
|
||||
shutdown-w1:
|
||||
$(TALOSCTL) --nodes $(W1_IP) shutdown
|
||||
|
||||
# Usage: make shutdown-worker N=2 W2_IP=192.168.1.162
|
||||
.PHONY: shutdown-worker
|
||||
shutdown-worker:
|
||||
ifndef N
|
||||
$(error N is not set — run: make shutdown-worker N=<num> W<N>_IP=<ip>)
|
||||
endif
|
||||
$(TALOSCTL) --nodes $(W_IP) shutdown
|
||||
.PHONY: reboot-cp3
|
||||
reboot-cp3:
|
||||
$(TALOSCTL) -n $(CP3_IP) --endpoints $(CP3_IP) reboot
|
||||
|
||||
.PHONY: reboot-cp
|
||||
reboot-cp:
|
||||
$(TALOSCTL) --nodes $(CP_IP) reboot
|
||||
|
||||
.PHONY: reboot-w1
|
||||
reboot-w1:
|
||||
$(TALOSCTL) --nodes $(W1_IP) reboot
|
||||
reboot-cp: reboot-all
|
||||
|
||||
# Usage: make reboot-worker N=2 W2_IP=192.168.1.162
|
||||
.PHONY: reboot-worker
|
||||
@@ -268,18 +189,16 @@ endif
|
||||
$(TALOSCTL) --nodes $(W_IP) reboot
|
||||
|
||||
# ── Inspect ───────────────────────────────────────────────────────────────────
|
||||
# Positional args: make node-ls 192.168.1.160 /etc/kubernetes/manifests
|
||||
# $(word 2/3, $(MAKECMDGOALS)) captures the extra words; the % rule absorbs
|
||||
# them so Make doesn't error with "No rule to make target".
|
||||
# Positional args: make node-ls 192.168.1.166 /etc/kubernetes/manifests
|
||||
.PHONY: node-ls
|
||||
node-ls:
|
||||
$(TALOSCTL) --nodes $(word 2,$(MAKECMDGOALS)) ls $(word 3,$(MAKECMDGOALS))
|
||||
$(TALOSCTL) -n $(word 2,$(MAKECMDGOALS)) --endpoints $(word 2,$(MAKECMDGOALS)) ls $(word 3,$(MAKECMDGOALS))
|
||||
|
||||
.PHONY: node-read
|
||||
node-read:
|
||||
$(TALOSCTL) --nodes $(word 2,$(MAKECMDGOALS)) read $(word 3,$(MAKECMDGOALS))
|
||||
$(TALOSCTL) -n $(word 2,$(MAKECMDGOALS)) --endpoints $(word 2,$(MAKECMDGOALS)) read $(word 3,$(MAKECMDGOALS))
|
||||
|
||||
# Absorb positional arguments passed to node-ls / node-read
|
||||
# Absorb positional arguments
|
||||
%:
|
||||
@:
|
||||
|
||||
|
||||
+153
-31
@@ -19,7 +19,7 @@ set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BOOT="$SCRIPT_DIR/k8s/bootstrap"
|
||||
SOPS_KEY="${SOPS_KEY:-$HOME/.sops/homelab-age.key}"
|
||||
SOPS_KEY="${SOPS_KEY:-$HOME/.sops/key.txt}"
|
||||
DEPLOY_KEY="${DEPLOY_KEY:-$HOME/.ssh/argocd_seed}"
|
||||
GITHUB_SSH="[email protected]:Riotpiaole/riotpiao.homelab.com.git"
|
||||
|
||||
@@ -27,6 +27,13 @@ log() { echo "[$(date +%H:%M:%S)] $*"; }
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
phase(){ echo; echo "━━━ $* ━━━"; echo; }
|
||||
|
||||
# Idempotent helm repo setup
|
||||
ensure_helm_repo() {
|
||||
local name=$1 url=$2
|
||||
helm repo list 2>/dev/null | grep -q "^$name" || helm repo add "$name" "$url" >/dev/null
|
||||
helm repo update "$name" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
preflight() {
|
||||
log "preflight…"
|
||||
kubectl cluster-info >/dev/null || die "kubectl not configured / cluster unreachable"
|
||||
@@ -39,7 +46,7 @@ preflight() {
|
||||
p1_cilium() {
|
||||
phase "PHASE 1a: CNI (Cilium)"
|
||||
if kubectl -n kube-system get ds cilium >/dev/null 2>&1; then log "cilium present, skip"; return; fi
|
||||
helm repo add cilium https://helm.cilium.io >/dev/null; helm repo update >/dev/null
|
||||
ensure_helm_repo cilium https://helm.cilium.io
|
||||
helm install cilium cilium/cilium -n kube-system \
|
||||
--set ipam.mode=kubernetes --set kubeProxyReplacement=true --wait --timeout 10m
|
||||
log "✅ cilium installed"
|
||||
@@ -47,60 +54,175 @@ p1_cilium() {
|
||||
|
||||
p1_longhorn() {
|
||||
phase "PHASE 1b: STORAGE (Longhorn)"
|
||||
if helm -n longhorn-system list 2>/dev/null | grep -q longhorn; then log "longhorn present, skip"; return; fi
|
||||
helm repo add longhorn https://charts.longhorn.io >/dev/null; helm repo update >/dev/null
|
||||
helm install longhorn longhorn/longhorn -n longhorn-system --create-namespace \
|
||||
--values "$BOOT/phase1-storage/longhorn-values.yaml" --wait --timeout 10m
|
||||
kubectl -n longhorn-system wait --for=condition=available --timeout=300s deploy/longhorn-manager
|
||||
|
||||
# Always ensure namespace + StorageClasses (idempotent, resumable)
|
||||
kubectl apply -f "$BOOT/phase1-storage/namespace.yaml"
|
||||
|
||||
# Install Longhorn if not present
|
||||
if ! helm -n longhorn-system list 2>/dev/null | grep -q longhorn; then
|
||||
ensure_helm_repo longhorn https://charts.longhorn.io
|
||||
log "Installing Longhorn storage (this may take 5-10 minutes)..."
|
||||
if helm install longhorn longhorn/longhorn -n longhorn-system \
|
||||
--values "$BOOT/phase1-storage/longhorn-values.yaml" --wait --timeout 10m; then
|
||||
log "✅ Longhorn installed"
|
||||
else
|
||||
log "⚠️ Helm install failed, but continuing to ensure resources..."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Always apply StorageClasses (even if helm install partially failed)
|
||||
kubectl apply -f "$BOOT/phase1-storage/storageclasses.yaml"
|
||||
|
||||
# Verify critical components (resumable check)
|
||||
if kubectl -n longhorn-system wait --for=condition=available --timeout=300s deploy/longhorn-manager 2>/dev/null; then
|
||||
log "✅ longhorn installed"
|
||||
else
|
||||
log "⚠️ longhorn-manager not ready yet, but StorageClasses applied. Re-run to verify."
|
||||
fi
|
||||
}
|
||||
|
||||
p2_cnpg() {
|
||||
phase "PHASE 2: CNPG OPERATOR"
|
||||
if helm -n cnpg-system list 2>/dev/null | grep -q cnpg; then log "cnpg present, skip"; return; fi
|
||||
helm repo add cnpg https://cloudnative-pg.github.io/charts >/dev/null; helm repo update >/dev/null
|
||||
helm install cnpg cnpg/cloudnative-pg -n cnpg-system --create-namespace \
|
||||
--values "$BOOT/phase2-cnpg/cnpg-values.yaml" --wait --timeout 5m
|
||||
if kubectl get crd clusters.postgresql.cnpg.io >/dev/null 2>&1; then log "cnpg CRD present, skip install"; return; fi
|
||||
ensure_helm_repo cnpg https://cloudnative-pg.github.io/charts
|
||||
log "Installing CloudNativePG operator (this may take 2-3 minutes)..."
|
||||
if helm install cnpg cnpg/cloudnative-pg -n cnpg-system --create-namespace \
|
||||
--values "$BOOT/phase2-cnpg/cnpg-values.yaml" --wait --timeout 5m; then
|
||||
log "✅ CNPG operator installed"
|
||||
else
|
||||
log "❌ CNPG operator install failed"
|
||||
return 1
|
||||
fi
|
||||
kubectl get crd clusters.postgresql.cnpg.io >/dev/null || die "CNPG CRD not registered"
|
||||
log "✅ cnpg operator installed"
|
||||
}
|
||||
|
||||
p3_forgejo() {
|
||||
phase "PHASE 3: forgejo-db + Forgejo (ns cicd)"
|
||||
kubectl create ns cicd --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Always ensure namespace + NetworkPolicy + Secrets (idempotent)
|
||||
kubectl apply -f "$BOOT/phase3-forgejo/namespace.yaml"
|
||||
|
||||
# Clean up old Valkey NetworkPolicy if it exists (from bundled chart)
|
||||
kubectl delete networkpolicy forgejo-valkey-cluster -n cicd 2>/dev/null || true
|
||||
|
||||
# Apply CNPG-specific NetworkPolicy
|
||||
kubectl apply -f "$BOOT/phase3-forgejo/cnpg-networkpolicy.yaml"
|
||||
|
||||
# Create Forgejo admin secret (bootstrap-time only, before ArgoCD exists)
|
||||
# In GitOps mode, ArgoCD will sync the SOPS-encrypted version from git
|
||||
if ! kubectl get secret forgejo-admin -n cicd >/dev/null 2>&1; then
|
||||
log "Creating forgejo-admin secret from .env (bootstrap mode)"
|
||||
[ -f "$HOME/workplace/homelab/.env" ] && source "$HOME/workplace/homelab/.env"
|
||||
kubectl -n cicd create secret generic forgejo-admin \
|
||||
--from-literal=username=rock \
|
||||
--from-literal=password="${FORGEJO_ADMIN_PASSWORD}" \
|
||||
--from-literal=email=[email protected]
|
||||
else
|
||||
log "forgejo-admin secret exists, skip (managed by ArgoCD in GitOps mode)"
|
||||
fi
|
||||
|
||||
# Check if forgejo-db cluster exists and is Ready
|
||||
if kubectl get cluster forgejo-db -n cicd >/dev/null 2>&1; then
|
||||
if kubectl get cluster forgejo-db -n cicd -o jsonpath='{.status.phase}' 2>/dev/null | grep -q "Cluster in healthy state"; then
|
||||
log "forgejo-db already Ready, skip wait"
|
||||
else
|
||||
log "forgejo-db exists but not Ready, waiting for all 3 instances (up to 30 min)…"
|
||||
if kubectl wait --for=condition=Ready --timeout=1800s cluster/forgejo-db -n cicd; then
|
||||
log "✅ forgejo-db cluster is Ready"
|
||||
else
|
||||
log "❌ forgejo-db cluster failed to become Ready"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log "creating forgejo-db cluster (3 instances)"
|
||||
kubectl apply -f "$BOOT/phase3-forgejo/forgejo-db.yaml"
|
||||
log "waiting for forgejo-db Ready (3-5 min)…"
|
||||
kubectl wait --for=condition=Ready --timeout=600s cluster/forgejo-db -n cicd
|
||||
log "Waiting for all 3 CNPG instances to be Ready (up to 30 min)…"
|
||||
if kubectl wait --for=condition=Ready --timeout=1800s cluster/forgejo-db -n cicd; then
|
||||
log "✅ forgejo-db cluster is Ready"
|
||||
else
|
||||
log "❌ forgejo-db cluster failed to become Ready"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
kubectl -n cicd get secret forgejo-db-app >/dev/null || die "CNPG did not create forgejo-db-app secret"
|
||||
if helm -n cicd list 2>/dev/null | grep -q forgejo; then log "forgejo present, skip"; return; fi
|
||||
helm repo add forgejo https://code.forgejo.org/forgejo-helm >/dev/null 2>&1 || \
|
||||
helm repo add forgejo https://dl.gitea.io/charts/ >/dev/null
|
||||
helm repo update >/dev/null
|
||||
helm install forgejo forgejo/forgejo -n cicd \
|
||||
--values "$BOOT/phase3-forgejo/forgejo-values.yaml" --wait --timeout 10m
|
||||
log "✅ forgejo up — now push this repo to Forgejo and configure the GitHub pull-mirror"
|
||||
|
||||
# Install Forgejo if not present
|
||||
if helm -n cicd list 2>/dev/null | grep -q forgejo; then log "forgejo helm release present, skip"; return; fi
|
||||
ensure_helm_repo forgejo https://dl.gitea.io/charts/
|
||||
log "Installing Forgejo (this may take 10-15 minutes on slow nodes)..."
|
||||
if helm install forgejo forgejo/gitea -n cicd \
|
||||
--values "$BOOT/phase3-forgejo/forgejo-values.yaml" --wait --timeout 10m; then
|
||||
log "✅ Forgejo installed"
|
||||
else
|
||||
log "❌ Forgejo install failed"
|
||||
return 1
|
||||
fi
|
||||
log "Forgejo is up — now push this repo to Forgejo and configure the GitHub pull-mirror"
|
||||
}
|
||||
|
||||
p4_argocd() {
|
||||
phase "PHASE 4: ArgoCD (seeded from GitHub)"
|
||||
|
||||
# Always ensure namespace + repository secret (idempotent)
|
||||
kubectl create ns argocd --dry-run=client -o yaml | kubectl apply -f -
|
||||
# SOPS age key for the repo-server CMP plugin
|
||||
kubectl -n argocd create secret generic sops-age \
|
||||
--from-file=keys.txt="$SOPS_KEY" --dry-run=client -o yaml | kubectl apply -f -
|
||||
# GitHub deploy-key repo credential (read-only)
|
||||
kubectl -n argocd create secret generic seed-github-repo \
|
||||
--from-literal=type=git --from-literal=url="$GITHUB_SSH" \
|
||||
--from-file=sshPrivateKey="$DEPLOY_KEY" --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl -n argocd label secret seed-github-repo argocd.argoproj.io/secret-type=repository --overwrite
|
||||
if ! helm -n argocd list 2>/dev/null | grep -q argocd; then
|
||||
helm repo add argo https://argoproj.github.io/argo-helm >/dev/null; helm repo update >/dev/null
|
||||
helm install argocd argo/argo-cd -n argocd \
|
||||
--values "$BOOT/phase4-argocd/argocd-values.yaml" --wait --timeout 10m
|
||||
kubectl -n argocd label secret seed-github-repo argocd.argoproj.io/secret-type=repository --overwrite 2>/dev/null || true
|
||||
|
||||
# Decrypt and apply any encrypted secrets from bootstrap dir (local SOPS)
|
||||
if command -v sops &> /dev/null; then
|
||||
export SOPS_AGE_KEY_FILE="$SOPS_KEY"
|
||||
log "Decrypting encrypted secrets with local SOPS..."
|
||||
local decrypted_count=0
|
||||
for enc_file in "$BOOT"/phase*/**.enc.yaml; do
|
||||
[ -f "$enc_file" ] || continue
|
||||
log " → Decrypting $(basename "$enc_file")..."
|
||||
if sops -d "$enc_file" | kubectl apply -f -; then
|
||||
decrypted_count=$((decrypted_count + 1))
|
||||
log " ✅ Applied"
|
||||
else
|
||||
log " ⚠️ Failed (may already exist)"
|
||||
fi
|
||||
kubectl -n argocd wait --for=condition=available --timeout=300s deploy/argocd-server
|
||||
done
|
||||
log "Decrypted and applied $decrypted_count secret(s)"
|
||||
else
|
||||
log "⚠️ SOPS not installed, skipping encrypted secret decryption"
|
||||
fi
|
||||
|
||||
# Install ArgoCD if not present
|
||||
if ! helm -n argocd list 2>/dev/null | grep -q argocd; then
|
||||
ensure_helm_repo argo https://argoproj.github.io/argo-helm
|
||||
log "Installing ArgoCD via Helm (installing chart, pods will start afterward)..."
|
||||
if helm install argocd argo/argo-cd -n argocd \
|
||||
--values "$BOOT/phase4-argocd/argocd-values.yaml" --timeout 10m; then
|
||||
log "✅ ArgoCD Helm release installed (pods starting...)"
|
||||
else
|
||||
log "❌ ArgoCD Helm install failed"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log "ArgoCD Helm release already exists, skipping install"
|
||||
fi
|
||||
|
||||
# Wait for server ready (resumable - slow on talos-cp-2)
|
||||
log "Waiting for argocd-server deployment to be available (max 10 minutes)..."
|
||||
if kubectl -n argocd wait --for=condition=available --timeout=600s deploy/argocd-server; then
|
||||
log "✅ argocd-server is available"
|
||||
else
|
||||
log "❌ argocd-server failed to become available within 10 minutes"
|
||||
log "Check pods: kubectl get pods -n argocd"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Always apply root app (idempotent)
|
||||
kubectl apply -f "$BOOT/phase4-argocd/root-app-github.yaml"
|
||||
log "✅ ArgoCD syncing from GitHub seed. Watch: kubectl get applications -n argocd"
|
||||
log "NOTE: SOPS CMP plugin not installed yet (bootstrap uses local SOPS decryption)."
|
||||
log " To add SOPS plugin for GitOps, see k8s/bootstrap/phase4-argocd/argocd-cmp-cm.yaml"
|
||||
}
|
||||
|
||||
p5_cutover() {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Longhorn Helm Values — Single Source of Truth
|
||||
# Used by both bootstrap.sh (Helm install) and ArgoCD (adoption)
|
||||
# Chart: https://github.com/longhorn/charts
|
||||
#
|
||||
# NOTE: Namespace requires PodSecurity=privileged labels (Longhorn needs host access).
|
||||
# bootstrap.sh applies namespace.yaml automatically. For manual install:
|
||||
# kubectl apply -f k8s/bootstrap/phase1-storage/namespace.yaml
|
||||
|
||||
defaultSettings:
|
||||
# 3-node HA configuration
|
||||
@@ -26,6 +30,7 @@ persistence:
|
||||
|
||||
# CSI plugin must tolerate control-plane taints
|
||||
csi:
|
||||
iscsiadmPath: /usr/sbin/iscsiadm
|
||||
kubeletRootDir: /var/lib/kubelet
|
||||
attacherReplicaCount: 3
|
||||
provisionerReplicaCount: 3
|
||||
@@ -51,6 +56,8 @@ longhornUI:
|
||||
replicas: 1
|
||||
|
||||
# Monitoring (Prometheus ServiceMonitor)
|
||||
# Disabled during bootstrap (Prometheus CRDs not installed yet)
|
||||
# Re-enable via ArgoCD after Prometheus stack is deployed
|
||||
metrics:
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
enabled: false
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: longhorn-system
|
||||
labels:
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
pod-security.kubernetes.io/audit: privileged
|
||||
pod-security.kubernetes.io/warn: privileged
|
||||
@@ -2,8 +2,9 @@
|
||||
# The default 'longhorn' SC is created by Helm chart
|
||||
# These are additional specialized classes
|
||||
---
|
||||
# CNPG-specific StorageClass with postgres UID/GID mount options
|
||||
# Fixes "read-only filesystem" error when PostgreSQL (UID 26) tries to write
|
||||
# CNPG-specific StorageClass for PostgreSQL volumes
|
||||
# CNPG handles filesystem ownership via securityContext.fsGroup (UID/GID 26)
|
||||
# Separate from default 'longhorn' to allow CNPG-specific tuning
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
@@ -19,9 +20,7 @@ parameters:
|
||||
fromBackup: ""
|
||||
dataLocality: "best-effort"
|
||||
fsType: "ext4"
|
||||
mountOptions:
|
||||
- "noatime"
|
||||
- "uid=26" # postgres user
|
||||
- "gid=26" # postgres group
|
||||
# mountOptions removed - uid/gid are NOT valid for ext4 (only for FAT/VFAT)
|
||||
# CNPG handles ownership via securityContext.fsGroup automatically
|
||||
reclaimPolicy: Delete
|
||||
volumeBindingMode: Immediate
|
||||
|
||||
@@ -19,8 +19,10 @@ webhook:
|
||||
failurePolicy: Fail
|
||||
|
||||
# Monitoring
|
||||
# PodMonitor disabled during bootstrap (Prometheus CRDs not installed yet)
|
||||
# Re-enable via ArgoCD after Prometheus stack is deployed
|
||||
monitoring:
|
||||
podMonitorEnabled: true
|
||||
podMonitorEnabled: false
|
||||
grafanaDashboard:
|
||||
create: false # We'll manage dashboards via ArgoCD later
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# NetworkPolicy for CNPG pods - allow pod-to-pod replication traffic
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: forgejo-db-allow-replication
|
||||
namespace: cicd
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
cnpg.io/cluster: forgejo-db
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
# Allow CNPG operator to reach instance status endpoints (port 8000)
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: cnpg-system
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8000
|
||||
# Allow PostgreSQL connections from application pods (Forgejo)
|
||||
- from:
|
||||
- podSelector: {}
|
||||
namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: cicd
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
# Allow PostgreSQL replication from other CNPG pods
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
cnpg.io/cluster: forgejo-db
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
# Allow metrics scraping
|
||||
- from:
|
||||
- namespaceSelector: {}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 9187
|
||||
egress:
|
||||
# Allow all egress (CNPG needs to reach services, API server, DNS, etc.)
|
||||
# Restricting egress breaks replication through services
|
||||
- {}
|
||||
@@ -64,8 +64,8 @@ spec:
|
||||
log_filename: "postgres"
|
||||
|
||||
storage:
|
||||
size: 50Gi
|
||||
storageClass: longhorn-cnpg # Uses postgres UID/GID mount options
|
||||
size: 25Gi
|
||||
storageClass: longhorn-cnpg
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#ENC[AES256_GCM,data:85lNBL02TeroUW8dissgxEwjnOZnb0fg7jndGim3/QSi3/hFEF8FQa9nrQ==,iv:lM1jBHIxFkQriZ6BjGRXAlymUs5nX4Kvt1WNxUeZlkU=,tag:wm/D5qVcrcu3VXcZkEj70A==,type:comment]
|
||||
apiVersion: ENC[AES256_GCM,data:Vrs=,iv:0TzPcIoozs2MXJNXkzgcVtjjBUgfOHaSXQZiD37fb+Q=,tag:CabTlLwtz6RcBF/gr4Ri4g==,type:str]
|
||||
kind: ENC[AES256_GCM,data:22Y5w+Df,iv:Mf2s3h8++Vxqb4JoymHXY4/WAknDZ2GGrVVtKK51JxI=,tag:k5DHUqWGMBPpLkQXADaMlw==,type:str]
|
||||
metadata:
|
||||
name: ENC[AES256_GCM,data:ItXKquY5a7gu8CJZog==,iv:jxbj7Qtv+DRbhzTdvtv+eJuTQPNIf497NZPYA6ld4s0=,tag:XdaaSavFBxOjvxna2kr/Tg==,type:str]
|
||||
namespace: ENC[AES256_GCM,data:VH6NMg==,iv:4PfWZu5qVGXP3ZzRHMrh5N9dzJ3SoUPPo58ppcDTnpk=,tag:Vr8E+WF0Z9ym3GyTGbdi2g==,type:str]
|
||||
type: ENC[AES256_GCM,data:DmbHZRIk,iv:EZHnf1h1L29G1HOBYBSBeydNe4nC8XiBOw8YEL3kxrY=,tag:pFhUSMAIEqJET3NmaajO1g==,type:str]
|
||||
stringData:
|
||||
username: ENC[AES256_GCM,data:ZTrmFA==,iv:1+tLTAxrDitXJwCAEccaVQzc9I9lNRgT3FsxO2NPDDc=,tag:69Wn4a8pjhdUycc/MXsn3Q==,type:str]
|
||||
password: ENC[AES256_GCM,data:w7Vn8XaC1ykNrwPpJjVYg8J5KXkUbaPspu0CoceqHVdai6BFNW5rtA==,iv:jAwDFvJfQ1GkeU/qpEVUAQ6cWqxYE8nrgs+/RouyUxg=,tag:foGmYika/9uNBdsKVrspEA==,type:str]
|
||||
email: ENC[AES256_GCM,data:pIOq80OjVERFCXJmx8+qTJEw,iv:mfkMj3u8W2ZX4N4IH39mZXfEp+xphS4shaJcFOD/LEE=,tag:FOSq0E7VzzOM69Bn77v2Dw==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwSi84U3FQZXJrYzA0WCtS
|
||||
c1g1Sm04eFhha2huSEVCd2x3OGVUT08yejFjCmpPV0NsTVRyWTRVUkx6WktKaU1k
|
||||
Vk12VGRIejRWbjQxeDZxdFUxWkgzaVUKLS0tIDRZdFNxY1AvcFhOREMxU01zTzhX
|
||||
RzFmMjdjOFd4ZkxscnBIa1E2NWtRaVEK7qZXtq2VwZBwsLAulRh93TpCXCos0Vu7
|
||||
fX+/oMEN6gF5VxDJ/e5C644EKUY+tSLAoh75xA5DAytOxEVdhe2ozA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1smu533f803gmd0jq60s2zaj9zlznajy0ca6rtewd4r37mr2hs3uqsrldfh
|
||||
lastmodified: "2026-07-25T17:45:22Z"
|
||||
mac: ENC[AES256_GCM,data:gi8smimG00EQbF492BVzsWOEubGdT0zSG39D+r3fgLL/wgtDqF2mgtFA0iDiy1pInt1Viu8yYpu1l/eBSD8BNW4bfrglIHOJdb1I5+19u8SMebhWCwbEcbiHaXNmVUKKapGsNjbApCApq00NXoOZuOUSUzZlh19JPvg/vd7nl2M=,iv:n8Kni4RQhj/VgIruqptVa6m0YwFJF6v2+p2boJsLT5c=,tag:Tc4gkhH7hSrJKEOJ3X3SjQ==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.2
|
||||
@@ -1,11 +1,22 @@
|
||||
# Forgejo Helm Values — Single Source of Truth
|
||||
# Chart: https://codeberg.org/forgejo-contrib/forgejo-helm
|
||||
|
||||
# Disable bundled dependencies (use external CNPG + Redis instead)
|
||||
postgresql-ha:
|
||||
enabled: false
|
||||
|
||||
valkey:
|
||||
enabled: false
|
||||
|
||||
valkey-cluster:
|
||||
enabled: false
|
||||
|
||||
redis:
|
||||
enabled: false
|
||||
|
||||
gitea:
|
||||
admin:
|
||||
username: "admin"
|
||||
email: "[email protected]"
|
||||
# Password set via secret (not in values)
|
||||
existingSecret: forgejo-admin
|
||||
|
||||
config:
|
||||
server:
|
||||
@@ -18,17 +29,7 @@ gitea:
|
||||
DB_TYPE: postgres
|
||||
HOST: forgejo-db-rw.cicd.svc.cluster.local:5432
|
||||
NAME: forgejo
|
||||
# User/password from CNPG-generated secret
|
||||
USER:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db-app
|
||||
key: username
|
||||
PASSWD:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db-app
|
||||
key: password
|
||||
# User/password injected via extraEnv (secretKeyRef doesn't work in config)
|
||||
|
||||
cache:
|
||||
ADAPTER: redis
|
||||
@@ -84,3 +85,17 @@ tolerations:
|
||||
# ArgoCD adoption labels
|
||||
labels:
|
||||
argocd.argoproj.io/instance: forgejo
|
||||
|
||||
# Inject database credentials via environment variables (overrides app.ini)
|
||||
deployment:
|
||||
env:
|
||||
- name: GITEA__DATABASE__USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db-app
|
||||
key: username
|
||||
- name: GITEA__DATABASE__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db-app
|
||||
key: password
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cicd
|
||||
labels:
|
||||
# Baseline allows most workloads while blocking clearly dangerous configurations
|
||||
# Redis needs some relaxed settings but doesn't need full privileged access
|
||||
pod-security.kubernetes.io/enforce: baseline
|
||||
pod-security.kubernetes.io/audit: baseline
|
||||
pod-security.kubernetes.io/warn: baseline
|
||||
@@ -0,0 +1,34 @@
|
||||
# ArgoCD CMP plugin for SOPS secret decryption
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cmp-cm
|
||||
namespace: argocd
|
||||
data:
|
||||
sops-secrets-v1.0.yaml: |
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: sops-secrets-v1.0
|
||||
spec:
|
||||
version: v1.0
|
||||
init:
|
||||
command: [sh, -c]
|
||||
args:
|
||||
- |
|
||||
# Install sops if not present
|
||||
if ! command -v sops &> /dev/null; then
|
||||
wget -qO- https://github.com/getsops/sops/releases/download/v3.9.3/sops-v3.9.3.linux.amd64 > /usr/local/bin/sops
|
||||
chmod +x /usr/local/bin/sops
|
||||
fi
|
||||
generate:
|
||||
command: [sh, -c]
|
||||
args:
|
||||
- |
|
||||
# Find all .enc.yaml files and decrypt them
|
||||
find . -name '*.enc.yaml' -type f | while read -r file; do
|
||||
sops -d "$file"
|
||||
done
|
||||
discover:
|
||||
find:
|
||||
glob: "**/*.enc.yaml"
|
||||
@@ -1,4 +1,4 @@
|
||||
# ArgoCD Helm Values — Single Source of Truth
|
||||
# ArgoCD Helm Values — Bootstrap Mode (SOPS plugin added post-bootstrap)
|
||||
# Chart: https://github.com/argoproj/argo-helm
|
||||
|
||||
global:
|
||||
@@ -32,6 +32,12 @@ server:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
|
||||
# Tolerations for control-plane
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
# Repo server configuration
|
||||
repoServer:
|
||||
resources:
|
||||
@@ -42,21 +48,11 @@ repoServer:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
|
||||
# SOPS plugin for encrypted secrets
|
||||
volumes:
|
||||
- name: sops-age
|
||||
secret:
|
||||
secretName: sops-age
|
||||
optional: true
|
||||
volumeMounts:
|
||||
- name: sops-age
|
||||
mountPath: /home/argocd/.config/sops/age
|
||||
readOnly: true
|
||||
|
||||
# Environment for SOPS
|
||||
env:
|
||||
- name: SOPS_AGE_KEY_FILE
|
||||
value: /home/argocd/.config/sops/age/keys.txt
|
||||
# Tolerations for control-plane
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
# Controller configuration
|
||||
controller:
|
||||
@@ -68,6 +64,12 @@ controller:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
|
||||
# Tolerations for control-plane
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
# Application controller configuration
|
||||
applicationSet:
|
||||
enabled: true
|
||||
@@ -87,26 +89,7 @@ redis:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
|
||||
# Tolerations for control-plane
|
||||
server:
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
repoServer:
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
controller:
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
# ArgoCD projects
|
||||
# ArgoCD configuration
|
||||
configs:
|
||||
# Default project allows all repos
|
||||
cm:
|
||||
@@ -117,7 +100,6 @@ configs:
|
||||
server.insecure: true
|
||||
|
||||
# RBAC (allow admin full access)
|
||||
configs:
|
||||
rbac:
|
||||
policy.default: role:readonly
|
||||
policy.csv: |
|
||||
|
||||
+2
-11
@@ -11,7 +11,8 @@ locals {
|
||||
kube_proxy_img = "registry.k8s.io/kube-proxy:${var.kubernetes_version}"
|
||||
scheduler_img = "registry.k8s.io/kube-scheduler:${var.kubernetes_version}"
|
||||
|
||||
factory_image = "factory.talos.dev/installer/613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245:${var.talos_version}"
|
||||
# Use Image Factory generated URL with Longhorn extensions (iscsi-tools, util-linux-tools)
|
||||
factory_image = data.talos_image_factory_urls.longhorn_installer.urls.installer
|
||||
}
|
||||
|
||||
# Control plane machine configurations
|
||||
@@ -29,16 +30,6 @@ resource "local_file" "controlplane_configs" {
|
||||
lan_ip = each.value.lan_ip
|
||||
lan_subnet = each.value.lan_subnet
|
||||
lan_gateway = each.value.lan_gateway
|
||||
wg0_ip = each.value.wg0_ip
|
||||
wg0_subnet = each.value.wg0_subnet
|
||||
wg0_port = each.value.wg0_port
|
||||
wg0_private_key = each.value.wg0_private_key
|
||||
wg0_peers = each.value.wg0_peers
|
||||
wg1_ip = each.value.wg1_ip
|
||||
wg1_subnet = each.value.wg1_subnet
|
||||
wg1_port = each.value.wg1_port
|
||||
wg1_private_key = each.value.wg1_private_key
|
||||
wg1_peers = each.value.wg1_peers
|
||||
kubelet_image = local.kubelet_image
|
||||
cluster_dns_ip = local.cluster_dns_ip
|
||||
install_disk = each.value.install_disk
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Talos Image Factory schematic with Longhorn dependencies
|
||||
resource "talos_image_factory_schematic" "longhorn" {
|
||||
schematic = jsonencode({
|
||||
customization = {
|
||||
systemExtensions = {
|
||||
officialExtensions = [
|
||||
"siderolabs/iscsi-tools",
|
||||
"siderolabs/util-linux-tools",
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
# Generate installer image URL for the schematic
|
||||
data "talos_image_factory_urls" "longhorn_installer" {
|
||||
talos_version = var.talos_version
|
||||
schematic_id = talos_image_factory_schematic.longhorn.id
|
||||
platform = "metal"
|
||||
}
|
||||
|
||||
# Output the schematic ID and installer URL for reference
|
||||
output "talos_schematic_id" {
|
||||
value = talos_image_factory_schematic.longhorn.id
|
||||
description = "Talos Image Factory schematic ID with Longhorn dependencies"
|
||||
}
|
||||
|
||||
output "talos_installer_url" {
|
||||
value = data.talos_image_factory_urls.longhorn_installer.urls.installer
|
||||
description = "Talos installer image URL with iscsi-tools and util-linux-tools"
|
||||
}
|
||||
@@ -10,9 +10,6 @@ machine:
|
||||
key: ${ca_key}
|
||||
certSANs:
|
||||
- ${lan_ip}
|
||||
%{ if wg0_ip != null ~}
|
||||
- ${wg0_ip}
|
||||
%{ endif ~}
|
||||
%{ for san in cloudflare_talos_sans ~}
|
||||
- ${san}
|
||||
%{ endfor ~}
|
||||
@@ -28,39 +25,6 @@ machine:
|
||||
dhcp: false
|
||||
dhcpOptions:
|
||||
ipv6: false
|
||||
%{ if wg0_ip != null ~}
|
||||
- interface: wg0
|
||||
addresses:
|
||||
- ${wg0_ip}/24
|
||||
wireguard:
|
||||
privateKey: "${wg0_private_key}"
|
||||
listenPort: ${wg0_port}
|
||||
peers:
|
||||
%{ for peer in wg0_peers ~}
|
||||
- publicKey: "${peer.public_key}"
|
||||
allowedIPs:
|
||||
%{ for ip in peer.allowed_ips ~}
|
||||
- ${ip}
|
||||
%{ endfor ~}
|
||||
%{ endfor ~}
|
||||
%{ endif ~}
|
||||
%{ if wg1_ip != null ~}
|
||||
- interface: wg1
|
||||
addresses:
|
||||
- ${wg1_ip}/24
|
||||
wireguard:
|
||||
privateKey: "${wg1_private_key}"
|
||||
listenPort: ${wg1_port}
|
||||
peers:
|
||||
%{ for peer in wg1_peers ~}
|
||||
- publicKey: "${peer.public_key}"
|
||||
allowedIPs:
|
||||
%{ for ip in peer.allowed_ips ~}
|
||||
- ${ip}
|
||||
%{ endfor ~}
|
||||
persistentKeepaliveInterval: ${peer.persistent_keepalive_secs}s
|
||||
%{ endfor ~}
|
||||
%{ endif ~}
|
||||
nameservers:
|
||||
%{ for ns in dns_servers ~}
|
||||
- ${ns}
|
||||
@@ -104,8 +68,7 @@ machine:
|
||||
topology.kubernetes.io/zone: ${zone}
|
||||
%{ if !allow_scheduling ~}
|
||||
# Dedicated control plane — re-apply the control-plane taint that
|
||||
# allowSchedulingOnControlPlanes=true removed cluster-wide. Only nodes with
|
||||
# allow_scheduling=true (talos-cp-1 / .213) stay schedulable.
|
||||
# allowSchedulingOnControlPlanes=true removed cluster-wide.
|
||||
nodeTaints:
|
||||
node-role.kubernetes.io/control-plane: ":NoSchedule"
|
||||
%{ endif ~}
|
||||
@@ -144,9 +107,6 @@ cluster:
|
||||
apiServer:
|
||||
certSANs:
|
||||
- ${controlplane_ip}
|
||||
%{ if wg0_ip != null ~}
|
||||
- ${wg0_ip}
|
||||
%{ endif ~}
|
||||
- ${lan_ip}
|
||||
%{ for san in cloudflare_apiserver_sans ~}
|
||||
- ${san}
|
||||
@@ -188,9 +148,7 @@ cluster:
|
||||
disabled: true
|
||||
service: {}
|
||||
etcd:
|
||||
# Advertise/peer etcd on the LAN so all control planes can reach each other.
|
||||
# Without this, Talos may pick the WireGuard IP (10.6.0.1), which the
|
||||
# LAN-only control planes can't route to — new members get stuck as learners.
|
||||
# Pin etcd peer/advertise addresses to the LAN subnet explicitly.
|
||||
advertisedSubnets:
|
||||
- 192.168.1.0/24
|
||||
ca:
|
||||
|
||||
@@ -106,23 +106,6 @@ variable "controlplane_configs" {
|
||||
lan_ip = string
|
||||
lan_subnet = string
|
||||
lan_gateway = string
|
||||
wg0_ip = optional(string)
|
||||
wg0_subnet = optional(string)
|
||||
wg0_port = optional(number)
|
||||
wg0_private_key = optional(string)
|
||||
wg0_peers = optional(list(object({
|
||||
public_key = string
|
||||
allowed_ips = list(string)
|
||||
})), [])
|
||||
wg1_ip = optional(string)
|
||||
wg1_subnet = optional(string)
|
||||
wg1_port = optional(number)
|
||||
wg1_private_key = optional(string)
|
||||
wg1_peers = optional(list(object({
|
||||
public_key = string
|
||||
allowed_ips = list(string)
|
||||
persistent_keepalive_secs = number
|
||||
})), [])
|
||||
install_disk = string
|
||||
longhorn_disks = list(object({
|
||||
device = string
|
||||
|
||||
Reference in New Issue
Block a user