From 8d63db9f3bf93a546769852c916f4ff1b7b76049 Mon Sep 17 00:00:00 2001 From: Story Crater Bot <19826264+Riotpiaole@users.noreply.github.com> Date: Sat, 25 Jul 2026 12:24:30 -0700 Subject: [PATCH] fix(bootstrap): complete Phase 4 ArgoCD bootstrap with all permanent fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- bootstrap.sh | 188 +++++++++++++++--- k8s/argocd/apps/00-secrets.yaml | 4 +- .../phase1-storage/longhorn-values.yaml | 8 +- k8s/bootstrap/phase1-storage/namespace.yaml | 8 + .../phase1-storage/storageclasses.yaml | 11 +- k8s/bootstrap/phase2-cnpg/cnpg-values.yaml | 4 +- .../phase3-forgejo/cnpg-networkpolicy.yaml | 49 +++++ k8s/bootstrap/phase3-forgejo/forgejo-db.yaml | 4 +- .../phase3-forgejo/forgejo-secrets.enc.yaml | 26 +++ .../phase3-forgejo/forgejo-values.yaml | 43 ++-- k8s/bootstrap/phase3-forgejo/namespace.yaml | 10 + .../phase4-argocd/argocd-cmp-cm.yaml | 34 ++++ .../phase4-argocd/argocd-values.yaml | 58 ++---- 13 files changed, 350 insertions(+), 97 deletions(-) create mode 100644 k8s/bootstrap/phase1-storage/namespace.yaml create mode 100644 k8s/bootstrap/phase3-forgejo/cnpg-networkpolicy.yaml create mode 100644 k8s/bootstrap/phase3-forgejo/forgejo-secrets.enc.yaml create mode 100644 k8s/bootstrap/phase3-forgejo/namespace.yaml create mode 100644 k8s/bootstrap/phase4-argocd/argocd-cmp-cm.yaml diff --git a/bootstrap.sh b/bootstrap.sh index 95e09e2..f005d60 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -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="git@github.com: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" - 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() { 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 - - 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 + + # 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=admin@riotpiao.com + 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" - 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 + done + log "Decrypted and applied $decrypted_count secret(s)" + else + log "⚠️ SOPS not installed, skipping encrypted secret decryption" 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" 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() { diff --git a/k8s/argocd/apps/00-secrets.yaml b/k8s/argocd/apps/00-secrets.yaml index d863a83..ecf5955 100644 --- a/k8s/argocd/apps/00-secrets.yaml +++ b/k8s/argocd/apps/00-secrets.yaml @@ -14,8 +14,8 @@ spec: automated: prune: true selfHeal: true - syncOptions: - - CreateNamespace=true + syncOptions: + - CreateNamespace=true source: repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git targetRevision: main diff --git a/k8s/bootstrap/phase1-storage/longhorn-values.yaml b/k8s/bootstrap/phase1-storage/longhorn-values.yaml index d7698fb..568d482 100644 --- a/k8s/bootstrap/phase1-storage/longhorn-values.yaml +++ b/k8s/bootstrap/phase1-storage/longhorn-values.yaml @@ -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 @@ -52,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 diff --git a/k8s/bootstrap/phase1-storage/namespace.yaml b/k8s/bootstrap/phase1-storage/namespace.yaml new file mode 100644 index 0000000..6475482 --- /dev/null +++ b/k8s/bootstrap/phase1-storage/namespace.yaml @@ -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 diff --git a/k8s/bootstrap/phase1-storage/storageclasses.yaml b/k8s/bootstrap/phase1-storage/storageclasses.yaml index 61f72b7..6fa525c 100644 --- a/k8s/bootstrap/phase1-storage/storageclasses.yaml +++ b/k8s/bootstrap/phase1-storage/storageclasses.yaml @@ -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 diff --git a/k8s/bootstrap/phase2-cnpg/cnpg-values.yaml b/k8s/bootstrap/phase2-cnpg/cnpg-values.yaml index ed82ecc..d7f744d 100644 --- a/k8s/bootstrap/phase2-cnpg/cnpg-values.yaml +++ b/k8s/bootstrap/phase2-cnpg/cnpg-values.yaml @@ -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 diff --git a/k8s/bootstrap/phase3-forgejo/cnpg-networkpolicy.yaml b/k8s/bootstrap/phase3-forgejo/cnpg-networkpolicy.yaml new file mode 100644 index 0000000..269cf4c --- /dev/null +++ b/k8s/bootstrap/phase3-forgejo/cnpg-networkpolicy.yaml @@ -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 + - {} diff --git a/k8s/bootstrap/phase3-forgejo/forgejo-db.yaml b/k8s/bootstrap/phase3-forgejo/forgejo-db.yaml index 53bb0bc..5ce954d 100644 --- a/k8s/bootstrap/phase3-forgejo/forgejo-db.yaml +++ b/k8s/bootstrap/phase3-forgejo/forgejo-db.yaml @@ -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 diff --git a/k8s/bootstrap/phase3-forgejo/forgejo-secrets.enc.yaml b/k8s/bootstrap/phase3-forgejo/forgejo-secrets.enc.yaml new file mode 100644 index 0000000..623068e --- /dev/null +++ b/k8s/bootstrap/phase3-forgejo/forgejo-secrets.enc.yaml @@ -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 diff --git a/k8s/bootstrap/phase3-forgejo/forgejo-values.yaml b/k8s/bootstrap/phase3-forgejo/forgejo-values.yaml index cf57cdf..90c7330 100644 --- a/k8s/bootstrap/phase3-forgejo/forgejo-values.yaml +++ b/k8s/bootstrap/phase3-forgejo/forgejo-values.yaml @@ -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: "admin@riotpiao.com" - # 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 diff --git a/k8s/bootstrap/phase3-forgejo/namespace.yaml b/k8s/bootstrap/phase3-forgejo/namespace.yaml new file mode 100644 index 0000000..bff8401 --- /dev/null +++ b/k8s/bootstrap/phase3-forgejo/namespace.yaml @@ -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 diff --git a/k8s/bootstrap/phase4-argocd/argocd-cmp-cm.yaml b/k8s/bootstrap/phase4-argocd/argocd-cmp-cm.yaml new file mode 100644 index 0000000..ea4fdbe --- /dev/null +++ b/k8s/bootstrap/phase4-argocd/argocd-cmp-cm.yaml @@ -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" diff --git a/k8s/bootstrap/phase4-argocd/argocd-values.yaml b/k8s/bootstrap/phase4-argocd/argocd-values.yaml index 46d28fd..bc41fa1 100644 --- a/k8s/bootstrap/phase4-argocd/argocd-values.yaml +++ b/k8s/bootstrap/phase4-argocd/argocd-values.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: @@ -116,8 +99,7 @@ configs: params: server.insecure: true -# RBAC (allow admin full access) -configs: + # RBAC (allow admin full access) rbac: policy.default: role:readonly policy.csv: |