Commit Graph
100 Commits
Author SHA1 Message Date
Story Crater Bot 2458da8e91 fix(forgejo): remove nodeSelector now that Longhorn runs on all nodes
With Longhorn now running on all 3 control-plane nodes (commit be7881d),
Forgejo pods no longer need to be pinned to talos-cp-1. The gitea-shared-storage
PVC can attach on any node, and the scheduler will properly co-locate pod + volume
via WaitForFirstConsumer + 3-replica Longhorn volumes.

Removes the kubernetes.io/hostname: talos-cp-1 nodeSelector added in commit
dde4b60 (which was a workaround for single-node storage).
2026-08-18 15:08:03 -07:00
Story Crater Bot e2fcfe1fa8 feat(storage): enable Longhorn on all 3 control-plane nodes for true HA
Changes:
- k8s/infrastructure/longhorn/longhorn-taint-toleration.yaml: new Setting
  to tolerate node-role.kubernetes.io/control-plane:NoSchedule taint,
  allowing Longhorn DaemonSet to run on cp-2/cp-3 (not just cp-1)
- k8s/infrastructure/longhorn/longhorn-nodes.yaml: explicit Node CRDs for
  talos-cp-2 and talos-cp-3 (auto-discovery doesn't work when nodes have
  taints; these define /var/lib/longhorn as the storage path)
- k8s/infrastructure/longhorn/longhorn-wffc-storageclass.yaml: bump
  numberOfReplicas from 1→3 (true HA: each volume gets 3 copies across
  3 nodes; if one node fails, 2 others still have the data)
- k8s/infrastructure/longhorn/kustomization.yaml: add new resources

Root cause: Longhorn was only running on talos-cp-1 (.213) because cp-2/cp-3
have the control-plane taint and Longhorn DaemonSet had no matching toleration.
Every workload with a PVC was forced to schedule on cp-1 (via nodeSelector or
implicit co-location with the storage), defeating the entire purpose of a 3-node
HA cluster.

With this fix:
- Longhorn manager runs on all 3 nodes
- Storage is replicated 3x (erasure-coded across nodes)
- Pods can schedule on any node without PVC attachment failures
- True HA: lose 1 node, cluster still serves all volumes
2026-08-18 15:08:03 -07:00
Story Crater Bot 86f5603063 fix(sso): complete forgejo OAuth2 integration + force pods to storage node
Adds missing CLIENT_SECRET env injection + nodeSelector constraint:
- k8s/argocd/bootstrap/forgejo.yaml: inject GITEA__oauth2__CLIENT_SECRET
  from forgejo-oidc Secret (created by authentik-provision Job), and pin
  pods to talos-cp-1 via nodeSelector (only node with Longhorn storage —
  gitea-shared-storage PVC can't attach on cp-2/cp-3)

Root cause chain for 'Forgejo SSO not working':
1. Authentik 2026.5.5 requires explicit grant_types on OAuth2 providers
2. Old provision script never set it → all providers had grant_types=[]
3. /authorize returned 'Invalid grant_type for provider' → all SSO broken
4. Fixed in k8s/security/iam/scripts/authentik-provision.py (commit be2a56c)
   + successfully re-ran via iam-jobs Application sync
5. But Forgejo deployment still missing CLIENT_SECRET env var → no creds
6. Forgejo bootstrap App used inline valuesObject (chicken-egg with git
   repo self-hosting), but missing the extraEnv block that was only in
   k8s/security/ci-cd/forgejo-values.yaml → CLIENT_SECRET never injected

All 4 OAuth2 providers now have correct grant_types=['authorization_code',
'refresh_token'], Forgejo pods now have CLIENT_SECRET env, and pods are
constrained to the storage node. SSO login flow should now work end-to-end.
2026-08-18 15:08:03 -07:00
Story Crater Bot 368e4a020a fix(iam): Authentik OAuth2 provisioning — grant_types/groups claim, idempotent script, skip PATCH on existing apps 2026-08-18 15:08:03 -07:00
Story Crater Bot a9800c7a3e fix(authentik): widen server probe timeouts (3s->15s) — slow-but-200 health checks under DB contention triggered a liveness kill loop, dropping the pod from Service endpoints and breaking OAuth provisioning 2026-08-18 15:08:03 -07:00
Story Crater Bot 51c07a845f fix(temporal): provision schema via CNPG temporal_visibility DB + drop mysql-only tx_isolation param 2026-08-18 15:08:03 -07:00
Story Crater Bot e2781c72e2 chore(terraform): remove leftover terraform state-backup script and env example — repo is pure GitOps, terraform fully retired 2026-08-18 15:08:03 -07:00
Story Crater Bot ae4f683850 fix(temporal): switch server to sprig configMapsToMount + setConfigFilePath — dockerize path removed in server 1.30.3, config was not loaded so it fell back to Cassandra and crashed 2026-08-18 15:08:03 -07:00
Story Crater Bot 4c9610f86f fix(minio): set HOME=/tmp in policy-setup PostSync hook — mc could not create /.mc as non-root, hanging the job in an endless wait loop 2026-08-18 15:08:03 -07:00
Story Crater Bot 5a8e5ae3fe docs: rewrite CLAUDE.md for ArgoCD GitOps, track in git 2026-08-18 15:08:03 -07:00
Story Crater Bot cf8c97864b fix(temporal): db-secret-sync image bitnami/kubectl:1.30 doesn't exist
Bitnami stopped publishing versioned image tags in 2025 - only 'latest' and
sha256-pinned digests remain for their free-tier images. Confirmed via
Docker Hub API before writing this fix: no '1.30' tag exists for
bitnami/kubectl, which caused an indefinite ImagePullBackOff (job stuck
'Running' with 0 pods able to start).

Switched to python:3.12-alpine + a stdlib urllib kubectl download, matching
the exact pattern already proven working in
k8s/security/iam/authentik-provision-job.yaml (which hit its own apk
permission problem on this same base image, now fixed the same way in
both places) - avoids depending on any third party's tagging policy.
2026-08-18 15:08:03 -07:00
Story Crater Bot e821358106 fix(temporal): db-secret-sync Job deadlocked as PreSync hook
PreSync hooks run BEFORE an Application's own normal (non-hook) resources
are synced. This Job's ServiceAccount/ClusterRole/RoleBindings are plain
resources in the same Application, so marking the Job PreSync created a
chicken-and-egg deadlock: confirmed live, the Job sat 'Running' for 14
minutes producing zero pods, with job-controller repeatedly logging
'serviceaccount temporal/temporal-db-secret-sync not found' - because that
ServiceAccount hadn't been created yet (it's created during the normal Sync
phase, which comes after PreSync).

