Commit Graph
519 Commits
Author SHA1 Message Date
rock 81e6e9bc03 Revert "feat(iam): send ZITADEL notifications to the Gotify relay"
This reverts commit aa1f224349.
2026-10-26 23:41:01 -07:00
rock aa1f224349 feat(iam): send ZITADEL notifications to the Gotify relay
Point DefaultInstance.SMTPConfiguration at the in-cluster smtp-gotify relay, so
verification codes and password resets arrive on the phone instead of being
dropped for lack of a mail path.

Validated with `helm template zitadel/zitadel 10.4.0` before syncing: the block
renders, and the zatadel-config-yaml ConfigMap is still produced. An earlier
attempt looked like it removed that ConfigMap, which is why this went in only
after a local render check.
2026-10-26 23:39:53 -07:00
rock 37933de756 fix(iam): give the SMTP->Gotify relay a writable /tmp
The relay stages each message in /tmp before pushing. readOnlyRootFilesystem
left it with a read-only /tmp, so it answered '250 queued' and then dropped the
message: no Gotify push, no error line. Mount an emptyDir at /tmp.
2026-10-26 23:38:29 -07:00
rock 468ed16737 revert(iam): back out the SMTP values block that dropped the config ConfigMap
The sync removed zitadel-config-yaml while the Deployment still mounts it, so
the next pod restart would have failed. Restore the previous render first; the
relay stays deployed and can be wired once the chart accepts the block.
2026-10-26 23:32:50 -07:00
rock ab9c2b444b fix(iam): make the SMTP->Gotify relay actually push
Two defects in the first cut, both silent from the sender's side because the
relay answers 250 either way:

- GOTIFY_URL needs a trailing slash; without it the push has no path.
- GOTIFY_TITLE_TEMPLATE referenced .Subject, which the relay does not expose,
  so template execution failed.

The relay's default title already carries the subject, so drop the template.

Also register the SOPS file in secret-generator.yaml: the sops-secrets app only
renders files listed there, so the Secret was never created and the relay sat in
CreateContainerConfigError.
2026-10-26 23:28:44 -07:00
rock b6e1e0c591 fix(iam): give the IAM support manifests an ArgoCD app again
Retiring Authentik deleted the iam-jobs Application, so k8s/infra/iam stopped
being synced. The new smtp-gotify relay would have sat in git and never been
created.

Add an iam-support Application for that path, and drop the stale
rbac-dashboard-rolebinding reference from its kustomization: the file went away
with Authentik, and a missing resource fails the whole app.
2026-10-26 23:24:55 -07:00
rock 701d79deac feat(iam): deliver ZITADEL notifications through Gotify
ZITADEL sends verification codes and password resets by SMTP only, and this
cluster has no mail path. Every human user therefore sits at
verified_email = empty and can never finish a first login, at ZITADEL or at
Forgejo.

Add an smtp-gotify relay in the iam namespace: it accepts plain SMTP on :2525
and forwards each message to the existing Gotify instance, so codes arrive on
the phone through gotify/igotify instead of email.

Point DefaultInstance.SMTPConfiguration at that Service with TLS off, because
the relay speaks plain SMTP inside the cluster. The relay holds the Gotify app
token in a SOPS-encrypted Secret; values never carry the token.
2026-10-26 23:24:33 -07:00
rock fbc1b6a33d feat(iam): manage the Forgejo OAuth login source from a script
Forgejo keeps login sources in its database and exposes no REST endpoint for
them, so neither GitOps nor `tea` (whose admin command only manages users) can
set them. The in-pod CLI is the only supported writer.

This script wraps `gitea admin auth update-oauth` so the rename from the
Authentik-era label to `zitadel` is repeatable instead of a one-off command in
a shell history. Note in the header: the source name is both the button label
and the callback path, so ZITADEL must have both redirect URIs registered
before a rename.
2026-10-26 23:17:03 -07:00
rock 3f93ddc002 feat(iam): provision the built-in login page
loginV2.required lives in the instance database, not in chart values, so
disabling the hosted Login UI in values is only half the change: the server
kept redirecting /oauth/v2/authorize to /ui/v2/login, which no longer had a
backend and returned 503.

