infra: add helmfile and talos cluster configuration
- helmfile: 18 releases across 22 namespaces - Pod disruption budgets for zero-downtime drain - Nginx ingress with LoadBalancer + Cilium LB-IPAM - Cluster bootstrap hooks
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# cluster-config/cilium-values.yaml
|
||||
# Cilium CNI — installed via talosctl (not helmfile) during cluster bootstrap.
|
||||
# Applied once: `helm install cilium cilium/cilium -n kube-system -f cilium-values.yaml`
|
||||
#
|
||||
# Why Cilium: Talos Linux does not ship kube-proxy. Cilium's eBPF dataplane
|
||||
# replaces it entirely (kubeProxyReplacement: true) and also handles LB-IPAM
|
||||
# so LoadBalancer services get real IPs without MetalLB.
|
||||
|
||||
# ── cgroup ────────────────────────────────────────────────────────────────────
|
||||
# Talos mounts cgroups at boot before any container runtime starts.
|
||||
# autoMount: false tells Cilium to use the existing mount rather than trying
|
||||
# to mount its own — double-mounting on Talos causes init failures.
|
||||
cgroup:
|
||||
autoMount:
|
||||
enabled: false
|
||||
hostRoot: /sys/fs/cgroup # where Talos exposes the cgroup v2 hierarchy
|
||||
|
||||
# ── IPAM ──────────────────────────────────────────────────────────────────────
|
||||
# kubernetes mode: Cilium allocates pod IPs from the pod CIDR that Talos
|
||||
# configured for each node (--pod-cidr in the kubelet). Alternative is
|
||||
# Cilium's own cluster-pool IPAM, but that requires extra config and
|
||||
# conflicts with the Talos node CIDR assignment.
|
||||
ipam:
|
||||
mode: kubernetes
|
||||
|
||||
# ── Operator ──────────────────────────────────────────────────────────────────
|
||||
# Single replica is fine for a 3-node homelab. The operator manages CiliumNode
|
||||
# objects and LB-IPAM pools — it does not sit in the data path.
|
||||
operator:
|
||||
replicas: 1
|
||||
|
||||
# ── kube-proxy replacement ────────────────────────────────────────────────────
|
||||
# Talos is deliberately installed without kube-proxy (machineConfig
|
||||
# install.extensions excludes it). Cilium must replace it completely —
|
||||
# partial replacement would leave Service ClusterIPs unreachable.
|
||||
kubeProxyReplacement: true
|
||||
|
||||
# ── L2 announcements ──────────────────────────────────────────────────────────
|
||||
# Without this, LB-IPAM (k8s/cilium/lb-ipam-pool.yaml) assigns real IPs to
|
||||
# LoadBalancer Services, but nothing ARPs for them on the LAN — the IP shows
|
||||
# up in `kubectl get svc` but is 100% unreachable from outside the cluster
|
||||
# (confirmed: forgejo's .165 and shadowsocks' .166 both had incomplete ARP
|
||||
# entries and 100% ping loss before this). This flag is what actually makes
|
||||
# k8s/cilium/l2-announcement-policy.yaml take effect instead of being inert.
|
||||
l2announcements:
|
||||
enabled: true
|
||||
|
||||
# ── API server endpoint ───────────────────────────────────────────────────────
|
||||
# Cilium needs to talk to the Kubernetes API to watch Nodes/Services/Endpoints.
|
||||
# On Talos the API server listens on 127.0.0.1:7445 locally (the external
|
||||
# port 6443 requires the node's external cert, which may not be available
|
||||
# during early bootstrap). This is the standard Talos Cilium bootstrap config.
|
||||
k8sServiceHost: 127.0.0.1
|
||||
k8sServicePort: 7445
|
||||
|
||||
# ── Security context / capabilities ──────────────────────────────────────────
|
||||
# Cilium's eBPF programs run in the kernel and require elevated capabilities.
|
||||
# These are the minimum set needed — removing any of them breaks networking.
|
||||
#
|
||||
# NET_ADMIN / NET_RAW — manipulate iptables/nftables and raw sockets
|
||||
# IPC_LOCK — lock eBPF maps in memory (prevents paging out BPF state)
|
||||
# SYS_ADMIN — call bpf() syscall and mount BPF filesystem
|
||||
# SYS_RESOURCE — raise RLIMIT_MEMLOCK for BPF map memory
|
||||
# DAC_OVERRIDE / FOWNER / SETGID / SETUID — file permission ops during init
|
||||
# CHOWN / KILL — container lifecycle management
|
||||
#
|
||||
# cleanCiliumState runs as a one-shot init container to wipe stale eBPF state
|
||||
# on upgrades — it needs NET_ADMIN, SYS_ADMIN, SYS_RESOURCE only.
|
||||
securityContext:
|
||||
capabilities:
|
||||
ciliumAgent:
|
||||
- CHOWN
|
||||
- KILL
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
- IPC_LOCK
|
||||
- SYS_ADMIN
|
||||
- SYS_RESOURCE
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
cleanCiliumState:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
- SYS_RESOURCE
|
||||
@@ -0,0 +1,19 @@
|
||||
# bootstrap.sh
|
||||
#!/bin/bash
|
||||
|
||||
# Wait for cluster to be ready
|
||||
kubectl wait --for=condition=Ready nodes --all --timeout=300s
|
||||
|
||||
# Longhorn requires privileged pods and hostPath volumes
|
||||
kubectl create namespace longhorn-system --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl label namespace longhorn-system \
|
||||
pod-security.kubernetes.io/enforce=privileged \
|
||||
pod-security.kubernetes.io/enforce-version=latest \
|
||||
--overwrite
|
||||
|
||||
# Install Longhorn
|
||||
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.7.0/deploy/longhorn.yaml
|
||||
|
||||
# Set as default StorageClass
|
||||
kubectl patch storageclass longhorn \
|
||||
-p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
|
||||
@@ -0,0 +1,971 @@
|
||||
# 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 <pending>. 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 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
|
||||
|
||||
# ── 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 -
|
||||
|
||||
# ── OAuth2-Proxy (Authentik OIDC protection) ──────────────────────────────────
|
||||
|
||||
- name: oauth2-proxy-temporal
|
||||
namespace: temporal
|
||||
chart: ./k8s/charts/oauth2-proxy
|
||||
values:
|
||||
- k8s/charts/oauth2-proxy/values-temporal.yaml
|
||||
needs:
|
||||
- ingress-nginx/ingress-nginx
|
||||
hooks:
|
||||
- events: ["presync"]
|
||||
command: bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
kubectl create secret generic temporal-oidc -n temporal \
|
||||
--from-literal=clientSecret="{{ env "AUTHENTIK_TEMPORAL_CLIENT_SECRET" }}" \
|
||||
--from-literal=cookieSecret="$(head -c 32 /dev/urandom | base64)" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
- name: oauth2-proxy-kmsvc
|
||||
namespace: sqs
|
||||
chart: ./k8s/charts/oauth2-proxy
|
||||
values:
|
||||
- k8s/charts/oauth2-proxy/values-kmsvc.yaml
|
||||
needs:
|
||||
- ingress-nginx/ingress-nginx
|
||||
hooks:
|
||||
- events: ["presync"]
|
||||
command: bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
kubectl create secret generic kmsvc-oidc -n sqs \
|
||||
--from-literal=clientSecret="{{ env "AUTHENTIK_KMSVC_CLIENT_SECRET" }}" \
|
||||
--from-literal=cookieSecret="$(head -c 32 /dev/urandom | base64)" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
- name: oauth2-proxy-longhorn
|
||||
namespace: longhorn-system
|
||||
chart: ./k8s/charts/oauth2-proxy
|
||||
values:
|
||||
- k8s/charts/oauth2-proxy/values-longhorn.yaml
|
||||
needs:
|
||||
- ingress-nginx/ingress-nginx
|
||||
hooks:
|
||||
- events: ["presync"]
|
||||
command: bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
kubectl create secret generic longhorn-oidc -n longhorn-system \
|
||||
--from-literal=clientSecret="{{ env "AUTHENTIK_LONGHORN_CLIENT_SECRET" }}" \
|
||||
--from-literal=cookieSecret="$(head -c 32 /dev/urandom | base64)" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
- name: oauth2-proxy-portainer
|
||||
namespace: dashboard
|
||||
chart: ./k8s/charts/oauth2-proxy
|
||||
values:
|
||||
- k8s/charts/oauth2-proxy/values-portainer.yaml
|
||||
needs:
|
||||
- ingress-nginx/ingress-nginx
|
||||
hooks:
|
||||
- events: ["presync"]
|
||||
command: bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
kubectl create secret generic portainer-oidc -n dashboard \
|
||||
--from-literal=clientSecret="{{ env "AUTHENTIK_PORTAINER_CLIENT_SECRET" }}" \
|
||||
--from-literal=cookieSecret="$(head -c 32 /dev/urandom | base64)" \
|
||||
--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"
|
||||
Reference in New Issue
Block a user