Fixed to PostSync. This app (sync-wave 7) still fully completes - including
this hook - before the temporal Application (sync-wave 8) begins, so the
ordering guarantee we need (secret exists before Temporal's pods try to
mount it) is unaffected; only the intra-app hook-vs-normal-resource
ordering was wrong.
2026-08-18 15:08:03 -07:00
Story Crater Bot 36db843a79 fix(iam): authentik-provision Job failing on apk permission denied
Job was crash-looping: 'apk add --no-cache curl' failed with Permission
denied - the container runs as non-root UID 1000 (securityContext.
runAsNonRoot: true), and both apk's working directories and /usr/local/bin
(where curl-downloaded kubectl was being written) are root-owned in the
python:3.12-alpine base image.

Replaced with a pure-Python download via urllib (stdlib, already a
dependency of this Job) writing to /tmp (world-writable) instead - no apk
install needed at all. PATH is extended to include /tmp before invoking the
provisioning script so authentik-provision.py's existing
subprocess.run(['kubectl', ...]) calls resolve it via normal PATH lookup,
no changes needed to the script itself.
2026-08-18 15:08:03 -07:00
Story Crater Bot 3c1342cef9 fix(temporal): actually enable PostgreSQL persistence (chart schema mismatch)
Root cause: pinned to temporalio/helm-charts @ 0.74.0, which uses the OLD
flat persistence schema (server.config.persistence.<store>.driver/.sql),
NOT the datastores:-wrapped schema shown in the CURRENT chart's
values/values.postgresql.yaml example (that key was introduced in a later
major version). Our old values.yaml used the datastores: key, which doesn't
exist in 0.74.0 - Helm doesn't validate unknown keys, so it was silently a
no-op. persistence.default.driver / persistence.visibility.driver stayed at
their chart default ("cassandra", with empty hosts: []) the entire time,
regardless of anything nested under datastores:.

Verified before writing this fix: cloned temporalio/helm-charts, checked out
tag temporal-0.74.0 (exact pin), ran  +
 against our actual values.yaml - confirmed the rendered
schema-setup Job used CASSANDRA_HOST/temporal-cassandra-tool the whole time.
Re-rendered with the corrected flat schema - zero Cassandra references,
correct postgres12 pluginName/connectAddr wired to ddb-cluster-rw.

Also fixed two compounding no-ops found the same way:
  -  -> real keys are schema.setup.enabled /
    schema.update.enabled / schema.createDatabase.enabled (jobs.autoSetup
    doesn't exist anywhere in this chart's templates or values.yaml).
  - cassandra.enabled was never actually set to false (stayed at chart
    default true) - now explicitly false, along with mysql/elasticsearch/
    prometheus/grafana (none of which we want).

