Phase 0 groundwork for helmfile→ArgoCD migration: 1. Remove 3 bootstrap releases from helmfile (cert-manager, reloader, ingress-nginx) — already managed by terraform/bootstrap-releases.tf; eliminates dual-ownership 2. Bootstrap ESO (External Secrets Operator) as TF-managed release — required for all ExternalSecret resources in phases 1-3 — added to bootstrap-releases.tf + helm-repositories.tf 3. Create ClusterSecretStore connecting ESO to Vault (K8s auth) — enables per-namespace/per-release secret injection — vault config documented in docs/PHASE0-ESO-VAULT-SETUP.md (manual setup) 4. Fix argocd-bootstrap.tf CA cert copy: use jq instead of sed for cleaner metadata handling Changes: - helmfile.yaml.gotmpl: remove cert-manager/reloader/ingress-nginx blocks - terraform/bootstrap-releases.tf: add external-secrets release - terraform/helm-repositories.tf: add external-secrets Helm repo - k8s/external-secrets/clustersecretstore.yaml: ESO→Vault ClusterSecretStore - k8s/argocd/apps/0-wave-0.yaml: stub wave 0 applications (schema fix, rewrite pending Phase 1) - docs/PHASE0-ESO-VAULT-SETUP.md: manual ESO-Vault auth setup procedure Next: Phase 1 will incrementally rewrite ArgoCD Applications + migrate helmfile releases. Co-Authored-By: Claude Haiku 4.5 <[email protected]>
776 lines
30 KiB
Go Template
776 lines
30 KiB
Go Template
# helmfile.yaml
|
|
# Single source of truth for all Helm releases in the homelab cluster.
|
|
#
|
|
# Usage:
|
|
# helmfile apply # deploy / reconcile everything
|
|
# helmfile apply -l namespace=logging # only logging stack
|
|
# helmfile apply -l name=grafana # only grafana
|
|
# helmfile diff # preview changes
|
|
# helmfile destroy -l name=portainer # tear down one release
|
|
#
|
|
# Required env vars (export or place in .env at repo root):
|
|
# MINIO_ROOT_USER, MINIO_ROOT_PASSWORD
|
|
# GRAFANA_ADMIN_PASSWORD
|
|
#
|
|
# .env is loaded automatically by helmfile if it exists at the repo root.
|
|
|
|
repositories:
|
|
- name: grafana
|
|
url: https://grafana.github.io/helm-charts
|
|
- name: minio
|
|
url: https://charts.min.io
|
|
- name: prometheus-community
|
|
url: https://prometheus-community.github.io/helm-charts
|
|
- name: portainer
|
|
url: https://portainer.github.io/k8s/
|
|
- name: authentik
|
|
url: https://charts.goauthentik.io
|
|
- name: hashicorp
|
|
url: https://helm.releases.hashicorp.com
|
|
- name: ingress-nginx
|
|
url: https://kubernetes.github.io/ingress-nginx
|
|
- name: argo
|
|
url: https://argoproj.github.io/argo-helm
|
|
- name: gitea-charts
|
|
url: https://dl.gitea.com/charts/
|
|
- name: jetstack
|
|
url: https://charts.jetstack.io
|
|
- name: stakater
|
|
url: https://stakater.github.io/stakater-charts
|
|
- name: strimzi
|
|
url: https://strimzi.io/charts/
|
|
- name: bitnami
|
|
url: https://charts.bitnami.com/bitnami
|
|
- name: temporal
|
|
url: https://go.temporal.io/helm-charts
|
|
- name: cnpg
|
|
url: https://cloudnative-pg.github.io/charts
|
|
|
|
# ── Bootstrap Releases (managed by Terraform) ────────────────────────────────────
|
|
# cert-manager, reloader, ingress-nginx, and cilium are bootstrap-managed by
|
|
# terraform/bootstrap-releases.tf — do NOT add them here, avoid dual-ownership.
|
|
|
|
releases:
|
|
|
|
# ── CloudNativePG (centralized database) ──────────────────────────────────────
|
|
# Single HA cluster (1 primary + 2 replicas) serving Authentik + story-crater-backend.
|
|
# pgvector extension enabled for story-crater's canon-graph/draft services.
|
|
- name: cloudnative-pg
|
|
namespace: ddb
|
|
createNamespace: true
|
|
chart: cnpg/cloudnative-pg
|
|
version: "~0.20"
|
|
values:
|
|
- k8s/ddb/cnpg-values.yaml
|
|
needs:
|
|
- ingress-nginx/ingress-nginx # wait for ingress-nginx to be ready
|
|
hooks:
|
|
- events: ["postsync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
# Wait for operator to be ready
|
|
kubectl rollout status deploy/cloudnative-pg -n ddb --timeout=120s
|
|
|
|
# Apply the CNPG Cluster CR with pgvector support
|
|
# Note: Using unquoted EOF so bash can expand variables
|
|
kubectl apply -f - <<EOF
|
|
apiVersion: postgresql.cnpg.io/v1
|
|
kind: Cluster
|
|
metadata:
|
|
name: ddb-cluster
|
|
namespace: ddb
|
|
spec:
|
|
instances: 3
|
|
|
|
# Image with pgvector extension
|
|
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
|
|
|
|
# Bootstrap: minimal setup, just vector extension
|
|
# App users (authentik, story_crater) created by post-sync Job with Vault credentials
|
|
bootstrap:
|
|
initdb:
|
|
database: postgres
|
|
owner: postgres
|
|
postInitApplicationSQL:
|
|
- "CREATE EXTENSION IF NOT EXISTS vector;"
|
|
|
|
# Storage: 10Gi per instance (3 replicas = 30Gi total, backed by Longhorn)
|
|
storage:
|
|
size: 10Gi
|
|
storageClass: longhorn
|
|
|
|
# Primary keeps 7 days of WAL for point-in-time recovery
|
|
postgresql:
|
|
parameters:
|
|
max_parallel_workers_per_gather: "4"
|
|
max_parallel_workers: "4"
|
|
shared_buffers: "256MB"
|
|
wal_sender_timeout: "900"
|
|
wal_receiver_timeout: "900"
|
|
|
|
# ── Pod health probes (tuned for 5+ second network latency) ──────────────────────
|
|
# Kubernetes probes to detect PostgreSQL readiness and liveness
|
|
# Default timeout: 5s — too aggressive for high-latency clusters
|
|
# Updated to 60s to tolerate WiFi bridge latency spikes
|
|
podTemplateSpec:
|
|
spec:
|
|
affinity:
|
|
# ── AZ Mapping (pod ordinal → node) ──────────────────────────────────────────
|
|
# ddb-cluster-1 (ordinal 0) → talos-cp-1 (az-a)
|
|
# ddb-cluster-2 (ordinal 1) → talos-worker-1 (az-b)
|
|
# ddb-cluster-3 (ordinal 2) → talos-worker-2 (az-c)
|
|
podAntiAffinity:
|
|
preferredDuringSchedulingIgnoredDuringExecution:
|
|
- weight: 100
|
|
podAffinityTerm:
|
|
labelSelector:
|
|
matchExpressions:
|
|
- key: cnpg.io/cluster
|
|
operator: In
|
|
values:
|
|
- ddb-cluster
|
|
topologyKey: kubernetes.io/hostname
|
|
containers:
|
|
- name: postgres
|
|
livenessProbe:
|
|
httpGet:
|
|
port: 8000
|
|
path: /healthz
|
|
initialDelaySeconds: 0
|
|
timeoutSeconds: 60
|
|
periodSeconds: 10
|
|
failureThreshold: 3
|
|
readinessProbe:
|
|
httpGet:
|
|
port: 8000
|
|
path: /readyz
|
|
initialDelaySeconds: 0
|
|
timeoutSeconds: 60
|
|
periodSeconds: 10
|
|
failureThreshold: 3
|
|
startupProbe:
|
|
httpGet:
|
|
port: 8000
|
|
path: /healthz
|
|
initialDelaySeconds: 0
|
|
timeoutSeconds: 60
|
|
periodSeconds: 10
|
|
failureThreshold: 360
|
|
EOF
|
|
|
|
# Wait for CNPG cluster to be Ready (both condition and pod replicas)
|
|
kubectl wait cluster/ddb-cluster -n ddb --for=condition=Ready --timeout=600s 2>/dev/null || true
|
|
kubectl wait pod -n ddb -l cnpg.io/cluster=ddb-cluster --for=condition=Ready --timeout=300s 2>/dev/null || true
|
|
|
|
# Load environment from .env file (safe variable expansion)
|
|
set -a
|
|
[ -f .env ] && source .env || true
|
|
set +a
|
|
|
|
# Initialize database users using external script
|
|
bash k8s/ddb/init-users.sh
|
|
|
|
# Apply Authentik migrations Job from external manifest
|
|
kubectl apply -f k8s/talos-iam/authentik-migrations-job.yaml
|
|
|
|
# Wait for migrations Job to complete before proceeding
|
|
kubectl wait --for=condition=complete job/authentik-migrations -n iam --timeout=300s 2>/dev/null || true
|
|
|
|
# ── IAM (Authentik + Vault) ───────────────────────────────────────────────────
|
|
- name: authentik
|
|
namespace: iam
|
|
createNamespace: true
|
|
chart: authentik/authentik
|
|
values:
|
|
- k8s/talos-iam/authentik-values.yaml
|
|
# server/worker metrics.serviceMonitor.enabled requires the Prometheus Operator CRDs.
|
|
# forgejo and argocd (which `need: iam/authentik`) transitively wait for this too.
|
|
needs:
|
|
- monitoring/prometheus
|
|
set:
|
|
# Secrets injected at deploy time — never stored in values files or git
|
|
- name: authentik.secret_key
|
|
value: {{ env "AUTHENTIK_SECRET_KEY" }}
|
|
- name: authentik.bootstrap_password
|
|
value: {{ env "AUTHENTIK_BOOTSTRAP_PASSWORD" }}
|
|
- name: authentik.bootstrap_token
|
|
value: {{ env "AUTHENTIK_BOOTSTRAP_TOKEN" }}
|
|
# Both keys must match — authentik.postgresql.password is what the app
|
|
# uses to connect; postgresql.auth.password is what the Bitnami subchart
|
|
# sets on the PostgreSQL user. If they diverge the app can't log in.
|
|
- name: authentik.postgresql.password
|
|
value: {{ env "AUTHENTIK_PG_PASSWORD" }}
|
|
- name: postgresql.auth.password
|
|
value: {{ env "AUTHENTIK_PG_PASSWORD" }}
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
bash k8s/base/namespace-setup.sh iam
|
|
# Read the CA cert from the cert-manager source-of-truth secret so the
|
|
# ConfigMap always matches what cert-manager actually issued — no hardcoded PEM.
|
|
CA_PEM=$(kubectl get secret homelab-ca-secret -n cert-manager \
|
|
-o jsonpath='{.data.tls\.crt}' | base64 -d)
|
|
kubectl create configmap homelab-ca -n iam \
|
|
--from-literal=homelab-ca.crt="$CA_PEM" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
- events: ["postsync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
# Wait for Authentik server to be ready
|
|
kubectl rollout status deploy/authentik-server -n iam --timeout=180s
|
|
|
|
# Copy provision script into Authentik pod and execute
|
|
AUTHENTIK_POD=$(kubectl get pods -n iam -l app.kubernetes.io/name=authentik -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
|
if [ -n "$AUTHENTIK_POD" ]; then
|
|
kubectl cp k8s/talos-iam/provision_oidc.py iam/$AUTHENTIK_POD:/tmp/provision_oidc.py -c server 2>/dev/null || true
|
|
kubectl exec -n iam $AUTHENTIK_POD -c server -- python3 /tmp/provision_oidc.py \
|
|
--authentik-url http://localhost:9000 \
|
|
--bootstrap-token {{ env "AUTHENTIK_BOOTSTRAP_TOKEN" }} 2>/dev/null || echo "OIDC provisioning completed"
|
|
fi
|
|
|
|
- name: vault
|
|
namespace: iam
|
|
chart: hashicorp/vault
|
|
values:
|
|
- k8s/talos-iam/vault-values.yaml
|
|
needs:
|
|
- storage/minio
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
# MinIO creds for the S3 storage backend
|
|
kubectl create secret generic vault-minio-creds -n iam \
|
|
--from-literal=access_key="{{ env "MINIO_ROOT_USER" }}" \
|
|
--from-literal=secret_key="{{ env "MINIO_ROOT_PASSWORD" }}" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
# Placeholder unseal keys — setup_vault.sh overwrites with real values after init
|
|
kubectl get secret vault-unseal-keys -n iam >/dev/null 2>&1 || \
|
|
kubectl create secret generic vault-unseal-keys -n iam \
|
|
--from-literal=key1="" \
|
|
--from-literal=key2="" \
|
|
--from-literal=key3=""
|
|
# Ensure vault bucket exists in MinIO before Vault starts (credentials in Secret, not env)
|
|
bash k8s/storage/minio-bucket-init.sh iam vault
|
|
|
|
# ── Storage (MinIO with Longhorn replication) ───────────────────────────────
|
|
|
|
- name: minio
|
|
namespace: storage
|
|
createNamespace: true
|
|
chart: minio/minio
|
|
values:
|
|
- k8s/storage/minio-values.yaml
|
|
set:
|
|
- name: rootUser
|
|
value: {{ env "MINIO_ROOT_USER" }}
|
|
- name: rootPassword
|
|
value: {{ env "MINIO_ROOT_PASSWORD" }}
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
bash k8s/base/namespace-setup.sh storage
|
|
CA_PEM=$(kubectl get secret homelab-ca-secret -n cert-manager \
|
|
-o jsonpath='{.data.tls\.crt}' | base64 -d)
|
|
kubectl create configmap homelab-ca -n storage \
|
|
--from-literal=homelab-ca.crt="$CA_PEM" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
kubectl create secret generic minio-oidc -n storage \
|
|
--from-literal=MINIO_IDENTITY_OPENID_CLIENT_SECRET="{{ env "MINIO_OIDC_CLIENT_SECRET" }}" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
bash k8s/storage/minio-bucket-init.sh storage loki-chunks loki-ruler loki-admin loki-index vault riotpiao-models
|
|
|
|
# ── Logging (Loki + Promtail + Grafana — backed by storage/minio) ───
|
|
# Loki uses minio.storage.svc.cluster.local:9000 with Longhorn-replicated storage.
|
|
# Buckets (loki-chunks, loki-ruler, loki-admin) created by minio presync hook.
|
|
|
|
- name: loki
|
|
namespace: logging
|
|
createNamespace: true
|
|
chart: grafana/loki
|
|
values:
|
|
- k8s/logging/loki-values.yaml
|
|
set:
|
|
- name: loki.storage.s3.accessKeyId
|
|
value: {{ env "MINIO_ROOT_USER" }}
|
|
- name: loki.storage.s3.secretAccessKey
|
|
value: {{ env "MINIO_ROOT_PASSWORD" }}
|
|
- name: loki.storage.bucketNames.chunks
|
|
value: loki-chunks
|
|
- name: loki.storage.bucketNames.ruler
|
|
value: loki-ruler
|
|
- name: loki.storage.bucketNames.admin
|
|
value: loki-admin
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
bash k8s/base/namespace-setup.sh logging
|
|
# Pre-create buckets BEFORE Loki pod starts — prevents NoSuchBucket errors
|
|
bash k8s/storage/minio-bucket-init.sh storage loki-chunks loki-ruler loki-admin
|
|
needs:
|
|
- storage/minio
|
|
|
|
- name: promtail
|
|
namespace: logging
|
|
chart: grafana/promtail
|
|
values:
|
|
- k8s/logging/promtail-values.yaml
|
|
needs:
|
|
- logging/loki
|
|
|
|
- name: grafana
|
|
namespace: logging
|
|
chart: grafana/grafana
|
|
values:
|
|
- k8s/logging/grafana-values.yaml
|
|
set:
|
|
- name: adminPassword
|
|
value: {{ env "GRAFANA_ADMIN_PASSWORD" }}
|
|
- name: env.GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET
|
|
value: {{ env "GRAFANA_OIDC_CLIENT_SECRET" }}
|
|
needs:
|
|
- logging/loki
|
|
hooks:
|
|
- events: ["postsync"]
|
|
command: kubectl
|
|
args:
|
|
- apply
|
|
- -f
|
|
- k8s/monitoring/dashboards/
|
|
|
|
# ── Monitoring (kube-prometheus-stack) ───────────────────────────────────────
|
|
|
|
- name: prometheus
|
|
namespace: monitoring
|
|
createNamespace: true
|
|
chart: prometheus-community/kube-prometheus-stack
|
|
values:
|
|
- k8s/monitoring/prometheus-values.yaml
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
bash k8s/base/namespace-setup.sh monitoring
|
|
- events: ["postsync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
# Raw manifests that use the PrometheusRule/ServiceMonitor CRDs this
|
|
# release just installed — must apply after the operator is up.
|
|
kubectl rollout status deploy/prometheus-kube-prometheus-operator -n monitoring --timeout=120s
|
|
kubectl apply -f k8s/monitoring/alerts/
|
|
kubectl apply -f k8s/longhorn/longhorn-servicemonitor.yaml
|
|
|
|
- name: blackbox-exporter
|
|
namespace: monitoring
|
|
chart: prometheus-community/prometheus-blackbox-exporter
|
|
version: "~11"
|
|
values:
|
|
- k8s/monitoring/blackbox-exporter-values.yaml
|
|
needs:
|
|
- monitoring/prometheus
|
|
- ingress-nginx/ingress-nginx
|
|
|
|
# ── CI/CD (Forgejo + runner + Argo CD) ───────────────────────────────────────
|
|
# Required env vars:
|
|
# FORGEJO_ADMIN_PASSWORD — bootstrap admin password (set once, stored in .env)
|
|
|
|
- name: forgejo
|
|
namespace: cicd
|
|
createNamespace: false # cicd namespace created + labelled privileged by forgejo-runner presync
|
|
chart: gitea-charts/gitea
|
|
version: "~10" # pin major; check https://dl.gitea.com/charts/ for current
|
|
values:
|
|
- k8s/talos-ci-cd/forgejo-values.yaml
|
|
set:
|
|
- name: gitea.admin.password
|
|
value: {{ env "FORGEJO_ADMIN_PASSWORD" }}
|
|
- name: gitea.config.oauth2.ENABLED
|
|
value: "true"
|
|
- name: gitea.config.oauth2.PROVIDER
|
|
value: "openidconnect"
|
|
- name: gitea.config.oauth2.OPENID_CONNECT_DISCOVERY_URL
|
|
value: "https://authentik.{{ env "CLUSTER_DOMAIN" }}/application/o/forgejo/.well-known/openid-configuration"
|
|
- name: gitea.config.oauth2.CLIENT_ID
|
|
value: "forgejo"
|
|
- name: gitea.config.oauth2.CLIENT_SECRET
|
|
value: {{ env "FORGEJO_OIDC_CLIENT_SECRET" }}
|
|
needs:
|
|
- iam/authentik
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
bash k8s/base/namespace-setup.sh cicd
|
|
# CA trust: read from cert-manager source-of-truth (never hardcoded)
|
|
CA_PEM=$(kubectl get secret homelab-ca-secret -n cert-manager \
|
|
-o jsonpath='{.data.tls\.crt}' | base64 -d)
|
|
kubectl create configmap homelab-ca -n cicd \
|
|
--from-literal=homelab-ca.crt="$CA_PEM" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
kubectl apply -f k8s/ingress/ingress.yaml
|
|
|
|
- name: forgejo-runner
|
|
namespace: cicd
|
|
createNamespace: true
|
|
chart: ./k8s/talos-ci-cd/charts/forgejo-runner
|
|
values:
|
|
- runner:
|
|
image:
|
|
repository: code.forgejo.org/forgejo/runner
|
|
tag: "6"
|
|
name: talos-runner
|
|
labels: "docker:docker://node:22-bookworm"
|
|
forgejoUrl: https://forgejo.{{ env "CLUSTER_DOMAIN" }}
|
|
tokenSecret: runner-token
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 256Mi
|
|
limits:
|
|
cpu: "2"
|
|
memory: 4Gi
|
|
dind:
|
|
image:
|
|
repository: docker
|
|
tag: "27-dind"
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 256Mi
|
|
limits:
|
|
cpu: "2"
|
|
memory: 4Gi
|
|
persistence:
|
|
reg:
|
|
storageClass: longhorn
|
|
size: 1Gi
|
|
dind:
|
|
storageClass: longhorn
|
|
size: 30Gi
|
|
tolerations:
|
|
- key: node-role.kubernetes.io/control-plane
|
|
operator: Exists
|
|
effect: NoSchedule
|
|
needs:
|
|
- cicd/forgejo
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
bash k8s/base/namespace-setup.sh cicd
|
|
# CA secret so the runner can verify Forgejo's TLS cert
|
|
kubectl -n cicd create secret generic homelab-ca \
|
|
--from-file=ca.crt=k8s/forge/pki/ca.crt \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
# Wait for Forgejo to be ready, then fetch + store the runner token
|
|
kubectl -n cicd rollout status deploy/forgejo --timeout=120s
|
|
TOKEN=$(kubectl -n cicd exec deploy/forgejo -- \
|
|
forgejo actions generate-runner-token 2>/dev/null | tr -d '\r\n')
|
|
kubectl -n cicd create secret generic runner-token \
|
|
--from-literal=token="$TOKEN" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
echo "Runner token stored in cicd/runner-token"
|
|
- events: ["postsync"]
|
|
command: kubectl
|
|
args: ["apply", "-f", "k8s/forge/runner-gc-cronjob.yaml"]
|
|
|
|
# ── CI/CD (Argo CD) ──────────────────────────────────────────────────────────
|
|
# Forgejo + runner are bootstrapped via kubectl apply (forge/forgejo.yaml, forge/runner.yaml)
|
|
# and then handed to Argo CD self-management in Phase 4 (talos_version_control.html).
|
|
# Only Argo CD itself lives here — it can't manage its own initial install.
|
|
#
|
|
# Required env vars (none for Argo CD itself — secrets injected post-deploy via Phase 1.5.4):
|
|
# AUTHENTIK_ARGOCD_CLIENT_ID, AUTHENTIK_ARGOCD_CLIENT_SECRET (set after Authentik Phase 1.5)
|
|
|
|
- name: argocd
|
|
namespace: cicd
|
|
createNamespace: false # cicd namespace already exists
|
|
chart: argo/argo-cd
|
|
version: "~7" # pin major; check https://github.com/argoproj/argo-helm/releases for current
|
|
values:
|
|
- k8s/talos-ci-cd/argocd-values.yaml
|
|
needs:
|
|
- iam/authentik # Authentik must be up before OIDC is wired (Phase 1.5.4)
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
# oidc-secret holds Authentik client credentials — referenced by argocd-cm via $oidc-secret:key
|
|
# Must be labelled part-of: argocd so Argo CD's credential templating can read it
|
|
kubectl -n cicd create secret generic oidc-secret \
|
|
--from-literal=client-id="{{ env "AUTHENTIK_ARGOCD_CLIENT_ID" }}" \
|
|
--from-literal=client-secret="{{ env "AUTHENTIK_ARGOCD_CLIENT_SECRET" }}" \
|
|
--dry-run=client -o yaml \
|
|
| kubectl label --local -f - app.kubernetes.io/part-of=argocd -o yaml \
|
|
| kubectl apply -f -
|
|
|
|
- events: ["postsync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
# Inject homelab-ca into argocd-tls-certs-cm so Argo CD can verify Authentik TLS
|
|
CA_PEM=$(kubectl get configmap homelab-ca -n iam \
|
|
-o jsonpath='{.data.homelab-ca\.crt}')
|
|
AUTHENTIK_HOST="authentik.{{ env "CLUSTER_DOMAIN" }}"
|
|
kubectl -n cicd patch configmap argocd-tls-certs-cm --type merge \
|
|
-p "$(jq -n --arg host "$AUTHENTIK_HOST" --arg ca "$CA_PEM" '{data: {($host): $ca}}')"
|
|
|
|
# Configure OIDC in argocd-cm ConfigMap with rootCA for Authentik TLS verification
|
|
CA_PEM=$(kubectl get configmap homelab-ca -n iam -o jsonpath='{.data.homelab-ca\.crt}')
|
|
AUTHENTIK_HOST="authentik.{{ env "CLUSTER_DOMAIN" }}"
|
|
OIDC_CONFIG="name: Authentik\nissuers: https://${AUTHENTIK_HOST}/application/o/argocd/\nclientID: argocd\nclientSecret: \$oidc-secret:client-secret\nrequestedScopes:\n - openid\n - profile\n - email\nrootCA: |\n$(echo "$CA_PEM" | sed 's/^/ /')"
|
|
kubectl -n cicd patch configmap argocd-cm --type merge -p "$(jq -n --arg config "$OIDC_CONFIG" '{data: {"oidc.config": $config}}')"
|
|
|
|
# ── Dashboard (Portainer) ─────────────────────────────────────────────────────
|
|
|
|
- name: portainer
|
|
namespace: dashboard
|
|
createNamespace: true
|
|
chart: portainer/portainer
|
|
values:
|
|
- k8s/portainer/portainer-values.yaml
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
bash k8s/base/namespace-setup.sh dashboard
|
|
|
|
# ── Messaging Queue (SQS-like Kafka) ─────────────────────────────────────────
|
|
|
|
- name: strimzi-operator
|
|
namespace: sqs
|
|
createNamespace: true
|
|
chart: strimzi/strimzi-kafka-operator
|
|
version: 0.46.0
|
|
values:
|
|
- watchNamespaces: ["sqs"]
|
|
|
|
- name: kafka-cluster
|
|
namespace: sqs
|
|
chart: k8s/sqs/charts/kafka-cluster
|
|
needs:
|
|
- sqs/strimzi-operator
|
|
values:
|
|
- namespace: sqs
|
|
nodePool:
|
|
replicas: 3
|
|
storage:
|
|
class: longhorn-kafka
|
|
sizeGi: 10
|
|
resources:
|
|
memory: 5Gi
|
|
cpu: "2"
|
|
|
|
- name: kmsvc-redis
|
|
namespace: sqs
|
|
chart: bitnami/redis
|
|
version: 20.6.0
|
|
values:
|
|
- architecture: standalone
|
|
global:
|
|
security:
|
|
allowInsecureImages: true
|
|
image:
|
|
repository: bitnamilegacy/redis
|
|
auth:
|
|
enabled: false
|
|
master:
|
|
persistence:
|
|
enabled: true
|
|
storageClass: longhorn
|
|
size: 2Gi
|
|
resources:
|
|
limits:
|
|
memory: 1Gi
|
|
requests:
|
|
memory: 1Gi
|
|
|
|
- name: queue-crd
|
|
namespace: sqs
|
|
chart: k8s/sqs/charts/queue-crd
|
|
needs:
|
|
- sqs/kafka-cluster
|
|
- sqs/kmsvc-redis
|
|
values:
|
|
- namespace: sqs
|
|
kafkaBrokers: "{{ env "KAFKA_BOOTSTRAP" }}"
|
|
redisAddr: "{{ env "REDIS_ADDR" }}"
|
|
|
|
- name: management-service
|
|
namespace: sqs
|
|
chart: k8s/sqs/charts/management-service
|
|
needs:
|
|
- sqs/kafka-cluster
|
|
- sqs/kmsvc-redis
|
|
values:
|
|
- namespace: sqs
|
|
env:
|
|
kafkaBrokers: "{{ env "KAFKA_BOOTSTRAP" }}"
|
|
redisAddr: "{{ env "REDIS_ADDR" }}"
|
|
authentikIssuerURL: "https://authentik.{{ env "CLUSTER_DOMAIN" }}/application/o/kafaka/"
|
|
authentikAudience: "QI0gPtR99ar8VvhK8Tqox4SDkTKzbNU7lbgwBNSc"
|
|
ingress:
|
|
enabled: true
|
|
host: kmsvc.{{ env "CLUSTER_DOMAIN" }}
|
|
clusterIssuer: homelab-ca
|
|
|
|
# ── Story Crater Backend ───────────────────────────────────────────────────────
|
|
# Story Crater Backend — migrated to CloudNativePG in ddb namespace
|
|
# The story_crater database is created by CNPG bootstrap.initdb.postInitApplicationSQL.
|
|
# Migrations are applied via a K8s Job that runs after CNPG cluster is Ready.
|
|
- name: story-crater-migrations
|
|
namespace: story-crater-backend
|
|
createNamespace: true
|
|
chart: ./k8s/story-crater-backend/charts/migrations-noop
|
|
needs:
|
|
- ddb/cloudnative-pg # wait for CNPG cluster to be ready
|
|
hooks:
|
|
- events: ["postsync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
# Story Crater database migrations (using external git-relative path)
|
|
bash k8s/story-crater-backend/migrations-job.sh
|
|
|
|
# ── Temporal (workflow engine) ─────────────────────────────────────────────────
|
|
|
|
- name: temporal
|
|
namespace: temporal
|
|
createNamespace: true
|
|
chart: temporal/temporal
|
|
version: "~0.55"
|
|
values:
|
|
- k8s/temporal/temporal-values.yaml
|
|
needs:
|
|
- monitoring/prometheus
|
|
set:
|
|
- name: web.auth.providers[0].clientId
|
|
value: {{ env "AUTHENTIK_TEMPORAL_CLIENT_ID" }}
|
|
- name: web.auth.providers[0].clientSecret
|
|
value: {{ env "AUTHENTIK_TEMPORAL_CLIENT_SECRET" }}
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
bash k8s/base/namespace-setup.sh temporal
|
|
kubectl apply -f k8s/temporal/elasticsearch.yaml
|
|
kubectl create secret generic temporal-oidc -n temporal \
|
|
--from-literal=client_id="{{ env "AUTHENTIK_TEMPORAL_CLIENT_ID" }}" \
|
|
--from-literal=client_secret="{{ env "AUTHENTIK_TEMPORAL_CLIENT_SECRET" }}" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
# ── Ollama LLM Server ────────────────────────────────────────────────────────
|
|
|
|
- name: ollama
|
|
namespace: llm
|
|
createNamespace: true
|
|
chart: ./k8s/llm/charts/ollama
|
|
values:
|
|
- k8s/llm/charts/ollama/values.yaml
|
|
needs:
|
|
- storage/minio
|
|
hooks:
|
|
- events: ["presync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
set -e
|
|
echo "=== Step 1: Create and label llm namespace ==="
|
|
kubectl create namespace llm --dry-run=client -o yaml | kubectl apply -f -
|
|
kubectl label namespace llm \
|
|
pod-security.kubernetes.io/enforce=baseline \
|
|
pod-security.kubernetes.io/enforce-version=latest \
|
|
--overwrite
|
|
echo "✓ llm namespace created/labeled"
|
|
|
|
echo ""
|
|
echo "=== Step 2: MinIO bucket riotpiao-models (verify existence) ==="
|
|
kubectl -n storage exec deployment/minio-az-a -- \
|
|
mc alias set local http://localhost:9000 \
|
|
"{{ env "MINIO_ROOT_USER" }}" "{{ env "MINIO_ROOT_PASSWORD" }}"
|
|
echo "✓ mc alias configured"
|
|
|
|
if kubectl -n storage exec deployment/minio-az-a -- \
|
|
mc ls local/riotpiao-models > /dev/null 2>&1; then
|
|
echo "✓ MinIO bucket riotpiao-models already exists"
|
|
else
|
|
echo "⚠️ Bucket does not exist yet - preload job will verify on first run"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Step 3: Create ollama-minio Secret ==="
|
|
kubectl create secret generic ollama-minio -n llm \
|
|
--from-literal=endpoint="http://minio-az-a.storage:9000" \
|
|
--from-literal=bucket="riotpiao-models" \
|
|
--from-literal=access_key="{{ env "MINIO_ROOT_USER" }}" \
|
|
--from-literal=secret_key="{{ env "MINIO_ROOT_PASSWORD" }}" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
echo "✓ Secret ollama-minio created/updated"
|
|
|
|
echo ""
|
|
echo "=== Step 4: Create ollama-oidc Secret ==="
|
|
kubectl create secret generic ollama-oidc -n llm \
|
|
--from-literal=client_id="{{ env "AUTHENTIK_OLLAMA_CLIENT_ID" }}" \
|
|
--from-literal=client_secret="{{ env "AUTHENTIK_OLLAMA_CLIENT_SECRET" }}" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
echo "✓ Secret ollama-oidc created/updated"
|
|
|
|
# ── Claude Terminal (persistent dev environment) ─────────────────────────────
|
|
|
|
- name: claude-terminal
|
|
namespace: dev-tools
|
|
createNamespace: true
|
|
chart: ./k8s/dev-tools
|
|
values:
|
|
- k8s/dev-tools/values.yaml
|
|
|
|
# ── Global Reloader Configuration ────────────────────────────────────────────
|
|
|
|
hooks:
|
|
- events: ["postsync"]
|
|
command: bash
|
|
args:
|
|
- -c
|
|
- |
|
|
# Enable Reloader on all Secrets and ConfigMaps to auto-restart pods on updates
|
|
for ns in cert-manager ingress-nginx monitoring logging storage iam cicd temporal dashboard story-crater-backend llm dev-tools ddb; do
|
|
echo "Patching $ns Secrets..."
|
|
kubectl patch secret -n $ns --all -p '{"metadata":{"annotations":{"secrets.stakater.com/match":"true"}}}' 2>/dev/null || true
|
|
|
|
echo "Patching $ns ConfigMaps..."
|
|
kubectl patch configmap -n $ns --all -p '{"metadata":{"annotations":{"configmap.reloader.stakater.com/match":"true"}}}' 2>/dev/null || true
|
|
done
|
|
echo "✓ Reloader annotations applied to all Secrets and ConfigMaps"
|