openova/clusters/omantel.omani.works/flux-system
hatiyildiz 046e5ebc18 feat(day2-iac): Crossplane Compositions + per-Sovereign Flux cluster tree + catalyst-dns binary
Group F deliverables — completes the day-2 IaC layer that takes over after OpenTofu's Phase 0 hand-off (per docs/SOVEREIGN-PROVISIONING.md §4).

Three artifacts:

1. platform/crossplane/compositions/ — XRDs + Compositions for canonical Hetzner resources
   under the canonical compose.openova.io/v1alpha1 group (per BLUEPRINT-AUTHORING.md §8):
   - XHetznerNetwork + composition-network.yaml — wraps hcloud_network + subnet
   - XHetznerFirewall + composition-firewall.yaml
   - XHetznerServer + composition-server.yaml
   - XHetznerLoadBalancer + composition-loadbalancer.yaml (lb11, 80→31080, 443→31443)
   - README documenting the canonical pattern

2. clusters/_template/ — the canonical per-Sovereign Flux Kustomization tree.
   Copied to clusters/<sovereign-fqdn>/ at provisioning time; cloud-init's
   GitRepository points at the result.
   - kustomization.yaml (root: flux-system + infrastructure + bootstrap-kit)
   - flux-system/ (placeholder for Flux self-config customization)
   - infrastructure/ (provider-hcloud + ProviderConfig referencing hcloud-credentials secret OpenTofu writes)
   - bootstrap-kit/ — 11 HelmRelease manifests in dependency order:
     01-cilium → 02-cert-manager → 03-flux → 04-crossplane → 05-sealed-secrets
     → 06-spire → 07-nats-jetstream → 08-openbao → 09-keycloak → 10-gitea → 11-bp-catalyst-platform
     Each pulls from oci://ghcr.io/openova-io/bp-<name>:1.0.0 — the wrapper charts published by blueprint-release CI.
     dependsOn declarations enforce the canonical install order at runtime.

3. clusters/omantel.omani.works/ — the first concrete Sovereign instance.
   Mirror of _template with SOVEREIGN_FQDN_PLACEHOLDER substituted to omantel.omani.works.
   This is what the wizard's first omantel.omani.works run will actually reconcile.

4. products/catalyst/bootstrap/api/cmd/catalyst-dns/main.go — small Go binary the
   OpenTofu module's null_resource.dns_pool invokes via local-exec at Phase-0 apply time.
   Reads DYNADOT_API_KEY/SECRET/DOMAIN/SUBDOMAIN/LB_IP env vars; calls existing dynadot.Client.AddSovereignRecords. Containerfile already builds + ships it at /usr/local/bin/catalyst-dns.

Architectural compliance (Lesson #24 closed):
- No bespoke Go cloud-API calls (Crossplane Compositions are the canonical day-2 IaC)
- No exec.Command("helm", ...) (Flux HelmReleases are the canonical install unit)
- No kubectl apply from outside (cloud-init kubectl-applies one Flux GitRepository, then Flux owns everything)

After this commit, the path is end-to-end: wizard → catalyst-api → tofu apply (with infra/hetzner/) → cloud-init installs k3s + Flux + applies GitRepository pointing at clusters/omantel.omani.works/ → Flux reconciles bootstrap-kit (11 HelmReleases in dependency order) → Crossplane adopts day-2 management.
2026-04-28 14:09:29 +02:00
..
kustomization.yaml feat(day2-iac): Crossplane Compositions + per-Sovereign Flux cluster tree + catalyst-dns binary 2026-04-28 14:09:29 +02:00