Password wiring: existingSecret: temporal-db-role + secretKey: password,
pointing at the CNPG-generated Secret - avoids storing the DB password as
plaintext in this values file. Added a new temporal-db-secret-sync
Application (sync-wave 7, one before temporal's wave 8) with a PreSync hook
Job that copies that Secret from the ddb namespace into temporal (Secrets
are namespace-scoped; CNPG creates it in ddb, but Temporal's pods run in
temporal). Deliberately a standalone directory/Application rather than
folded into temporal/'s own kustomization.yaml, which has a The Temporal CLI manages, monitors, and debugs Temporal apps. It lets you run
a local Temporal Service, start Workflow Executions, pass messages to running
Workflows, inspect state, and more.

* Start a local development service:
      `temporal server start-dev`
* View help: pass `--help` to any command:
      `temporal activity complete --help`

Usage:
  temporal [command]

Available Commands:
  activity    Operate on Activity Executions
  batch       Manage running batch jobs
  completion  Generate the autocompletion script for the specified shell
  config      Manage config files (EXPERIMENTAL)
  env         Manage environments
  help        Help about any command
  operator    Manage Temporal deployments
  schedule    Perform operations on Schedules
  server      Run Temporal Server
  task-queue  Manage Task Queues
  worker      Read or update Worker state
  workflow    Start, list, and operate on Workflows

Flags:
      --client-connect-timeout duration
                The client connection timeout. 0s means no timeout.
                (default 0s)
      --color string
                Output coloring. Accepted values: always, never, auto.
                (default "auto")
      --command-timeout duration
                The command execution timeout. 0s means no timeout.
                (default 0s)
      --config-file $CONFIG_PATH/temporalio/temporal.toml
                File path to read TOML config from, defaults to
                $CONFIG_PATH/temporalio/temporal.toml where
                `$CONFIG_PATH` is defined as `$HOME/.config` on Unix,
                `$HOME/Library/Application Support` on macOS, and
                `%AppData%` on Windows.
      --disable-config-env
                If set, disables loading environment config from
                environment variables.
      --disable-config-file
                If set, disables loading environment config from config file.
      --env ENV
                Active environment name (ENV). (default "default")
      --env-file $HOME/.config/temporalio/temporal.yaml
                Path to environment settings file. Defaults to
                $HOME/.config/temporalio/temporal.yaml.
  -h, --help
                help for temporal
      --log-format string
                Log format. Accepted values: text, json. (default "text")
      --log-level string
                Log level. Default is "never" for most commands and
                "warn" for "server start-dev". Accepted values: debug,
                info, warn, error, never. (default "never")
      --no-json-shorthand-payloads
                Raw payload output, even if the JSON option was used.
  -o, --output string
                Non-logging data output format. Accepted values: text,
                json, jsonl, none. (default "text")
      --profile string
                Profile to use for config file.
      --time-format string
                Time format. Accepted values: relative, iso, raw.
                (default "relative")
  -v, --version
                version for temporal

Use "temporal [command] --help" for more information about a command. transformer that would silently rewrite the copy-job's ddb-scoped
RoleBinding back to temporal (same class of bug just fixed in
k8s/security/iam/kustomization.yaml).
2026-08-18 15:08:02 -07:00
Story Crater Bot 6b4f72ef1c feat(iam): automate Authentik OAuth provisioning + create admin user rock 2026-08-18 15:08:02 -07:00
Story Crater Bot d2d377a6a2 fix(monitoring,minio): prometheus CRD sync loop + stuck minio-policy-setup hook
1. prometheus CRD sync failure (OutOfSync, permanently failing):
   - helm.skipCrds: true on the prometheus Application - stop ArgoCD from
     managing these CRDs through client-side apply (kube-prometheus-stack's
     CRDs are large enough that the kubectl.kubernetes.io/last-applied-
     configuration annotation exceeds etcd's 262144-byte limit on every sync).
   - New prometheus-crds Application: plain git-sourced YAML (extracted via
     helm show crds, committed under k8s/platform/monitoring/crds/), synced
     with ServerSideApply=true. Chosen over a Helm-sourced 'CRDs only' app
     because there's no clean way to ask ArgoCD's Helm source for 'render only
     the crds/ directory' - a committed plain-YAML source is unambiguous.
   - ServerSideApply=true can't go on the main prometheus Application: it
     conflicts with managedNamespaceMetadata's forced namespace apply
     ('--force cannot be used with --server-side'), hence the split.

2. minio-tenant stuck OutOfSync (blocked 97+ minutes):
   - minio-policy-setup PostSync hook Job was NAME:
  mc alias set - set a new alias to configuration file

USAGE:
  mc alias set ALIAS URL ACCESSKEY SECRETKEY

FLAGS:
  --path value                     bucket path lookup supported by the server. Valid options are '[auto, on, off]' (default: "auto")
  --api value                      API signature. Valid options are '[S3v4, S3v2]'
  --config-dir value, -C value     path to configuration folder (default: "/Users/rockliang/.mc") [$MC_CONFIG_DIR]
  --quiet, -q                      disable progress bar display [$MC_QUIET]
  --disable-pager, --dp            disable mc internal pager and print to raw stdout [$MC_DISABLE_PAGER]
  --no-color                       disable color theme [$MC_NO_COLOR]
  --json                           enable JSON lines formatted output [$MC_JSON]
  --debug                          enable debug output [$MC_DEBUG]
  --resolve value                  resolves HOST[:PORT] to an IP address. Example: minio.local:9000=10.10.75.1 [$MC_RESOLVE]
  --insecure                       disable SSL certificate verification [$MC_INSECURE]
  --limit-upload value             limits uploads to a maximum rate in KiB/s, MiB/s, GiB/s. (default: unlimited) [$MC_LIMIT_UPLOAD]
  --limit-download value           limits downloads to a maximum rate in KiB/s, MiB/s, GiB/s. (default: unlimited) [$MC_LIMIT_DOWNLOAD]
  --custom-header value, -H value  add custom HTTP header to the request. 'key:value' format.
  --help, -h                       show help

EXAMPLES:
  1. Add MinIO service under "myminio" alias. For security reasons turn off bash history momentarily.
     $ set +o history
     $ mc alias set myminio http://localhost:9000 minio minio123
     $ set -o history
  2. Add MinIO service under "myminio" alias, to use dns style bucket lookup. For security reasons
     turn off bash history momentarily.
     $ set +o history
     $ mc alias set myminio http://localhost:9000 minio minio123 --api "s3v4" --path "off"
     $ set -o history
  3. Add Amazon S3 storage service under "mys3" alias. For security reasons turn off bash history momentarily.
     $ set +o history
     $ mc alias set mys3 https://s3.amazonaws.com \
                 BKIKJAA5BMMU2RHO6IBB V8f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12
     $ set -o history
  4. Add Amazon S3 storage service under "mys3" alias, prompting for keys.
     $ mc alias set mys3 https://s3.amazonaws.com --api "s3v4" --path "off"
     Enter Access Key: BKIKJAA5BMMU2RHO6IBB
     Enter Secret Key: V8f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12
  5. Add Amazon S3 storage service under "mys3" alias using piped keys.
     $ set +o history
     $ echo -e "BKIKJAA5BMMU2RHO6IBB\nV8f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12" | \
                 mc alias set mys3 https://s3.amazonaws.com --api "s3v4" --path "off"
     $ set -o history against
     http://minio.storage.svc.cluster.local:9000 - stale port. The minio
     Service's port now tracks requestAutoCert on the Tenant (443 when
     auto-TLS is on, 80 when off - we set it to false earlier), so 9000
     doesn't exist on that Service anymore and the job hung in its 'waiting
     for minio...' retry loop indefinitely, blocking ArgoCD's sync operation
     (PostSync hooks block the sync from completing until they succeed).
   - Fixed to use minio-cluster-hl.storage.svc.cluster.local:9000 - the
     headless per-pod Service, which always listens on 9000 regardless of
     the Tenant's TLS mode, so this can't silently break again the same way.
2026-08-18 15:08:02 -07:00
Story Crater Bot 8bca21e8ab fix(ingress-nginx): force-ssl-redirect=true globally
Our Ingress objects intentionally omit per-rule tls: blocks (single wildcard
cert served via --default-ssl-certificate). nginx-ingress's normal ssl-redirect
setting only forces HTTPS 301 for hosts with an explicit TLS block on their own
Ingress resource - since none of ours do, plain http://*.riotpiao.com requests
were served directly instead of redirected, exposing every client-facing
console (forgejo, authentik, argocd, grafana, vault, etc.) over plaintext HTTP.

force-ssl-redirect forces the redirect globally regardless of per-ingress TLS
block presence. Verified fix works (tested via manual patch then reverted -
confirmed 308 redirects to https:// on forgejo/authentik/argocd) before
committing via GitOps.
2026-08-18 15:08:02 -07:00
Story Crater Bot d0c150eab9 fix(argocd): repoURL http://forgejo.riotpiao.com:3000 -> https://forgejo.riotpiao.com
Root cause of widespread 'Unknown' sync status / Skipping auto-sync across
almost every Application: CoreDNS rewrites forgejo.riotpiao.com to the nginx
ingress controller service (rewrite name forgejo.riotpiao.com -> ingress-nginx-
controller...), which only listens on 80/443, not 3000. Every git fetch from
argocd-repo-server to the :3000 repoURL was timing out (context deadline
exceeded), so ArgoCD couldn't compare desired vs live state for any app.

Fix: use https://forgejo.riotpiao.com (no port, TLS via nginx + wildcard cert)
consistent with the 'all external endpoints HTTPS' requirement. Verified git
smart-http response 200 on the new URL before committing.
2026-08-18 15:08:02 -07:00
Story Crater Bot 06164288bb fix(ingress): correct broken/mismatched backends found in full audit
- minio console ingress: minio-console -> minio-cluster-console:9090 (service renamed by operator)
- minio-api ingress: point to minio:9000 (restored once requestAutoCert disabled)
- minio tenant: requestAutoCert: false (MinIO was TLS-only internally, breaking
  plain-HTTP clients like Vault's S3 backend - this was the real cause of the
  Vault S3 hang)
- argocd ingress: moved from namespace cicd -> argocd (service lives in argocd
  namespace; ingress in wrong namespace can never route, was returning 503)
- removed duplicate kmsvc ingress (sqs namespace already has management-service
  ingress with proper TLS block for same host/backend)

Audit method: cross-checked every ingress backend.service.{name,port} against
actual Service objects in cluster. Found 3 broken backends out of 15 ingresses.
2026-08-18 15:08:02 -07:00
Story Crater Bot 5dd38d37d4 fix(vault): correct MinIO S3 backend endpoint/timeout/api_addr — converges to minio-cluster-hl:9000 2026-08-18 15:08:02 -07:00
Story Crater Bot 1ee24e947b feat(temporal): drop Cassandra/ES, migrate persistence to CNPG PostgreSQL 2026-08-18 15:08:02 -07:00
Story Crater Bot 24dda9d8f8 fix(minio): disable standalone console (use tenant built-in console instead) 2026-08-18 15:08:02 -07:00
Story Crater Bot 92891d0f9c feat(temporal): Cassandra/ES persistence bring-up attempt (RBAC + config iterate) 2026-08-18 15:08:02 -07:00
Story Crater Bot 3610fb9e5e fix(prometheus): pin to az-a longhorn-wffc SC, drop conflicting ServerSideApply 2026-08-18 15:08:02 -07:00
Story Crater Bot 2845ded626 fix(ingress): switch riotpiao-com-tls to letsencrypt-prod issuer
Wildcard cert was left on letsencrypt-staging; staging root is not
browser-trusted so HTTPS to *.riotpiao.com fails cert validation.
Switch issuerRef to letsencrypt-prod to issue a trusted wildcard.
2026-08-18 15:08:02 -07:00
Story Crater Bot 96b4064ec7 fix(prometheus): scrapeTimeout must be <= scrapeInterval — authentik/nginx SMs (60s>30s) + global (60s>30s) blocked operator config gen, no Prometheus STS created 2026-08-18 15:08:02 -07:00
Story Crater Bot dc1ab77b54 fix: set logging/monitoring namespaces privileged PSS for promtail/node-exporter hostPath access 2026-08-18 15:08:02 -07:00
Story Crater Bot cd861a75e6 fix(argocd): raise repo-server memory 512Mi->1Gi — OOMKilled under CMP+Helm rendering caused chronic restarts, not-ready endpoint, and cluster-wide sync 'no route to host' failures 2026-08-18 15:08:02 -07:00
Story Crater Bot a77e2b6579 fix(kmsvc-redis): use bitnamilegacy/redis mirror + allowInsecureImages — docker.io/bitnami pulled version-pinned tags, ImagePullBackOff blocked redis + queue-operator 2026-08-18 15:08:02 -07:00
Story Crater Bot 6cbd887b89 fix(forgejo-runner): add fsGroup 1000 so runner user can write /data/.runner — register hit permission denied on root-owned Longhorn PVC 2026-08-18 15:08:02 -07:00
Story Crater Bot 7e345625fa chore(ci): refresh forgejo runner registration token — prior token invalid/expired 2026-08-18 15:08:02 -07:00
Story Crater Bot b1be1d0d2c fix(forgejo-runner): point at in-cluster forgejo Service :3000 not public :443 — runner i/o timeout, forgejo serves 3000 not 443 2026-08-18 15:08:02 -07:00
Story Crater Bot 0a6568491b fix(minio,loki): declare loki-chunks/ruler/admin buckets in minio Tenant — loki failed with NoSuchBucket 2026-08-18 15:08:02 -07:00
Story Crater Bot 63fc502359 fix(loki,vault,iam): loki minio endpoint :80 not :9000, emit vault-minio-creds via CMP, drop redundant broken authentik-migrations job 2026-08-18 15:08:02 -07:00
Story Crater Bot 7ab39280c2 fix(ingress): add homelab-ingress ArgoCD app to apply orphaned ingress.yaml — services had no Ingress object, unreachable via LAN ingress .160 2026-08-18 15:08:02 -07:00
Story Crater Bot 97330d780c feat(terraform): add per-node Cloudflare Tunnel cert SANs to controlplane certSANs — remote talosctl/kubectl over tunnel pass TLS verification
Adds optional cloudflare_talos_sans (machine.certSANs, talos API :50000) and
cloudflare_apiserver_sans (cluster.apiServer.certSANs, kube-apiserver :6443) per
control-plane node. cp-1 gets cp1.homelab + cp1-talos.homelab; cp-2/cp-3 get
their cpN-talos.homelab. Values set in gitignored tfvars.
2026-08-18 15:08:02 -07:00
Story Crater Bot 2b89981c28 chore(ci): add SOPS-encrypted runner-token secret record for forgejo-runner registration 2026-08-18 15:08:02 -07:00
Story Crater Bot 384548b424 fix(storage): add longhorn-wffc SC + pin portainer/forgejo-runner to az-a — fixes PVC attach 2026-08-18 15:08:02 -07:00
Story Crater Bot cda75eeb7b fix(authentik): drop redundant authentik-migrate init container — server entrypoint migrates; old-image manage migrate tripped version-history precheck on empty DB 2026-08-18 15:08:02 -07:00
Story Crater Bot 79118de8d3 feat(data): add CNPG managed roles + Database CRs for authentik/temporal — replaces missing helmfile post-sync user creation
authentik/temporal DB users+databases were never provisioned (old helmfile hook
gone; db-init-job only made schemas in shared app DB). Adds managed.roles
(authentik/temporal login roles, passwords from basic-auth secrets) + Database CRs
(dedicated DBs owned by each role). Role secrets applied out-of-band (SOPS), not in
kustomize resources so data-schemas app doesn't choke on ciphertext.
2026-08-18 15:08:02 -07:00
Story Crater Bot 48f3dd1db9 feat(argocd): wire SOPS CMP sidecar + fix loki/grafana/authentik secret resolution 2026-08-18 15:08:02 -07:00
Story Crater Bot d9ae0a6c44 feat(substrate): deploy cert-manager/ingress-nginx/reloader + privileged PodSecurity for ingress-nginx 2026-08-18 15:08:02 -07:00
Story Crater Bot a6465f7158 fix(minio): correct operator chart source + rewrite Tenant to v5 schema + config.env creds — tenant now boots 2026-08-18 15:08:02 -07:00
Story Crater Bot 2623eecfca feat(argocd): SOPS CMP plugin decryption for Stage 0 secrets (simplify to directory source) 2026-08-18 15:08:02 -07:00
Story Crater Bot dca0e7cb9a feat(cloudflared): wire tunnel token secret and document bootstrap
- Create SOPS-encrypted cloudflared-secrets.enc.yaml with tunnel token
- Add Cloudflare vars to .env.example (CLOUDFLARE_CONNECTOR_TOKEN, ACCOUNT_ID, TUNNEL_ID, API_TOKEN)
- Document Phase 0 cloudflared-token Secret creation in BOOTSTRAP.md (manual step until CMP plugin wires it)
- Note: Cloudflare-side TCP routing (cp1.homelab -> 192.168.1.213:6443, etc.) must be configured manually in Zero Trust dashboard

Tunnel already deployed as ArgoCD Application in k8s/argocd/apps/60-applications.yaml (wave 8); this closes the missing Secret gap and documents the bootstrap path.
2026-08-18 15:08:02 -07:00
Story Crater Bot 1168dc8417 fix(k8s,docs): scale ddb-cluster to single instance, pin minio to storage namespace, document 3-CP topology in USAGE 2026-08-18 15:08:02 -07:00
Story Crater Bot 8f86a03828 refactor(argocd): replace wave/layer/phase schemes with two-phase bootstrap + app-of-apps and document both CD scopes — fixes self-hosted-git chicken-egg and stale paths 2026-08-18 15:08:02 -07:00
Story Crater Bot 5b34e71111 feat(terraform): restructure control planes into a 3-node map with LAN etcd advertise and live machine CA — enables talos-cp-1/2/3 HA and drops worker configs 2026-08-18 15:08:02 -07:00
Story Crater Bot 9956379f5f chore: remove scratch planning docs — not meant for the repo 2026-08-18 15:08:02 -07:00
Story Crater Bot 54bfb5ade6 feat(gitops): migrate domain to riotpiao.com, add CNPG + Forgejo HA on Redis/Postgres, wire ArgoCD apps — enables cluster rebuild after etcd wipe and unblocks the git-source chicken-egg via standalone Helm-source Applications 2026-08-18 15:08:02 -07:00
Story Crater Bot 491e88e493 feat(ci,iac): Consolidate Forgejo CI workflows and add Talos Terraform IaC
Consolidate three separate Forgejo Actions (argocd-sync, security-scan, validate-k8s) into single cluster-ci workflow for cleaner CI/CD pipeline with proper job sequencing and reduced auth overhead.

Add Terraform configuration for Talos cluster machine configs:
- Provider setup for Talos
- Centralized variables for CP and worker configs
- Template-based config generation for controlplane.yaml and worker-*.yaml
- Sensitive data separated in terraform.tfvars (gitignored)
- Local state tracking for infrastructure
2026-08-18 15:08:01 -07:00
Story Crater Bot e48580adb9 fix(ci): Forgejo Actions auth + kustomize cleanup in validate-k8s workflow 2026-08-18 15:08:01 -07:00
Story Crater Bot 517d823f77 feat(minio): Expand CRDs to include Policies and Users — full YAML-driven resource creation
Add MinIO Policies and Users via CRD alongside Buckets.

Resources now declarative:
- Bucket: riotpiao-models (versioning enabled)
- Policy: policy-ollama (scoped bucket access)
- User: user-ollama (service account for Ollama/LLM)

Access keys can be overridden via SOPS or kustomize overlays.
All MinIO resource creation now git-tracked and version controlled.
2026-08-18 15:08:01 -07:00
Story Crater Bot 5c259237d7 feat(data): Add CNPG cluster + database schema initialization
Create production PostgreSQL cluster via CNPG (3-node HA, Longhorn storage).

Schema initialization Job creates schemas for:
- Authentik (identity provider)
- Temporal (workflow engine)
- Vault (secrets management)
- App (generic application databases)

Database layer now captures complete IaC for stateful infrastructure.
Services find ready schemas when deployed.
2026-08-18 15:08:01 -07:00
Story Crater Bot c12fbcf45e feat(minio): Add MinIO Bucket CRD for riotpiao-models — replaces shell script setup 2026-08-18 15:08:01 -07:00
Story Crater Bot 0f30d77288 refactor(k8s): Reorganize into 5-layer structure with production kustomizations 2026-08-18 15:08:01 -07:00
Story Crater Bot 563f720d09 refactor: retire Terraform, migrate to pure ArgoCD GitOps + CI validation 2026-08-18 15:08:01 -07:00
Story Crater Bot adbad3d97e fix(ci): use direct in-cluster Kubernetes auth for CI runner — drop kubeconfig file dependency 2026-08-18 15:08:01 -07:00
Story Crater Bot 3833c0d119 fix: terraform fmt — normalize formatting across all files 2026-08-18 15:08:01 -07:00
Story Crater Bot df9d9845c0 fix(ci): correct core-cli auth + S3 backend config for CI runner (iterate) 2026-08-18 15:08:01 -07:00
Story Crater Bot f16f439feb feat: Terraform CI via Forgejo Actions + MinIO S3 state backend
- ArgoCD manages MinIO (phase 0), Terraform manages infrastructure
- Runner workflow: pulls state from S3, validates, plans, applies
- 34 resources imported to state, S3 backend operational
- Fixed AppProject repos, S3 endpoint deprecation, runner package manager
2026-08-18 15:08:01 -07:00
Story Crater Bot 292146bce4 feat(phase4): ArgoCD-driven Terraform apply via PVC imports (Pod Job approach tried and reverted) 2026-08-18 15:08:01 -07:00
Story Crater Bot 74729f1c59 docs(terraform): add state management script and best practices guide 2026-08-18 15:08:01 -07:00
Story Crater Bot 31a266ee58 chore(phase4): stub helmfile — all releases managed by Terraform + ArgoCD 2026-08-18 15:08:01 -07:00
Story Crater Bot 5e887b4da3 feat(argocd): migrate phase3 (authentik) to ArgoCD, keep temporal on helmfile 2026-08-18 15:08:01 -07:00
Story Crater Bot 0b4a79b5ca fix(argocd): use homelab-ca wildcard TLS instead of --insecure mode 2026-08-18 15:08:01 -07:00
Story Crater Bot 2060d4c493 feat(argocd): migrate phase2 releases (CNPG/Loki/Grafana/Forgejo/Forgejo-Runner) to ArgoCD 2026-08-18 15:08:01 -07:00
Story Crater Bot 3e0a1c86ba feat(argocd): migrate phase1 hookless releases to ArgoCD 2026-08-18 15:08:01 -07:00
Story Crater Bot 8eab299b30 docs(phase1): create migration guide for 9 hookless releases
Detailed Phase 1 workflow:
- Template Application spec (Helm source, values, sync policy)
- Per-release migration pattern (create → test → remove → commit)
- Helmfile ↔ ArgoCD mapping table
- Local chart handling (source.path vs source.chart)
- Verification checklist
- Rollback instructions

Reference: execute one release at a time, verify before next.
2026-08-18 15:08:01 -07:00
Story Crater Bot 08a3fd9bcb feat(phase0): configure ArgoCD SOPS decryption + update encrypted secrets
Phase 0 continuation: enable ArgoCD to decrypt SOPS-encrypted secrets on sync.

1. Update ArgoCD Helm values (terraform/argocd-bootstrap.tf):
   - Add SOPS_AGE_KEY_FILE env var to repoServer
   - Mount sops-age K8s Secret at /home/argocd/.sops
   - Add ConfigManagementPlugin for SOPS (detects *.enc.yaml files)

2. Update encrypted secrets with real values:
   - k8s/base/secrets.enc.yaml: encrypted with actual service credentials
   - All secret values encrypted at rest in git
   - ArgoCD decrypts on sync using K8s Secret + AGE key

Prerequisites:
  - K8s Secret created: kubectl create secret generic sops-age -n argocd --from-file=keys.txt=/Users/rockliang/.sops/key.txt
  - SOPS_AGE_KEY_FILE env var set in ArgoCD repoServer (done above)

Next: Phase 1 — migrate 9 hookless releases to ArgoCD + create Applications that reference encrypted secrets.
2026-08-18 15:08:01 -07:00
Story Crater BotandClaude Haiku 4.5 ee385e59a1 feat(phase0): setup SOPS for encrypted secret management
Phase 0 groundwork for helmfile→ArgoCD migration using SOPS (Secrets Operations):

1. Install SOPS + AGE encryption
   - AGE key generated and stored locally at ~/.sops/key.txt
   - Public key embedded in .sops.yaml for file encryption rules

2. Create K8s Secret for AGE private key
   - kubectl: create secret generic sops-age -n argocd --from-file=keys.txt=~/.sops/key.txt
   - ArgoCD will use this key to decrypt secrets at sync time

3. Encrypt initial secrets
   - k8s/base/secrets.enc.yaml: AES256_GCM encrypted secrets for all services
   - Placeholder values (will be replaced with real values per environment)
   - Secrets never visible in git (encrypted at rest)

4. Configure SOPS
   - .sops.yaml: creation rules for k8s/*/secrets.enc.yaml files
   - All future secret files auto-encrypt on edit (sops -e)

Setup: Store AGE key as K8s Secret in argocd namespace:
  export KUBECONFIG=cluster-config/kubeconfig
  kubectl create secret generic sops-age -n argocd --from-file=keys.txt=~/.sops/key.txt

Next: Configure ArgoCD Helm plugin to decrypt secrets on sync (Phase 0 continuation).

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
2026-08-18 15:08:01 -07:00
Story Crater BotandClaude Haiku 4.5 f2b6ad1c60 feat(terraform): import Longhorn StorageClasses and app PVCs to Terraform state
- Phase 1: longhorn, longhorn-kafka StorageClasses (cluster-wide defaults)
- Phase 2 pilot: grafana, loki, portainer, forgejo PVCs
- All imports protected by lifecycle.prevent_destroy
- Removes Helm annotations (meta.helm.sh/*) to prevent dual-ownership conflicts
- Remote state backend (MinIO S3) syncs automatically on plan/apply
- Import-only approach: zero data loss, existing volumes untouched
- See terraform/LONGHORN_PVC_IMPORT.md for execution record

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
2026-08-18 15:08:01 -07:00
Story Crater Bot 3a7471fc9b docs(iac): enforce single source of truth for infrastructure
Add IaC practice section to coding-standards.md:
- All infrastructure state via Terraform or Helm (never ad-hoc scripts)
- Clear division: Terraform owns helm releases/namespaces/storage/state
- Anti-pattern: split bucket definitions across multiple files
- Bootstrap-only exception: document one-time setup with rationale

Rationale: prevents state drift, credential duplication, and unclear ownership.
2026-08-18 15:08:01 -07:00
Story Crater Bot 29d5ba3e6e chore(terraform): clean up TF state git-tracking (gitignore, drop cached state, dep bump) 2026-08-18 15:08:01 -07:00
Story Crater Bot 49eabd0e46 feat(terraform): enable S3 remote state backend (MinIO)
Migrate terraform state from local file to MinIO S3 bucket (terraform-state).
Backend config: https://minio-api.riotpiao.homelab.com (external endpoint).
State now persisted remotely, shared across team, safe for cluster rebuild.

Also added terraform-state bucket to MinIO managed buckets.
2026-08-18 15:08:01 -07:00
Story Crater Bot 22e18c6f33 feat(minio): add loki storage buckets (chunks/ruler/admin/index) 2026-08-18 15:08:01 -07:00
Story Crater Bot acfc9cfc04 feat(authentik): import 24 resources to TF; chore(bootstrap): add cilium to TF
Import all live authentik resources (groups, users, oauth2 providers, applications)
into terraform state via authentik-generated.tf. Provider config in authentik-config.tf.
Resources are drift-free and match live cluster.

Add cilium CNI to bootstrap helm_release.for_each (1.19.5, kube-system).
Cilium was unmanaged (helmfile-only); now IaC-owned. Critical path for
cluster rebuild recovery. Adds cilium repo to helm-repositories.tf.
2026-08-18 15:08:01 -07:00
Story Crater Bot 80e146e106 feat(minio): migrate to official MinIO Operator chart, TF-owned 2026-08-18 15:08:01 -07:00
Story Crater Bot 07b785e5fc Fix: Inject homelab-ca cert into ArgoCD repo-server
- Mount homelab-ca-secret for TLS verification
- Allows repo-server to reach forgejo.riotpiao.homelab.com
- Fixes x509 certificate verification error
2026-08-18 15:08:01 -07:00
Story Crater Bot bb1239ab0d Re-enable cert-manager manifests for TF import
- ClusterIssuers + Certificates now back in TF
- Will import existing live resources
2026-08-18 15:08:01 -07:00
Story Crater Bot 68dc426e53 feat(argocd): phase2 app-of-apps for 19 workloads + AppProject sourceRepos fix 2026-08-18 15:08:01 -07:00
Story Crater Bot da0ec35f6c fix(terraform): set ingress-nginx PodSecurity to privileged, keep TF-managed 2026-08-18 15:08:01 -07:00
Story Crater Bot f64fda297e Temp: disable kubernetes_manifest cert-manager resources (already live)
- Will import separately after helm issues resolved
- Avoids re-create conflicts during bootstrap apply
2026-08-18 15:08:01 -07:00
Story Crater Bot 1c1b924d1d Fix: downgrade ArgoCD to 7.3.3, ignore helm metadata drift
- ArgoCD 7.9.1 -> 7.3.3 (match live cluster)
- Ignore helm release metadata in lifecycle rules
- Prevents unnecessary upgrade attempts
2026-08-18 15:08:00 -07:00
Story Crater Bot 4e473978b5 Step 1 complete: Bootstrap layer with ArgoCD, cert-manager, namespaces imported to TF
- ArgoCD migrated to argocd namespace
- Cert-manager issuers/certs created
- 20 namespaces imported with pod-security labels
- S3 backend temporarily offline (MinIO), using local backup
- Pending: Remove metadata drift from helm releases, re-apply
2026-08-18 15:08:00 -07:00
Story Crater Bot 7835ac2932 feat: Terraform foundation for cluster & app bootstrap
Phase 1 infrastructure-as-code setup:
- Core providers (kubernetes, helm, null)
- 15 Helm repositories (grafana, minio, prometheus, etc.)
- Namespace scaffolding (15 namespaces with pod-security labels)
- Storage classes (longhorn, longhorn-kafka with prevent_destroy)
- TLS certificate bootstrap (selfsigned, CA, wildcard cert)
- Remote state backend config (local for now, S3/GCS TODO)
- Variable definitions for all secrets/OIDC clients

Tested: terraform plan passes with no changes (bootstrap infrastructure ready)
Next: Create 25 helm_release resources (Phase 2-4)

Kept helmfile intact; network/Cilium managed via helmfile (no config risk)
Co-Authored-By: Claude Haiku 4.5 <[email protected]>
2026-08-18 15:08:00 -07:00
Story Crater Bot db7f9ef125 feat: three-tier log aggregation for Loki
Critical services (iam/monitoring/temporal/cicd) keep 100% logs.
Others get 50% sampling + selective drops (health/debug noise).
Balances log volume (40-50% reduction) with error visibility.
2026-08-18 15:08:00 -07:00
Story Crater Bot 78defa0423 feat: track full CoreDNS Deployment manifest, add topologySpreadConstraints
CoreDNS is Talos-bootstrapped and previously untracked except for its
ConfigMap. Pull the full live spec into one file as the single source of
truth, add topologySpreadConstraints so the 2 replicas don't land on the
same node. ScheduleAnyway (not DoNotSchedule) to avoid blocking scheduling
if a node is briefly unavailable.
2026-08-18 15:08:00 -07:00
Story Crater Bot be69b908c2 feat: spread management-service pods across nodes via topologySpreadConstraints
3-9 replicas (HPA) previously relied on implicit scheduler spreading.
ScheduleAnyway (not DoNotSchedule) so pods still get scheduled if a node
is briefly unavailable, just less evenly.
2026-08-18 15:08:00 -07:00
Story Crater Bot 934634a2f6 docs: update USAGE.md for core CLI + IAM management
- Rename talos → core CLI references
- Add IAM management section (Authentik apps, groups, users)
- Add workflows for secret rotation and user management
- Link to detailed core CLI docs (~/workplace/core/USAGE.md)
- Add port-forwarding and troubleshooting tips
2026-08-18 15:08:00 -07:00
Story Crater Bot 3c9daa023e fix: temporal service config - add explicit ClusterIP services for history/matching 2026-08-18 15:08:00 -07:00
Story Crater Bot a1dd91e56f feat: point SQS charts to public GHCR image
Forgejo registry unreachable from worker nodes (network isolation +
host-to-ClusterIP routing gaps). Both management-service and queue-operator
now ship from the same public GHCR image, with queue-operator selected via
command override.
2026-08-18 15:08:00 -07:00
Story Crater Bot 9dbe8b6f43 remove: strip all oauth2-proxy deployments
- Delete oauth2-proxy helm releases from helmfile (temporal, kmsvc, longhorn, portainer)
- Remove oauth2-proxy manifests and ingress redirects
- Add direct ingress for kmsvc management service
- Update temporal/portainer/longhorn ingress comments to reflect direct service exposure

Services now accessible without oauth2-proxy layer.
2026-08-18 15:08:00 -07:00
Story Crater Bot 1eef4711e5 k8s/aux: add cert-manager longhorn dashboard forge dev-tools and shadowsocks
- cert-manager ClusterIssuers (LetsEncrypt + homelab-ca)
- Longhorn storage dashboard
- Portainer dashboard config
- Forgejo git service
- Claude terminal remote access
- Shadowsocks tunnel for remote access
2026-08-18 15:08:00 -07:00
Story Crater Bot f64cde0687 k8s/cilium: add lb-ipam pool configuration
- Cilium LB-IPAM pool (192.168.1.160-192.168.1.170)
- Fixed IP assignment for LoadBalancer services
2026-08-18 15:08:00 -07:00
Story Crater Bot 61be24e27f k8s/services: add ingress networking portainer llm and project guides
- Nginx ingress + TLS termination (homelab-ca)
- Portainer container UI
- CoreDNS internal DNS rewrites
- DuckDNS DDNS updater
- Ollama LLM inference
- 8 project-usage guides (team reference)
2026-08-18 15:08:00 -07:00
Story Crater Bot 69ad5c371c k8s/messaging: add kafka kmsvc and temporal workflows
- Kafka 3-broker cluster (RF=3, min-ISR=2)
- kmsvc SQS-like API on Kafka
- Redis dedup (standalone, can extend to HA)
- Temporal workflow orchestration (Cassandra backend)
2026-08-18 15:08:00 -07:00
Story Crater Bot a3f261f548 k8s/ci-cd: add forgejo gitops and argocd deployment
- Forgejo git forge + OCI registry
- Argo CD pull-based GitOps
- Private CA TLS (self-signed 10-year cert)
- Machine credentials scoped to repositories
2026-08-18 15:08:00 -07:00
Story Crater Bot 0af06b1239 k8s/monitoring: add prometheus grafana loki observability
- Loki log aggregation (MinIO backed, 10-day retention)
- Promtail daemonset (pod + talos journal logs)
- Prometheus + kube-state-metrics
- Grafana dashboards (6-row template per service)
2026-08-18 15:08:00 -07:00
Story Crater Bot 831dd50805 k8s/iam: add cloudnativepg postgres and vault + authentik
- PostgreSQL 3-replica HA with pgvector
- Vault S3 storage backend (MinIO)
- Authentik federated OIDC provider
- Vault auto-unseal via postStart hook
2026-08-18 15:08:00 -07:00