Set loginV2.required=false in the provisioning run so the server serves its
own login page. Verified: authorize now redirects to /ui/login/login and the
page renders the "Welcome Back!" login form.
2026-10-26 23:12:53 -07:00
rock eaf9804374 fix(iam): use the built-in ZITADEL login page
Sign-in failed with "Could not create session for user". The ZITADEL API
logged, on every session call:

  authz: verify access token -> invalid token
  token verifier repo: issuer does not match: Expected
    https://zitadel.riotpiao.com, got: login-client
  zitadel-login: [Password] Could not update session

The hosted Login UI authenticates as the machine user `login-client`. That
user did not exist in this instance, because the chart's setup job creates it
and setupJob is disabled after the first sync. Without the user the UI cannot
write a session, so no one can sign in, at ZITADEL or at Forgejo.

Disable the hosted Login UI so ZITADEL serves its built-in login, which needs
no machine user. Re-enable only after creating `login-client` and registering
the public key that matches zitadel-login-service-key.

Also register both callback spellings for the forgejo and paperless apps: the
callback path follows the login-source name, so a rename between authentik and
zitadel must not break sign-in.
2026-10-26 23:08:20 -07:00
rock 903a72a6fd feat(iam): provision the LLM inference roles and assert project roles
The api-gw gates /v1/* on a capability named in the access token. ZITADEL
had no such role, so every inference call was 403 once the token came from
ZITADEL instead of Authentik.

- add llm:inference (Authentik spelling, keeps consumer manifests unchanged)
  and llm-inference (dash alias) to the project roles
- enable projectRoleAssertion on the project, which is what puts
  urn:zitadel:iam:org:project:roles into the access token; without it the
  role list never reaches the resource server
2026-10-26 10:26:54 -07:00
rock 81883a8ea1 chore(iam): retire Authentik
Zitadel is running and has provisioned OIDC clients for all 12 consumers
(state.json: kubernetes, paperless, forgejo, argocd, grafana, immich, minio,
vault, poimen, api-gw, queue-sqs, homarr). Authentik's remaining job was to
serve until the last consumer moved.

Remove every Authentik artifact from GitOps:
- ArgoCD Application `authentik` and its Helm values
- ArgoCD Application `iam-jobs` (provisioner RBAC + migration job)
- ArgoCD Application `oidc-bootstrap` (one-shot apiserver restart)
- authentik-db CNPG cluster definition
- authentik-secrets SOPS file and secret-generator entry
- Ingress rule, cloudflared tunnel hostname, CoreDNS rewrite
- Prometheus alerts and ServiceMonitor
- admin OIDC ClusterRoleBinding provisioning binding
- scripts/iam/provision-rbac.py (the Authentik provisioner)

The chart repo https://charts.goauthentik.io is removed from the AppProject.

Kube-apiserver OIDC and consumer configs are switched to Zitadel in the
local cluster-config (gitignored) and applied separately via talosctl.
2026-10-26 03:32:18 -07:00
rock 7f96da880f fix(ingress): restore the authentik.riotpiao.com route
Commit a02bee5 had both the Ingress rule and the cloudflared tunnel
hostname for authentik.riotpiao.com. Both were lost, so the tunnel
answered 404 for the hostname and the ingress had no matching rule.

The CoreDNS rewrite and the authentik-server Service were still intact;
only the two routing entries were missing. Re-add the Ingress (namespace
iam, service authentik-server port 80, TLS terminated by the wildcard
default-ssl-certificate) and the tunnel hostname.
2026-10-26 03:12:26 -07:00
rock f37e5f32cb fix(paperless): use Recreate strategy for paperless-ai RWO PVC deadlock 2026-10-26 00:35:59 -07:00
rock 8c4a900b40 feat(vault): wire Zitadel OIDC login via postStart auth/oidc config 2026-10-26 00:28:19 -07:00
rock e6ba83cd38 feat(iam): wire all 9 consumer manifests to Zitadel OIDC 2026-10-26 00:14:51 -07:00
rock a02bee50e2 feat(iam): wire all 9 consumer manifests to Zitadel OIDC
Co-authored-by: rock <[email protected]>
2026-10-26 04:34:51 +00:00
rock f9af686c36 Merge pull request 'integration: merge minio-client fix, local-path backend, and local cloudflared ingress' (#70) from integration/merge-56-58-69 into main
integration: merge minio-client fix, local-path backend, and local cloudflared ingress (#70)
2026-10-26 03:24:32 +00:00
rock 3ec46d26f0 merge: harness/zitadel-iam-wiring 2026-10-25 20:20:54 -07:00
rock 52bca6b1b6 merge: feat/local-path-backend 2026-10-25 20:20:40 -07:00
rock 3e079b1cf7 merge: fix/minio-mc-image (keep digest pin + comment) 2026-10-25 20:20:22 -07:00
rock 227a67a8c1 feat(cloudflared): manage tunnel ingress locally via ConfigMap
The tunnel was remotely managed from the Zero Trust dashboard: the
Deployment ran 'run --token' and ignored the ConfigMap that already
existed in the tree. Switch to a locally-managed tunnel so the ingress
routing table lives in git.

- deployment: mount cloudflared-config (config.yml) and the
  cloudflared-credentials Secret; drop TUNNEL_TOKEN env
- configmap.yaml: per-hostname ingress rules to ingress-nginx via the
  CoreDNS rewrite, plus a 404 catch-all
- secret-generator: register the SOPS-encrypted credentials file
- homelab-ca: add the cloudflared namespace copy so the connector trusts
  the ingress certificate (force-ssl-redirect makes plaintext impossible)

Credentials were extracted from the live cloudflared-token Secret and
re-encrypted with SOPS; no plaintext secret is committed.
2026-10-25 19:57:37 -07:00
pi d4e9dbf350 refactor(iam): retire terraform/zitadel, provision through the API
The Terraform root module duplicated a writer that the provisioning script could
be, and its state carried generated client secrets. Declare the objects in the
script instead: project, 19 roles, 12 OIDC apps, applied through the management
API with the iam-admin PAT. State is a 0600 JSON file under ~/.config/zitadel.

Also add zitadel to the Cloudflare tunnel hostname list, with the import command
and the note that the tunnel's Public Hostname entry is a separate dashboard
step: DNS alone yields a 404 from the tunnel.
2026-10-25 19:35:01 -07:00
pi be44a576d4 fix(iam): own the Zitadel login keypair through ksops
The chart generated the login RSA keypair on every render, so
checksum/secret-login-service-key changed each time and both Zitadel
Deployments stayed permanently OutOfSync.

Adopt the live keypair into k8s/argocd/secrets/zitadel-login-service-key.enc.yaml
(SOPS, age recipient from .sops.yaml), list it in the ksops generator, and point
login.loginServiceKeySecretName at it. The chart then stops generating the
keypair and the Deployment checksums become deterministic.
2026-10-25 19:16:18 -07:00
pi 39081222a3 fix(iam): stop the setup hook running after first boot
A PreSync setup run is not harmless once the instance exists: it replays every
repeatable migration on every sync, and interrupting one leaves a
system.migration.started event with no matching done. ZITADEL then waits
forever on 'migration already started'. That deadlocked this instance twice
(projections.projects4, then repeatable_delete_stale_org_fields).

Move the setup job's hook from pre-install,pre-upgrade to post-delete so ArgoCD
sync never runs it. Re-enable deliberately for a schema upgrade.
2026-10-25 19:10:32 -07:00
pi 7e5b793670 fix(iam,edge): freeze first-boot and wire the WireGuard CIDR variable
Zitadel first boot completed on 2026-10-26, so set FirstInstance.Skip: true and
initJob.enabled: false. Without Skip, every sync re-runs machinekeyWriter and
silently rotates the iam-admin key that terraform/zitadel authenticates with.

Also wire wireguard_allowed_cidr into the WireGuard security-group rule; the
variable was declared but the rule still used relay_admin_cidr, which blocks
the homelab peer unless its egress address happens to match the operator CIDR.
2026-10-25 18:48:15 -07:00
pi 354814a531 security: stop tracking Talos secret backup; rotate edge WireGuard key
- remove terraform/terraform.tfvars.local.bak from the index (it carries the
  live cluster PKI: CA keys, service-account key, secretbox secret)
- ignore terraform/*.bak and terraform/edge/.secrets/
- generate a fresh WireGuard relay keypair into the git-ignored local tfvars;
  the committed example now documents rotation instead of holding key material
2026-10-25 18:15:48 -07:00
pi 54fac02783 fix(cloudflared): pin tunnel protocol to http2
QUIC loses long-lived SSE streams when Chinese ISP gateways throttle UDP.
http2 over TCP is the deliberate fallback already in use.
2026-10-25 18:04:53 -07:00
pi eb83aa6290 refactor(edge,iam): apply best practices to relay and Zitadel config
- edge: extract VPC/subnet/bandwidth/disk/DNS/Caddy settings into variables
- edge: add Caddyfile template with SSE-safe flush_interval -1 and security headers
- edge: add WireGuard NAT masquerade so homelab peer accepts forwarded client IPs
- edge: mark user_data sensitive and add prevent_destroy to the relay instance
- edge: validate relay_admin_cidr and Cloudflare hostnames
- edge: parameterise Cloudflare TTL/proxied and enforce proxied tunnel CNAMEs
- iam: fix PDB key to chart-supported pdb and remove unsupported extraVolumes
- iam: keep login hardened; drop unsupported login PDB
2026-10-25 18:03:55 -07:00
pi 6ebecec1c4 fix(zitadel): raise setup job deadline to 20m
The chart default killed zitadel-setup at ~5.5 minutes while it was still
replaying projections.projects4, which failed the whole PreSync and left the
Application OutOfSync/Missing. 1200s leaves room for a cold database without
masking a genuine hang.
2026-10-25 09:47:33 -07:00
rock 091ecdd7bb feat(edge): Terraform for the China-facing relay and Cloudflare DNS
Adds terraform/edge, a root module with two providers:

- Cloudflare 5.28.1: CNAMEs to the existing tunnel, plus an opt-in resource that
  can take the tunnel ingress list over when manage_tunnel_config is true. It is
  off by default because that resource REPLACES the whole list, and the tunnel is
  remotely managed from the dashboard today.
- Tencent Cloud 1.83.38: VPC, subnet, security group with three rules, key pair,
  one CVM relay with cloud-init (WireGuard listener, forwarding sysctls, Caddy),
  and DNSPod A records for the .cn hostnames.

The design keeps the homelab inbound-free: the relay terminates TLS in China and
forwards over a WireGuard tunnel the cluster dials outbound. Cloudflare keeps
serving every existing hostname untouched.

Verified: terraform init succeeds for both providers, fmt clean, validate
reports 0 errors, and plan resolves to 10 creates with placeholder variables.
Provider-side name validation caught a real bug during that run: key pair and
security group names allow only letters, digits and underscore, so the derived
names use underscores.

Not applied. Credentials come from CLOUDFLARE_API_TOKEN,
TENCENTCLOUD_SECRET_ID and TENCENTCLOUD_SECRET_KEY; the WireGuard private key is
TF_VAR only. State is local and git-ignored like terraform/zitadel.

Blocked on facts this module cannot invent: the real .cn domain, whether it has
an ICP filing, the CVM image ID, the operator CIDR, and the homelab WireGuard
public key.
2026-10-24 22:59:54 -07:00
rock 7251032081 Merge pull request 'fix(monitoring): cap Prometheus retention below the PVC size' (#68) from fix/prometheus-retention-size into main 2026-10-25 04:43:21 +00:00
rock b7fc6ed60b fix(monitoring): cap Prometheus retention below the PVC size
retentionSize was 95GB against a 25Gi volume, so the limit never fired. The
volume filled, and Prometheus crashlooped with:

  write /prometheus/queries.active: no space left on device
  failed to initialize active query tracker

18GB keeps roughly 7GiB of headroom for the WAL and block overhead.
2026-10-24 21:43:01 -07:00
rock 6312674058 chore(sms): retire the stuck macOS namespace
The macOS workload is scaled to 0 and its PVC macos-disk has been Pending
for 72 days against storage class longhorn-imessage-local. No node carries
the workload=imessage label it pins to. The namespace only holds that dead
PVC plus two unused Services. Remove the Application, the manifests and the
namespace so ArgoCD prunes them.
2026-10-24 20:05:20 -07:00
rock 2fe6d50ffd Merge pull request 'fix(argocd): allow the Zitadel chart repo in the homelab AppProject' (#67) from fix/allow-zitadel-chart-repo into main 2026-10-25 01:26:38 +00:00
rock e2d9717ce1 fix(argocd): allow the Zitadel chart repo in the homelab AppProject
The zitadel Application failed its first sync with InvalidSpecError:
application repo https://charts.zitadel.com is not permitted in project
'homelab'. AppProject sourceRepos is an allowlist, so the chart repo must be
listed before any chart from it can be reconciled.
2026-10-24 18:26:21 -07:00
rock 111b19dac2 Merge pull request 'fix(sops): repair the ksops generator list and rotate the Zitadel masterkey' (#66) from fix/sops-generator-indent into main 2026-10-25 01:24:43 +00:00
rock e335694cbb fix(sops): repair the generator list and rotate the Zitadel masterkey
Two defects in the ksops generator:

1. The `zitadel-masterkey.enc.yaml` entry was added with two-space indentation
   while every other entry uses four. YAML rejected the list, and kustomize
   reported it as `loading generator plugins ... MalformedYAMLError: yaml: line
   39`. That failed the whole sops-secrets application, so no Secret in that app
   could be created. Verified by running the same kustomize build inside
   argocd-repo-server: exit 1 before the fix, exit 0 with 33 manifests after it.

2. The masterkey value was generated during the first verification pass and
   appeared in that session's output. Rotate it now, before the instance exists:
   a fresh 32-byte value, SOPS-encrypted, with the offline copy updated. No
   ZITADEL data exists yet, so rotation costs nothing. After the first sync it
   would mean re-encrypting system.encryption_keys.

The offline copy lives outside the repo at ~/zitadel-masterkey.offline (0600).
2026-10-24 18:24:28 -07:00
rock d1a7d3be6e fix(oidc): remove the API-server restart Jobs from GitOps
The oidc-bootstrap ArgoCD Application runs with selfHeal and prune. Three
Jobs in restart-apiserver.yaml kill the kube-apiserver process on each
control plane, and bootstrap.yaml carried a fourth Job that deletes the
mirror pods. ArgoCD recreated a deleted Job within two minutes, so the
kill repeated and every API server reached CrashLoopBackOff.

Remove all four. The OIDC authenticator already loaded, so the one-shot
restart is not needed again.
2026-10-24 15:06:32 -07:00
rock 2f664c32d9 fix(cluster): grant queue-operator watches and retire the macOS workload
queue-operator could not list temporalworkers.kmsvc.io or deployments.apps
at cluster scope, so its informers failed on every retry and it reconciled
nothing. Add those rules to the ClusterRole.

macos-bluebubbles pins itself to a node labelled workload=imessage, and no
such node exists. Its PVC macos-disk uses StorageClass longhorn-imessage-local
and stayed unbound for 71 days. Scale the Deployment to 0 and record why.
2026-10-24 04:03:02 -07:00
rock 6c98ca7436 Merge pull request 'fix(sops): drop the orphan forgejo-registry-pat file' (#65) from fix/sops-drop-orphan-forgejo-pat into main 2026-10-24 10:47:14 +00:00
rock 6ee22401fd fix(sops): drop the orphan forgejo-registry-pat file
ksops failed the whole sops-secrets application on this file: it declares
Secret forgejo-registry-pat in namespace forgejo, and no such namespace exists.
The failure blocks every other file in the generator list, including
zitadel-masterkey.

The same Secret name is already generated in namespace api by
forgejo-registry-pat-api.enc.yaml, and that is the copy the gateway reads. The
forgejo namespace copy is stale: Forgejo runs in namespace cicd, and namespace
forgejo was never created.

Remove the file and its generator entry. ArgoCD never created the Secret, so
pruning has nothing to remove.
2026-10-24 03:46:29 -07:00
rock e9817283e6 Merge pull request 'feat(iam): wire Zitadel as the Authentik replacement' (#63) from harness/zitadel-iam-wiring into main 2026-10-24 10:41:17 +00:00
rock 6e9586aa15 Merge pull request 'chore(argocd): delete the azhi-riotpiao Application' (#64) from chore/remove-azhi-riotpiao-app into main 2026-10-24 10:41:09 +00:00
rock 979a413a5b feat(iam): read the Zitadel bootstrap token from .env
ZITADEL_BOOTSTRAP_TOKEN and ZITADEL_URL now live in .env and .env.example, the
same shape the Authentik variables use. The line is present and empty: the PAT
does not exist until the chart's setup job creates the iam-admin machine user on
the first sync, so a value cannot be minted ahead of time.

The provisioning script loads .env itself instead of relying on the caller to
source it, and resolves the token in a fixed order: ZITADEL_BOOTSTRAP_TOKEN,
then the cached file under ~/.config/zitadel, then the cluster Secret. The .env
value wins so a human can paste a rotated token without touching the cluster;
the cluster Secret remains the only source that always reflects a rotation.

.env stays git-ignored. It already holds the live Authentik secrets, and this
change does not move them.

Not exercised against a live instance: the kube-apiserver on talos-cp-1 is in
CrashLoopBackOff, so kubectl only works against cp-2 or cp-3 right now.
2026-10-24 03:38:21 -07:00
rock 2f62351731 feat(iam): one script to provision Zitadel end to end
scripts/iam/zitadel-provision.py replaces the manual sequence that
provision-rbac.py did for Authentik. Phases: keys, apply, secrets, users,
verify. `all` runs keys -> apply -> secrets -> verify; users stays manual
because the password hash must come from a human, one user at a time.

- keys: extract the chart's iam-admin machine key and PAT to ~/.config/zitadel
  at 0600, outside the repo.
- apply: terraform apply terraform/zitadel with the machine key.
- secrets: write generated client secrets into the SOPS files, write client ids
  into the plain manifests that carry them, write machine keys per agent.
  Requires an exactly-one-match regex before it edits a manifest, prints the
  diff without --apply-config, and is a no-op when the value already matches.
- users: import a human user with the Django pbkdf2_sha256 hash converted to the
  passwap MCF form ZITADEL accepts.
- verify: discovery endpoint, client count, and it refuses to clear cutover
  while the groups-claim action is disabled.

Design points that came out of review:
- Multi-document SOPS files (minio) are rewritten whole, because `sops set`
  cannot address one document. sops reads the creation rule from the filename,
  so the temp file is passed with --filename-override; without it sops finds no
  matching rule and refuses. Ciphertext is staged in the destination directory
  and installed with os.replace, so the mode is preserved and EXDEV is
  impossible. SIGTERM/SIGINT trigger cleanup of the decrypted temp file.
- homarr is listed as manual: the live workload mounts Secret homarr-oidc, which
  no SOPS file in git defines, and the tracked homarr-auth-oidc.enc.yaml has no
  consumer. Writing the new secret into the orphan file would report success
  while the dashboard kept the old one.
- Secrets never enter argv: curl reads its config from stdin, so the PAT and the
  password hash do not appear in ps.

.gitignore: allow this one script through the scripts/iam/*.py ignore rule (it
embeds no credential), and ignore authentik-m2m-tokens.env, which holds live M2M
tokens and was one `git add -A` away from being committed.

Checks: py_compile clean, --help lists every phase, convert_hash verified on a
synthetic hash, the 3-document SOPS rewrite tested on a real copy (3 documents
survive, value set, second run unchanged, mode 644 kept, no temp left), all
three client-id regexes match exactly once, dry-run leaves the manifests
untouched. Reviewer round: BLOCK on the sops creation rule, the orphan homarr
Secret, the missing client-id writes and argv exposure; all fixed and
re-reviewed to SIGN-OFF.

Not run against a live ZITADEL: no instance exists yet.
2026-10-24 03:38:21 -07:00
rock f81ff0e2fc feat(iam): declare Zitadel identity objects in Terraform
Adds terraform/zitadel: the provisioning half the ArgoCD wiring left open.
It declares 1 project, 19 project roles, 12 OIDC applications, 5 machine users
plus keys, one API application for the gateway, project grants, per-agent claim
metadata, and a disabled groups-claim action.

Facts established while writing it:

- Client IDs cannot be preserved. ZITADEL v4.19.2 CreateOIDCApplicationRequest
  has no client_id field, and provider 3.8.7 rejects a settable application id
  (terraform validate reports "Invalid or unknown key"). Plan §3.2 assumed a
  client_id-preserving re-point; every consumer now also changes client ID.
- Role keys equal the existing group names, so the groups claim can be
  byte-identical and no RBAC file changes. v4.19.2 allows role keys of 1-200
  characters with no character-set restriction.
- Machine users get JWT profile keys, not passwords. Per-agent claims move to
  user metadata and reach tokens only through the groups-claim action.
- The action stays gated: enable_groups_claim_action defaults to false because
  the action runtime API is unverified. Two validations stop a null script from
  reaching file().

Checks: terraform fmt -check -recursive clean, terraform validate 0 errors,
plan reaches only the expected missing-credentials error, structural gate
0 error / 0 warning, and one reviewer round: BLOCK on a plan-time
fileexists(null) and on the missing api-gw machine path, both fixed, re-reviewed
to SIGN-OFF.

State is local and git-ignored; it carries generated client secrets and machine
keys. Move it to MinIO before a second operator applies.

Not applied: no Zitadel instance is running yet.
2026-10-24 03:38:21 -07:00
rock cb1219bf96 feat(iam): wire Zitadel into namespace iam under ArgoCD
Coexistence wiring for the Authentik replacement: ZITADEL gets its own CNPG
cluster, SOPS masterkey, Helm values and ArgoCD Application in namespace iam.
Authentik keeps serving; no consumer is moved and no Authentik object changes.

- k8s/infra/databases/zitadel-db.yaml: CNPG Postgres 16.2, 3 instances
- k8s/infra/iam/zitadel-values.yaml: chart 10.4.0 pinned, DSN from the CNPG
  secret, masterkey from a Secret, metrics ServiceMonitor, second-sync caveat
- k8s/argocd/apps/30-security.yaml: Application named zitadel, wave 3,
  automated sync deliberately absent for the first sync (chart PreSync hook
  re-run risk)
- k8s/argocd/secrets/zitadel-masterkey.enc.yaml: 32-byte masterkey, SOPS
- k8s/infra/monitoring/alerts/svc-zitadel-rules.yaml: target-down, login-UI,
  crash-loop and PreSync-job-failed rules
- terraform/files/coredns/Corefile: in-cluster rewrite for the zitadel host

Not applied. The cluster is mid-incident: vault-0 and authentik-server are 0/1,
authentik-db-2 has 14 restarts, prometheus-0 is 1/2 CrashLoopBackOff and
kube-state-metrics has 1925 restarts. Two manual steps still gate the first
sync: the Cloudflare Tunnel public hostname plus a proxied CNAME, and
terraform apply + make apply-cp for the CoreDNS rewrite (Talos-owned).

The offline masterkey copy is at ~/zitadel-masterkey.offline (mode 600). Move it
to sealed storage and delete it before the first sync. Losing it makes every
encrypted ZITADEL value unreadable.
2026-10-24 03:38:21 -07:00
rock 8e9ac37583 chore(argocd): delete the azhi-riotpiao Application
The Application pointed at github.com/ToyoGlobal/azhi-infra, branch
feat/azi-riotpiao-cnpg, path k8s/overlays/azi-riotpiao. It never reconciled:
status stayed Unknown because the source repository could not be read, and the
destination namespace azi-riotpiao was never created. ArgoCD reported it as an
OutOfSync managed resource of homelab-root with nothing behind it.

Also drops the ToyoGlobal URL from the AppProject allowlist. No manifest from
that repository is referenced any more, so the allowlist entry is dead weight.

Not touched here, because it is not in git: Secret azhi-infra-github in
namespace argocd holds the read-only GitHub credential for that repository.
Delete it separately once no other Application needs the repo.

The live Application object was deleted by hand. Until this commit reaches
main, the next homelab-root sync recreates it, because main still declares it.
2026-10-24 03:35:52 -07:00
rock 3c851b16c9 Merge branch 'fix/certmanager-dns01'
Restores wildcard renewal: pin the ACME DNS-01 self-check to the public
resolvers and drop the per-host TLS blocks that competed with the wildcard.
2026-10-24 02:39:38 -07:00