fix(bootstrap): complete Phase 4 ArgoCD bootstrap with all permanent fixes
- Fix ArgoCD Application schema: move syncOptions under syncPolicy (00-secrets.yaml) - Remove helm install --wait flag (talos-cp-2 slow node timeout issue) - Add comprehensive progress logging with timestamps to bootstrap.sh - Fix SOPS key path (/Users/rockliang/.sops/key.txt, not homelab-age.key) - Add local SOPS decryption for bootstrap secrets - Add CNPG NetworkPolicy allowing app→database connectivity - Disable Forgejo bundled dependencies (saves 66Gi storage) - Inject database credentials via deployment.env (GITEA__DATABASE__*) - Remove invalid ext4 mount options from StorageClass - Add namespace manifests with PodSecurity labels - Add encrypted forgejo-admin secret (SOPS) - Reduce forgejo-db size 50Gi→25Gi per instance - Prepare ArgoCD SOPS CMP plugin (for post-bootstrap)
This commit is contained in:
+155
-33
@@ -19,7 +19,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
BOOT="$SCRIPT_DIR/k8s/bootstrap"
|
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}"
|
DEPLOY_KEY="${DEPLOY_KEY:-$HOME/.ssh/argocd_seed}"
|
||||||
GITHUB_SSH="[email protected]:Riotpiaole/riotpiao.homelab.com.git"
|
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; }
|
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||||
phase(){ echo; echo "━━━ $* ━━━"; echo; }
|
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() {
|
preflight() {
|
||||||
log "preflight…"
|
log "preflight…"
|
||||||
kubectl cluster-info >/dev/null || die "kubectl not configured / cluster unreachable"
|
kubectl cluster-info >/dev/null || die "kubectl not configured / cluster unreachable"
|
||||||
@@ -39,7 +46,7 @@ preflight() {
|
|||||||
p1_cilium() {
|
p1_cilium() {
|
||||||
phase "PHASE 1a: CNI (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
|
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 \
|
helm install cilium cilium/cilium -n kube-system \
|
||||||
--set ipam.mode=kubernetes --set kubeProxyReplacement=true --wait --timeout 10m
|
--set ipam.mode=kubernetes --set kubeProxyReplacement=true --wait --timeout 10m
|
||||||
log "✅ cilium installed"
|
log "✅ cilium installed"
|
||||||
@@ -47,60 +54,175 @@ p1_cilium() {
|
|||||||
|
|
||||||
p1_longhorn() {
|
p1_longhorn() {
|
||||||
phase "PHASE 1b: STORAGE (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
|
# Always ensure namespace + StorageClasses (idempotent, resumable)
|
||||||
helm install longhorn longhorn/longhorn -n longhorn-system --create-namespace \
|
kubectl apply -f "$BOOT/phase1-storage/namespace.yaml"
|
||||||
--values "$BOOT/phase1-storage/longhorn-values.yaml" --wait --timeout 10m
|
|
||||||
kubectl -n longhorn-system wait --for=condition=available --timeout=300s deploy/longhorn-manager
|
# 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"
|
kubectl apply -f "$BOOT/phase1-storage/storageclasses.yaml"
|
||||||
log "✅ longhorn installed"
|
|
||||||
|
# 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() {
|
p2_cnpg() {
|
||||||
phase "PHASE 2: CNPG OPERATOR"
|
phase "PHASE 2: CNPG OPERATOR"
|
||||||
if helm -n cnpg-system list 2>/dev/null | grep -q cnpg; then log "cnpg present, skip"; return; fi
|
if kubectl get crd clusters.postgresql.cnpg.io >/dev/null 2>&1; then log "cnpg CRD present, skip install"; return; fi
|
||||||
helm repo add cnpg https://cloudnative-pg.github.io/charts >/dev/null; helm repo update >/dev/null
|
ensure_helm_repo cnpg https://cloudnative-pg.github.io/charts
|
||||||
helm install cnpg cnpg/cloudnative-pg -n cnpg-system --create-namespace \
|
log "Installing CloudNativePG operator (this may take 2-3 minutes)..."
|
||||||
--values "$BOOT/phase2-cnpg/cnpg-values.yaml" --wait --timeout 5m
|
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"
|
kubectl get crd clusters.postgresql.cnpg.io >/dev/null || die "CNPG CRD not registered"
|
||||||
log "✅ cnpg operator installed"
|
log "✅ cnpg operator installed"
|
||||||
}
|
}
|
||||||
|
|
||||||
p3_forgejo() {
|
p3_forgejo() {
|
||||||
phase "PHASE 3: forgejo-db + Forgejo (ns cicd)"
|
phase "PHASE 3: forgejo-db + Forgejo (ns cicd)"
|
||||||
kubectl create ns cicd --dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
kubectl apply -f "$BOOT/phase3-forgejo/forgejo-db.yaml"
|
# Always ensure namespace + NetworkPolicy + Secrets (idempotent)
|
||||||
log "waiting for forgejo-db Ready (3-5 min)…"
|
kubectl apply -f "$BOOT/phase3-forgejo/namespace.yaml"
|
||||||
kubectl wait --for=condition=Ready --timeout=600s cluster/forgejo-db -n cicd
|
|
||||||
|
# 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 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"
|
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 || \
|
# Install Forgejo if not present
|
||||||
helm repo add forgejo https://dl.gitea.io/charts/ >/dev/null
|
if helm -n cicd list 2>/dev/null | grep -q forgejo; then log "forgejo helm release present, skip"; return; fi
|
||||||
helm repo update >/dev/null
|
ensure_helm_repo forgejo https://dl.gitea.io/charts/
|
||||||
helm install forgejo forgejo/forgejo -n cicd \
|
log "Installing Forgejo (this may take 10-15 minutes on slow nodes)..."
|
||||||
--values "$BOOT/phase3-forgejo/forgejo-values.yaml" --wait --timeout 10m
|
if helm install forgejo forgejo/gitea -n cicd \
|
||||||
log "✅ forgejo up — now push this repo to Forgejo and configure the GitHub pull-mirror"
|
--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() {
|
p4_argocd() {
|
||||||
phase "PHASE 4: ArgoCD (seeded from GitHub)"
|
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 -
|
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 \
|
kubectl -n argocd create secret generic seed-github-repo \
|
||||||
--from-literal=type=git --from-literal=url="$GITHUB_SSH" \
|
--from-literal=type=git --from-literal=url="$GITHUB_SSH" \
|
||||||
--from-file=sshPrivateKey="$DEPLOY_KEY" --dry-run=client -o yaml | kubectl apply -f -
|
--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
|
kubectl -n argocd label secret seed-github-repo argocd.argoproj.io/secret-type=repository --overwrite 2>/dev/null || true
|
||||||
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
|
# Decrypt and apply any encrypted secrets from bootstrap dir (local SOPS)
|
||||||
helm install argocd argo/argo-cd -n argocd \
|
if command -v sops &> /dev/null; then
|
||||||
--values "$BOOT/phase4-argocd/argocd-values.yaml" --wait --timeout 10m
|
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
|
||||||
|
done
|
||||||
|
log "Decrypted and applied $decrypted_count secret(s)"
|
||||||
|
else
|
||||||
|
log "⚠️ SOPS not installed, skipping encrypted secret decryption"
|
||||||
fi
|
fi
|
||||||
kubectl -n argocd wait --for=condition=available --timeout=300s deploy/argocd-server
|
|
||||||
|
# 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"
|
kubectl apply -f "$BOOT/phase4-argocd/root-app-github.yaml"
|
||||||
log "✅ ArgoCD syncing from GitHub seed. Watch: kubectl get applications -n argocd"
|
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() {
|
p5_cutover() {
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ spec:
|
|||||||
automated:
|
automated:
|
||||||
prune: true
|
prune: true
|
||||||
selfHeal: true
|
selfHeal: true
|
||||||
syncOptions:
|
syncOptions:
|
||||||
- CreateNamespace=true
|
- CreateNamespace=true
|
||||||
source:
|
source:
|
||||||
repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||||
targetRevision: main
|
targetRevision: main
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
# Longhorn Helm Values — Single Source of Truth
|
# Longhorn Helm Values — Single Source of Truth
|
||||||
# Used by both bootstrap.sh (Helm install) and ArgoCD (adoption)
|
# Used by both bootstrap.sh (Helm install) and ArgoCD (adoption)
|
||||||
# Chart: https://github.com/longhorn/charts
|
# 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:
|
defaultSettings:
|
||||||
# 3-node HA configuration
|
# 3-node HA configuration
|
||||||
@@ -52,6 +56,8 @@ longhornUI:
|
|||||||
replicas: 1
|
replicas: 1
|
||||||
|
|
||||||
# Monitoring (Prometheus ServiceMonitor)
|
# Monitoring (Prometheus ServiceMonitor)
|
||||||
|
# Disabled during bootstrap (Prometheus CRDs not installed yet)
|
||||||
|
# Re-enable via ArgoCD after Prometheus stack is deployed
|
||||||
metrics:
|
metrics:
|
||||||
serviceMonitor:
|
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
|
# The default 'longhorn' SC is created by Helm chart
|
||||||
# These are additional specialized classes
|
# These are additional specialized classes
|
||||||
---
|
---
|
||||||
# CNPG-specific StorageClass with postgres UID/GID mount options
|
# CNPG-specific StorageClass for PostgreSQL volumes
|
||||||
# Fixes "read-only filesystem" error when PostgreSQL (UID 26) tries to write
|
# CNPG handles filesystem ownership via securityContext.fsGroup (UID/GID 26)
|
||||||
|
# Separate from default 'longhorn' to allow CNPG-specific tuning
|
||||||
apiVersion: storage.k8s.io/v1
|
apiVersion: storage.k8s.io/v1
|
||||||
kind: StorageClass
|
kind: StorageClass
|
||||||
metadata:
|
metadata:
|
||||||
@@ -19,9 +20,7 @@ parameters:
|
|||||||
fromBackup: ""
|
fromBackup: ""
|
||||||
dataLocality: "best-effort"
|
dataLocality: "best-effort"
|
||||||
fsType: "ext4"
|
fsType: "ext4"
|
||||||
mountOptions:
|
# mountOptions removed - uid/gid are NOT valid for ext4 (only for FAT/VFAT)
|
||||||
- "noatime"
|
# CNPG handles ownership via securityContext.fsGroup automatically
|
||||||
- "uid=26" # postgres user
|
|
||||||
- "gid=26" # postgres group
|
|
||||||
reclaimPolicy: Delete
|
reclaimPolicy: Delete
|
||||||
volumeBindingMode: Immediate
|
volumeBindingMode: Immediate
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ webhook:
|
|||||||
failurePolicy: Fail
|
failurePolicy: Fail
|
||||||
|
|
||||||
# Monitoring
|
# Monitoring
|
||||||
|
# PodMonitor disabled during bootstrap (Prometheus CRDs not installed yet)
|
||||||
|
# Re-enable via ArgoCD after Prometheus stack is deployed
|
||||||
monitoring:
|
monitoring:
|
||||||
podMonitorEnabled: true
|
podMonitorEnabled: false
|
||||||
grafanaDashboard:
|
grafanaDashboard:
|
||||||
create: false # We'll manage dashboards via ArgoCD later
|
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"
|
log_filename: "postgres"
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
size: 50Gi
|
size: 25Gi
|
||||||
storageClass: longhorn-cnpg # Uses postgres UID/GID mount options
|
storageClass: longhorn-cnpg
|
||||||
|
|
||||||
monitoring:
|
monitoring:
|
||||||
enablePodMonitor: true
|
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
|
# Forgejo Helm Values — Single Source of Truth
|
||||||
# Chart: https://codeberg.org/forgejo-contrib/forgejo-helm
|
# 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:
|
gitea:
|
||||||
admin:
|
admin:
|
||||||
username: "admin"
|
existingSecret: forgejo-admin
|
||||||
email: "[email protected]"
|
|
||||||
# Password set via secret (not in values)
|
|
||||||
|
|
||||||
config:
|
config:
|
||||||
server:
|
server:
|
||||||
@@ -18,17 +29,7 @@ gitea:
|
|||||||
DB_TYPE: postgres
|
DB_TYPE: postgres
|
||||||
HOST: forgejo-db-rw.cicd.svc.cluster.local:5432
|
HOST: forgejo-db-rw.cicd.svc.cluster.local:5432
|
||||||
NAME: forgejo
|
NAME: forgejo
|
||||||
# User/password from CNPG-generated secret
|
# User/password injected via extraEnv (secretKeyRef doesn't work in config)
|
||||||
USER:
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: forgejo-db-app
|
|
||||||
key: username
|
|
||||||
PASSWD:
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: forgejo-db-app
|
|
||||||
key: password
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
ADAPTER: redis
|
ADAPTER: redis
|
||||||
@@ -84,3 +85,17 @@ tolerations:
|
|||||||
# ArgoCD adoption labels
|
# ArgoCD adoption labels
|
||||||
labels:
|
labels:
|
||||||
argocd.argoproj.io/instance: forgejo
|
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
|
# Chart: https://github.com/argoproj/argo-helm
|
||||||
|
|
||||||
global:
|
global:
|
||||||
@@ -32,6 +32,12 @@ server:
|
|||||||
cpu: 500m
|
cpu: 500m
|
||||||
memory: 1Gi
|
memory: 1Gi
|
||||||
|
|
||||||
|
# Tolerations for control-plane
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
|
||||||
# Repo server configuration
|
# Repo server configuration
|
||||||
repoServer:
|
repoServer:
|
||||||
resources:
|
resources:
|
||||||
@@ -42,21 +48,11 @@ repoServer:
|
|||||||
cpu: 500m
|
cpu: 500m
|
||||||
memory: 1Gi
|
memory: 1Gi
|
||||||
|
|
||||||
# SOPS plugin for encrypted secrets
|
# Tolerations for control-plane
|
||||||
volumes:
|
tolerations:
|
||||||
- name: sops-age
|
- key: node-role.kubernetes.io/control-plane
|
||||||
secret:
|
operator: Exists
|
||||||
secretName: sops-age
|
effect: NoSchedule
|
||||||
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
|
|
||||||
|
|
||||||
# Controller configuration
|
# Controller configuration
|
||||||
controller:
|
controller:
|
||||||
@@ -68,6 +64,12 @@ controller:
|
|||||||
cpu: 1000m
|
cpu: 1000m
|
||||||
memory: 2Gi
|
memory: 2Gi
|
||||||
|
|
||||||
|
# Tolerations for control-plane
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
|
||||||
# Application controller configuration
|
# Application controller configuration
|
||||||
applicationSet:
|
applicationSet:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -87,26 +89,7 @@ redis:
|
|||||||
cpu: 200m
|
cpu: 200m
|
||||||
memory: 256Mi
|
memory: 256Mi
|
||||||
|
|
||||||
# Tolerations for control-plane
|
# ArgoCD configuration
|
||||||
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
|
|
||||||
configs:
|
configs:
|
||||||
# Default project allows all repos
|
# Default project allows all repos
|
||||||
cm:
|
cm:
|
||||||
@@ -116,8 +99,7 @@ configs:
|
|||||||
params:
|
params:
|
||||||
server.insecure: true
|
server.insecure: true
|
||||||
|
|
||||||
# RBAC (allow admin full access)
|
# RBAC (allow admin full access)
|
||||||
configs:
|
|
||||||
rbac:
|
rbac:
|
||||||
policy.default: role:readonly
|
policy.default: role:readonly
|
||||||
policy.csv: |
|
policy.csv: |
|
||||||
|
|||||||
Reference in New Issue
Block a user