# 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 # ── cert-manager ───────────────────────────────────────────────────────────── releases: - name: cert-manager namespace: cert-manager createNamespace: true chart: jetstack/cert-manager version: "~v1" values: - k8s/cert-manager/cert-manager-values.yaml set: # CRDs must be installed by the chart — avoids a separate kubectl apply step - name: crds.enabled value: true hooks: - events: ["postsync"] command: bash args: - -c - | # Wait for cert-manager webhooks to be ready before applying CRD instances. # Without this, ClusterIssuer/Certificate creation races the webhook and fails. kubectl rollout status deploy/cert-manager -n cert-manager --timeout=120s kubectl rollout status deploy/cert-manager-webhook -n cert-manager --timeout=120s kubectl apply -f - <<'EOF' # Phase 2a — bootstrap issuer (selfSigned) used only to sign the CA cert. # Never referenced by ingresses — its sole job is to sign homelab-ca below. apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: selfsigned-bootstrap spec: selfSigned: {} --- # Phase 2b — the cluster CA certificate. # cert-manager stores the signed cert + key in homelab-ca-secret. # isCA: true marks it so it can sign other certs. # 10-year lifetime; renewBefore triggers 30 days early. apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: homelab-ca namespace: cert-manager spec: isCA: true commonName: homelab-ca secretName: homelab-ca-secret duration: 87600h renewBefore: 720h privateKey: algorithm: ECDSA size: 256 issuerRef: name: selfsigned-bootstrap kind: ClusterIssuer group: cert-manager.io --- # Phase 2c — the real issuer all ingresses reference. # Reads the CA cert+key from homelab-ca-secret and signs per-hostname certs. # Annotate any ingress with: cert-manager.io/cluster-issuer: homelab-ca apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: homelab-ca spec: ca: secretName: homelab-ca-secret EOF # ── Stakater Reloader ───────────────────────────────────────────────────────── # Watches Deployments for secret.reloader.stakater.com/reload and # configmap.reloader.stakater.com/reload annotations, then triggers a rolling # restart whenever the named Secret or ConfigMap is updated (e.g. cert-manager # renewing a TLS cert, or homelab-ca rotating). This replaces the need for # manual `kubectl rollout restart` on cert renewal. - name: reloader namespace: reloader createNamespace: true chart: stakater/reloader version: "~1" # Pod Disruption Budgets applied after reloader (ensures all namespaces exist) hooks: - events: ["postsync"] command: bash args: - -c - kubectl apply -f k8s/base/poddisruptionbudgets.yaml # ── Ingress ─────────────────────────────────────────────────────────────────── - name: ingress-nginx namespace: ingress-nginx createNamespace: true chart: ingress-nginx/ingress-nginx values: - k8s/ingress/nginx-values.yaml # ServiceMonitor enabled in nginx-values.yaml requires the Prometheus Operator # CRDs, which the prometheus release installs — must apply after it. needs: - monitoring/prometheus hooks: - events: ["presync"] command: bash args: - -c - | bash k8s/base/namespace-setup.sh ingress-nginx # LB-IPAM pool must exist before any LoadBalancer service is created, # otherwise services stay . Apply it here as the first hook. kubectl apply -f k8s/cilium/lb-ipam-pool.yaml kubectl apply -f k8s/coredns/coredns-configmap.yaml kubectl apply -f k8s/coredns/coredns-deployment.yaml kubectl rollout restart deployment/coredns -n kube-system kubectl rollout status deployment/coredns -n kube-system --timeout=60s # Wildcard TLS cert — must exist before nginx starts so it can read the secret. # cert-manager issues it in the ingress-nginx namespace; wait until Ready. kubectl apply -f k8s/ingress/wildcard-cert.yaml kubectl wait certificate homelab-tls -n ingress-nginx \ --for=condition=Ready --timeout=120s - events: ["postsync"] command: kubectl args: - apply - -f - k8s/ingress/ingress.yaml # ── 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. # ── 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) ───────────────────────────────────────────────────── # ── Messaging Queue (SQS-like Kafka) ───────────────────────────────────────── # ── 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 - # ── Claude Terminal (persistent dev environment) ───────────────────────────── # ── 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"