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.
This commit is contained in:
parent
9519c1ef00
commit
046e5ebc18
47
clusters/_template/bootstrap-kit/01-cilium.yaml
Normal file
47
clusters/_template/bootstrap-kit/01-cilium.yaml
Normal file
@ -0,0 +1,47 @@
|
||||
# bp-cilium — Catalyst bootstrap-kit Blueprint. CNI must come first; k3s started with --flannel-backend=none precisely so Cilium can take over.
|
||||
#
|
||||
# Wrapper chart: platform/cilium/chart/
|
||||
# Catalyst-curated values: platform/cilium/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: kube-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-cilium
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-cilium
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-cilium
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: cilium
|
||||
targetNamespace: kube-system
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-cilium
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-cilium
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/_template/bootstrap-kit/02-cert-manager.yaml
Normal file
49
clusters/_template/bootstrap-kit/02-cert-manager.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-cert-manager — Catalyst bootstrap-kit Blueprint. TLS for everything below — Lets Encrypt issuer with Dynadot DNS-01 (omani.works pool) or HTTP-01 (BYO domains).
|
||||
#
|
||||
# Wrapper chart: platform/cert-manager/chart/
|
||||
# Catalyst-curated values: platform/cert-manager/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cert-manager
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-cert-manager
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-cert-manager
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-cert-manager
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: cert-manager
|
||||
targetNamespace: cert-manager
|
||||
dependsOn:
|
||||
- name: bp-cilium
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-cert-manager
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-cert-manager
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/_template/bootstrap-kit/03-flux.yaml
Normal file
49
clusters/_template/bootstrap-kit/03-flux.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-flux — Catalyst bootstrap-kit Blueprint. Host-level Flux. Per-vcluster Flux is bootstrapped later by environment-controller.
|
||||
#
|
||||
# Wrapper chart: platform/flux/chart/
|
||||
# Catalyst-curated values: platform/flux/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: flux-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-flux
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-flux
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-flux
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: flux
|
||||
targetNamespace: flux-system
|
||||
dependsOn:
|
||||
- name: bp-cert-manager
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-flux
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-flux
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/_template/bootstrap-kit/04-crossplane.yaml
Normal file
49
clusters/_template/bootstrap-kit/04-crossplane.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-crossplane — Catalyst bootstrap-kit Blueprint. Day-2 cloud resource control plane. Adopts management of resources OpenTofu created in Phase 0 (Phase 1 hand-off per SOVEREIGN-PROVISIONING.md §4).
|
||||
#
|
||||
# Wrapper chart: platform/crossplane/chart/
|
||||
# Catalyst-curated values: platform/crossplane/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: crossplane-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-crossplane
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-crossplane
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-crossplane
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: crossplane
|
||||
targetNamespace: crossplane-system
|
||||
dependsOn:
|
||||
- name: bp-flux
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-crossplane
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-crossplane
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/_template/bootstrap-kit/05-sealed-secrets.yaml
Normal file
49
clusters/_template/bootstrap-kit/05-sealed-secrets.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-sealed-secrets — Catalyst bootstrap-kit Blueprint. Transient bootstrap-only — used during Phase 0 to ship initial secrets through GitOps. Replaced by OpenBao + ESO once those land.
|
||||
#
|
||||
# Wrapper chart: platform/sealed-secrets/chart/
|
||||
# Catalyst-curated values: platform/sealed-secrets/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: kube-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-sealed-secrets
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-sealed-secrets
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-sealed-secrets
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: sealed-secrets
|
||||
targetNamespace: kube-system
|
||||
dependsOn:
|
||||
- name: bp-cert-manager
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-sealed-secrets
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-sealed-secrets
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/_template/bootstrap-kit/06-spire.yaml
Normal file
49
clusters/_template/bootstrap-kit/06-spire.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-spire — Catalyst bootstrap-kit Blueprint. Workload identity. SPIFFE/SPIRE issues 5-min rotating SVIDs to every Pod. Required by NATS JetStream and OpenBao below for SVID-based auth.
|
||||
#
|
||||
# Wrapper chart: platform/spire/chart/
|
||||
# Catalyst-curated values: platform/spire/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: spire-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-spire
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-spire
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-spire
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: spire
|
||||
targetNamespace: spire-system
|
||||
dependsOn:
|
||||
- name: bp-cert-manager
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-spire
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-spire
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/_template/bootstrap-kit/07-nats-jetstream.yaml
Normal file
49
clusters/_template/bootstrap-kit/07-nats-jetstream.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-nats-jetstream — Catalyst bootstrap-kit Blueprint. Catalyst's control-plane event spine. Per-Org Account isolation. KV bucket per Environment.
|
||||
#
|
||||
# Wrapper chart: platform/nats-jetstream/chart/
|
||||
# Catalyst-curated values: platform/nats-jetstream/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: nats-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-nats-jetstream
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-nats-jetstream
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-nats-jetstream
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: nats-jetstream
|
||||
targetNamespace: nats-system
|
||||
dependsOn:
|
||||
- name: bp-spire
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-nats-jetstream
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-nats-jetstream
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/_template/bootstrap-kit/08-openbao.yaml
Normal file
49
clusters/_template/bootstrap-kit/08-openbao.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-openbao — Catalyst bootstrap-kit Blueprint. Secret backend. 3-node Raft, region-local. No stretched cluster (per SECURITY.md §5).
|
||||
#
|
||||
# Wrapper chart: platform/openbao/chart/
|
||||
# Catalyst-curated values: platform/openbao/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: openbao
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-openbao
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-openbao
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-openbao
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: openbao
|
||||
targetNamespace: openbao
|
||||
dependsOn:
|
||||
- name: bp-spire
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-openbao
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-openbao
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/_template/bootstrap-kit/09-keycloak.yaml
Normal file
49
clusters/_template/bootstrap-kit/09-keycloak.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-keycloak — Catalyst bootstrap-kit Blueprint. User identity. Topology decided by Sovereign CRD spec.keycloakTopology.
|
||||
#
|
||||
# Wrapper chart: platform/keycloak/chart/
|
||||
# Catalyst-curated values: platform/keycloak/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: keycloak
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-keycloak
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-keycloak
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-keycloak
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: keycloak
|
||||
targetNamespace: keycloak
|
||||
dependsOn:
|
||||
- name: bp-cert-manager
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-keycloak
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-keycloak
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
61
clusters/_template/bootstrap-kit/10-gitea.yaml
Normal file
61
clusters/_template/bootstrap-kit/10-gitea.yaml
Normal file
@ -0,0 +1,61 @@
|
||||
# bp-gitea — Catalyst Blueprint #10 of 11. Per-Sovereign Git server with
|
||||
# the public Blueprint catalog mirror seeded. Catalyst's catalog-svc reads
|
||||
# Blueprint metadata from this Gitea (not from the public openova monorepo
|
||||
# directly) so the Sovereign is air-gap-ready by construction.
|
||||
#
|
||||
# Wrapper chart: platform/gitea/chart/
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: gitea
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-gitea
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-gitea
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-gitea
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: gitea
|
||||
targetNamespace: gitea
|
||||
dependsOn:
|
||||
- name: bp-keycloak
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-gitea
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-gitea
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
values:
|
||||
global:
|
||||
sovereignFQDN: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
# gitea hostname is gitea.SOVEREIGN_FQDN_PLACEHOLDER. The DNS A record
|
||||
# was already published by the Phase-0 catalyst-dns helper.
|
||||
ingress:
|
||||
hosts:
|
||||
- host: gitea.SOVEREIGN_FQDN_PLACEHOLDER
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
@ -0,0 +1,70 @@
|
||||
# bp-catalyst-platform — Catalyst Blueprint #11 of 11. The umbrella
|
||||
# Blueprint that brings up the Catalyst control plane: console, marketplace,
|
||||
# admin, catalog-svc, projector, provisioning, environment-controller,
|
||||
# blueprint-controller, billing.
|
||||
#
|
||||
# Per docs/ARCHITECTURE.md §11 (Catalyst-on-Catalyst): once this is Ready,
|
||||
# the Sovereign is fully self-sufficient — sovereign-admin can log into
|
||||
# console.SOVEREIGN_FQDN_PLACEHOLDER and proceed with Phase 2 day-1 setup.
|
||||
#
|
||||
# Wrapper chart: products/catalyst/chart/
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: catalyst-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-catalyst-platform
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-catalyst-platform
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-catalyst-platform
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: catalyst-platform
|
||||
targetNamespace: catalyst-system
|
||||
dependsOn:
|
||||
- name: bp-gitea
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-catalyst-platform
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-catalyst-platform
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
# Per-Sovereign overrides for the umbrella — sovereign-FQDN-derived hostnames
|
||||
# for console/admin/api. All chart-level Catalyst service config (image refs,
|
||||
# OTel endpoints, NATS subjects) lives in products/catalyst/chart/values.yaml.
|
||||
values:
|
||||
global:
|
||||
sovereignFQDN: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
ingress:
|
||||
hosts:
|
||||
console:
|
||||
host: console.SOVEREIGN_FQDN_PLACEHOLDER
|
||||
admin:
|
||||
host: admin.SOVEREIGN_FQDN_PLACEHOLDER
|
||||
marketplace:
|
||||
host: SOVEREIGN_FQDN_PLACEHOLDER
|
||||
api:
|
||||
host: api.SOVEREIGN_FQDN_PLACEHOLDER
|
||||
18
clusters/_template/bootstrap-kit/kustomization.yaml
Normal file
18
clusters/_template/bootstrap-kit/kustomization.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Order is documented but not enforced here — Flux respects HelmRelease
|
||||
# dependsOn declarations for actual install order. Listing in canonical
|
||||
# Phase 0 sequence per SOVEREIGN-PROVISIONING.md §3.
|
||||
resources:
|
||||
- 01-cilium.yaml
|
||||
- 02-cert-manager.yaml
|
||||
- 03-flux.yaml
|
||||
- 04-crossplane.yaml
|
||||
- 05-sealed-secrets.yaml
|
||||
- 06-spire.yaml
|
||||
- 07-nats-jetstream.yaml
|
||||
- 08-openbao.yaml
|
||||
- 09-keycloak.yaml
|
||||
- 10-gitea.yaml
|
||||
- 11-bp-catalyst-platform.yaml
|
||||
9
clusters/_template/flux-system/kustomization.yaml
Normal file
9
clusters/_template/flux-system/kustomization.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Flux's own components were applied directly by cloud-init. The
|
||||
# GitRepository + Kustomization that point at clusters/<sovereign-fqdn>/
|
||||
# (this directory) are what start the GitOps loop and are also written
|
||||
# by cloud-init. This entry is a stub for future Flux config customization
|
||||
# (pull intervals, registry credentials, etc.) — empty by default.
|
||||
resources: []
|
||||
6
clusters/_template/infrastructure/kustomization.yaml
Normal file
6
clusters/_template/infrastructure/kustomization.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- provider-hcloud.yaml
|
||||
- provider-config-hcloud.yaml
|
||||
@ -0,0 +1,15 @@
|
||||
# ProviderConfig for provider-hcloud. Token source = the K8s secret
|
||||
# `hcloud-credentials` in `crossplane-system`, which the OpenTofu module's
|
||||
# cloud-init writes at Phase-0 time so Crossplane can adopt resources
|
||||
# immediately after install.
|
||||
apiVersion: hcloud.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: default
|
||||
spec:
|
||||
credentials:
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: hcloud-credentials
|
||||
key: token
|
||||
8
clusters/_template/infrastructure/provider-hcloud.yaml
Normal file
8
clusters/_template/infrastructure/provider-hcloud.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
# Crossplane provider-hcloud — installed AFTER bp-crossplane lands core.
|
||||
# Adopts Phase-0 OpenTofu-created resources and manages day-2 changes.
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-hcloud
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-hcloud:v0.4.0
|
||||
15
clusters/_template/kustomization.yaml
Normal file
15
clusters/_template/kustomization.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
# Per-Sovereign Flux Kustomization root.
|
||||
#
|
||||
# Copied from clusters/_template/ → clusters/<sovereign-fqdn>/ at provisioning
|
||||
# time, with SOVEREIGN_FQDN_PLACEHOLDER substituted. The Sovereign's k3s
|
||||
# control plane (cloud-init bootstrap) installs Flux core, then applies a
|
||||
# GitRepository pointing at this Sovereign's directory. From there Flux owns
|
||||
# everything.
|
||||
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- flux-system
|
||||
- infrastructure
|
||||
- bootstrap-kit
|
||||
47
clusters/omantel.omani.works/bootstrap-kit/01-cilium.yaml
Normal file
47
clusters/omantel.omani.works/bootstrap-kit/01-cilium.yaml
Normal file
@ -0,0 +1,47 @@
|
||||
# bp-cilium — Catalyst bootstrap-kit Blueprint. CNI must come first; k3s started with --flannel-backend=none precisely so Cilium can take over.
|
||||
#
|
||||
# Wrapper chart: platform/cilium/chart/
|
||||
# Catalyst-curated values: platform/cilium/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: kube-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-cilium
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-cilium
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-cilium
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: cilium
|
||||
targetNamespace: kube-system
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-cilium
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-cilium
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
@ -0,0 +1,49 @@
|
||||
# bp-cert-manager — Catalyst bootstrap-kit Blueprint. TLS for everything below — Lets Encrypt issuer with Dynadot DNS-01 (omani.works pool) or HTTP-01 (BYO domains).
|
||||
#
|
||||
# Wrapper chart: platform/cert-manager/chart/
|
||||
# Catalyst-curated values: platform/cert-manager/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cert-manager
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-cert-manager
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-cert-manager
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-cert-manager
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: cert-manager
|
||||
targetNamespace: cert-manager
|
||||
dependsOn:
|
||||
- name: bp-cilium
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-cert-manager
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-cert-manager
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/omantel.omani.works/bootstrap-kit/03-flux.yaml
Normal file
49
clusters/omantel.omani.works/bootstrap-kit/03-flux.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-flux — Catalyst bootstrap-kit Blueprint. Host-level Flux. Per-vcluster Flux is bootstrapped later by environment-controller.
|
||||
#
|
||||
# Wrapper chart: platform/flux/chart/
|
||||
# Catalyst-curated values: platform/flux/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: flux-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-flux
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-flux
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-flux
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: flux
|
||||
targetNamespace: flux-system
|
||||
dependsOn:
|
||||
- name: bp-cert-manager
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-flux
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-flux
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
@ -0,0 +1,49 @@
|
||||
# bp-crossplane — Catalyst bootstrap-kit Blueprint. Day-2 cloud resource control plane. Adopts management of resources OpenTofu created in Phase 0 (Phase 1 hand-off per SOVEREIGN-PROVISIONING.md §4).
|
||||
#
|
||||
# Wrapper chart: platform/crossplane/chart/
|
||||
# Catalyst-curated values: platform/crossplane/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: crossplane-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-crossplane
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-crossplane
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-crossplane
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: crossplane
|
||||
targetNamespace: crossplane-system
|
||||
dependsOn:
|
||||
- name: bp-flux
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-crossplane
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-crossplane
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
@ -0,0 +1,49 @@
|
||||
# bp-sealed-secrets — Catalyst bootstrap-kit Blueprint. Transient bootstrap-only — used during Phase 0 to ship initial secrets through GitOps. Replaced by OpenBao + ESO once those land.
|
||||
#
|
||||
# Wrapper chart: platform/sealed-secrets/chart/
|
||||
# Catalyst-curated values: platform/sealed-secrets/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: kube-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-sealed-secrets
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-sealed-secrets
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-sealed-secrets
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: sealed-secrets
|
||||
targetNamespace: kube-system
|
||||
dependsOn:
|
||||
- name: bp-cert-manager
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-sealed-secrets
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-sealed-secrets
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/omantel.omani.works/bootstrap-kit/06-spire.yaml
Normal file
49
clusters/omantel.omani.works/bootstrap-kit/06-spire.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-spire — Catalyst bootstrap-kit Blueprint. Workload identity. SPIFFE/SPIRE issues 5-min rotating SVIDs to every Pod. Required by NATS JetStream and OpenBao below for SVID-based auth.
|
||||
#
|
||||
# Wrapper chart: platform/spire/chart/
|
||||
# Catalyst-curated values: platform/spire/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: spire-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-spire
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-spire
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-spire
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: spire
|
||||
targetNamespace: spire-system
|
||||
dependsOn:
|
||||
- name: bp-cert-manager
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-spire
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-spire
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
@ -0,0 +1,49 @@
|
||||
# bp-nats-jetstream — Catalyst bootstrap-kit Blueprint. Catalyst's control-plane event spine. Per-Org Account isolation. KV bucket per Environment.
|
||||
#
|
||||
# Wrapper chart: platform/nats-jetstream/chart/
|
||||
# Catalyst-curated values: platform/nats-jetstream/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: nats-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-nats-jetstream
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-nats-jetstream
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-nats-jetstream
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: nats-jetstream
|
||||
targetNamespace: nats-system
|
||||
dependsOn:
|
||||
- name: bp-spire
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-nats-jetstream
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-nats-jetstream
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/omantel.omani.works/bootstrap-kit/08-openbao.yaml
Normal file
49
clusters/omantel.omani.works/bootstrap-kit/08-openbao.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-openbao — Catalyst bootstrap-kit Blueprint. Secret backend. 3-node Raft, region-local. No stretched cluster (per SECURITY.md §5).
|
||||
#
|
||||
# Wrapper chart: platform/openbao/chart/
|
||||
# Catalyst-curated values: platform/openbao/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: openbao
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-openbao
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-openbao
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-openbao
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: openbao
|
||||
targetNamespace: openbao
|
||||
dependsOn:
|
||||
- name: bp-spire
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-openbao
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-openbao
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
49
clusters/omantel.omani.works/bootstrap-kit/09-keycloak.yaml
Normal file
49
clusters/omantel.omani.works/bootstrap-kit/09-keycloak.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# bp-keycloak — Catalyst bootstrap-kit Blueprint. User identity. Topology decided by Sovereign CRD spec.keycloakTopology.
|
||||
#
|
||||
# Wrapper chart: platform/keycloak/chart/
|
||||
# Catalyst-curated values: platform/keycloak/chart/values.yaml
|
||||
# Reconciled by: Flux on the new Sovereign's k3s control plane.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: keycloak
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-keycloak
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-keycloak
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-keycloak
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: keycloak
|
||||
targetNamespace: keycloak
|
||||
dependsOn:
|
||||
- name: bp-cert-manager
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-keycloak
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-keycloak
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
61
clusters/omantel.omani.works/bootstrap-kit/10-gitea.yaml
Normal file
61
clusters/omantel.omani.works/bootstrap-kit/10-gitea.yaml
Normal file
@ -0,0 +1,61 @@
|
||||
# bp-gitea — Catalyst Blueprint #10 of 11. Per-Sovereign Git server with
|
||||
# the public Blueprint catalog mirror seeded. Catalyst's catalog-svc reads
|
||||
# Blueprint metadata from this Gitea (not from the public openova monorepo
|
||||
# directly) so the Sovereign is air-gap-ready by construction.
|
||||
#
|
||||
# Wrapper chart: platform/gitea/chart/
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: gitea
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-gitea
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-gitea
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-gitea
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: gitea
|
||||
targetNamespace: gitea
|
||||
dependsOn:
|
||||
- name: bp-keycloak
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-gitea
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-gitea
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
values:
|
||||
global:
|
||||
sovereignFQDN: omantel.omani.works
|
||||
# gitea hostname is gitea.omantel.omani.works. The DNS A record
|
||||
# was already published by the Phase-0 catalyst-dns helper.
|
||||
ingress:
|
||||
hosts:
|
||||
- host: gitea.omantel.omani.works
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
@ -0,0 +1,70 @@
|
||||
# bp-catalyst-platform — Catalyst Blueprint #11 of 11. The umbrella
|
||||
# Blueprint that brings up the Catalyst control plane: console, marketplace,
|
||||
# admin, catalog-svc, projector, provisioning, environment-controller,
|
||||
# blueprint-controller, billing.
|
||||
#
|
||||
# Per docs/ARCHITECTURE.md §11 (Catalyst-on-Catalyst): once this is Ready,
|
||||
# the Sovereign is fully self-sufficient — sovereign-admin can log into
|
||||
# console.omantel.omani.works and proceed with Phase 2 day-1 setup.
|
||||
#
|
||||
# Wrapper chart: products/catalyst/chart/
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: catalyst-system
|
||||
labels:
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: bp-catalyst-platform
|
||||
namespace: flux-system
|
||||
spec:
|
||||
type: oci
|
||||
interval: 15m
|
||||
url: oci://ghcr.io/openova-io/bp-catalyst-platform
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: bp-catalyst-platform
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 15m
|
||||
releaseName: catalyst-platform
|
||||
targetNamespace: catalyst-system
|
||||
dependsOn:
|
||||
- name: bp-gitea
|
||||
chart:
|
||||
spec:
|
||||
chart: bp-catalyst-platform
|
||||
version: 1.0.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-catalyst-platform
|
||||
namespace: flux-system
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
# Per-Sovereign overrides for the umbrella — sovereign-FQDN-derived hostnames
|
||||
# for console/admin/api. All chart-level Catalyst service config (image refs,
|
||||
# OTel endpoints, NATS subjects) lives in products/catalyst/chart/values.yaml.
|
||||
values:
|
||||
global:
|
||||
sovereignFQDN: omantel.omani.works
|
||||
ingress:
|
||||
hosts:
|
||||
console:
|
||||
host: console.omantel.omani.works
|
||||
admin:
|
||||
host: admin.omantel.omani.works
|
||||
marketplace:
|
||||
host: omantel.omani.works
|
||||
api:
|
||||
host: api.omantel.omani.works
|
||||
@ -0,0 +1,18 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Order is documented but not enforced here — Flux respects HelmRelease
|
||||
# dependsOn declarations for actual install order. Listing in canonical
|
||||
# Phase 0 sequence per SOVEREIGN-PROVISIONING.md §3.
|
||||
resources:
|
||||
- 01-cilium.yaml
|
||||
- 02-cert-manager.yaml
|
||||
- 03-flux.yaml
|
||||
- 04-crossplane.yaml
|
||||
- 05-sealed-secrets.yaml
|
||||
- 06-spire.yaml
|
||||
- 07-nats-jetstream.yaml
|
||||
- 08-openbao.yaml
|
||||
- 09-keycloak.yaml
|
||||
- 10-gitea.yaml
|
||||
- 11-bp-catalyst-platform.yaml
|
||||
@ -0,0 +1,9 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Flux's own components were applied directly by cloud-init. The
|
||||
# GitRepository + Kustomization that point at clusters/<sovereign-fqdn>/
|
||||
# (this directory) are what start the GitOps loop and are also written
|
||||
# by cloud-init. This entry is a stub for future Flux config customization
|
||||
# (pull intervals, registry credentials, etc.) — empty by default.
|
||||
resources: []
|
||||
@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- provider-hcloud.yaml
|
||||
- provider-config-hcloud.yaml
|
||||
@ -0,0 +1,15 @@
|
||||
# ProviderConfig for provider-hcloud. Token source = the K8s secret
|
||||
# `hcloud-credentials` in `crossplane-system`, which the OpenTofu module's
|
||||
# cloud-init writes at Phase-0 time so Crossplane can adopt resources
|
||||
# immediately after install.
|
||||
apiVersion: hcloud.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: default
|
||||
spec:
|
||||
credentials:
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: hcloud-credentials
|
||||
key: token
|
||||
@ -0,0 +1,8 @@
|
||||
# Crossplane provider-hcloud — installed AFTER bp-crossplane lands core.
|
||||
# Adopts Phase-0 OpenTofu-created resources and manages day-2 changes.
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-hcloud
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-hcloud:v0.4.0
|
||||
15
clusters/omantel.omani.works/kustomization.yaml
Normal file
15
clusters/omantel.omani.works/kustomization.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
# Per-Sovereign Flux Kustomization root.
|
||||
#
|
||||
# Copied from clusters/_template/ → clusters/<sovereign-fqdn>/ at provisioning
|
||||
# time, with omantel.omani.works substituted. The Sovereign's k3s
|
||||
# control plane (cloud-init bootstrap) installs Flux core, then applies a
|
||||
# GitRepository pointing at this Sovereign's directory. From there Flux owns
|
||||
# everything.
|
||||
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- flux-system
|
||||
- infrastructure
|
||||
- bootstrap-kit
|
||||
57
platform/crossplane/compositions/README.md
Normal file
57
platform/crossplane/compositions/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Catalyst Crossplane Compositions — canonical Hetzner XRDs
|
||||
|
||||
**XRD API group:** `compose.openova.io/v1alpha1`
|
||||
(per `docs/BLUEPRINT-AUTHORING.md` §8 + `VALIDATION-LOG.md` Pass 42/48; **never** `catalyst.openova.io` — that is the Catalyst CRD group, not the Crossplane composite group.)
|
||||
|
||||
This directory contains the four canonical Hetzner-backed XRDs + their default Compositions that Catalyst uses to manage day-2 cloud infrastructure on a franchised Sovereign. After Phase 0 (`infra/hetzner/main.tf`) hands off to Phase 1, **all** further Hetzner resources — additional regions, attached volumes, additional firewalls, additional load balancers — go through these XRDs and are reconciled by Crossplane.
|
||||
|
||||
Per `docs/INVIOLABLE-PRINCIPLES.md` principle #3:
|
||||
|
||||
> Crossplane is the ONLY IaC after Phase 1 hand-off. Not direct provider SDKs. Not Terraform. Not the catalyst-api Go service calling cloud APIs.
|
||||
|
||||
## XRDs in this directory
|
||||
|
||||
| XRD | Wraps |
|
||||
|---|---|
|
||||
| `XHetznerNetwork` | `hcloud_network` + `hcloud_network_subnet` (provider-hcloud `Network` + `NetworkSubnet`) |
|
||||
| `XHetznerFirewall` | `hcloud_firewall` (provider-hcloud `Firewall`) |
|
||||
| `XHetznerServer` | `hcloud_server` (provider-hcloud `Server`) |
|
||||
| `XHetznerLoadBalancer` | `hcloud_load_balancer` + targets + services (provider-hcloud `LoadBalancer` + `LoadBalancerTarget` + `LoadBalancerService`) |
|
||||
|
||||
Each `xrd-*.yaml` declares the OpenAPIv3 schema; each matching `composition-*.yaml` references the upstream `provider-hcloud` managed resources.
|
||||
|
||||
## Why these four
|
||||
|
||||
These mirror the four resource families OpenTofu provisions in `infra/hetzner/main.tf` Phase 0. After Phase 1 hand-off, Crossplane **adopts** the OpenTofu-created resources by `external-name` (the Hetzner numeric resource ID), and any further changes — adding a worker, opening a port, adding a region — are made by submitting an XR (claim) of the appropriate type instead of editing OpenTofu state.
|
||||
|
||||
## Provider configuration
|
||||
|
||||
The provider itself (`provider-hcloud`) and its `ProviderConfig` are installed by `platform/crossplane/chart/templates/provider-hcloud.yaml`, which is reconciled by Flux from the cluster directory. The Hetzner API token is mounted from a K8s `Secret` named `hcloud-credentials` in the `crossplane-system` namespace — that secret is created by the OpenTofu module's hand-off step.
|
||||
|
||||
## Adoption pattern
|
||||
|
||||
When OpenTofu creates a resource in Phase 0, the resource gets a label like:
|
||||
|
||||
```
|
||||
catalyst.openova.io/sovereign: omantel.omani.works
|
||||
catalyst.openova.io/role: control-plane
|
||||
```
|
||||
|
||||
Phase 1 ingests these into Crossplane by creating an XR with `metadata.annotations[crossplane.io/external-name]` set to the Hetzner numeric ID. Crossplane then takes over the lifecycle — `kubectl delete xhetznerserver/cp1` after Phase 1 will deprovision the underlying Hetzner server, just like `tofu destroy` would have done in Phase 0. (See `clusters/<sovereign-fqdn>/infrastructure/adoption-claims.yaml` for the bootstrap claim manifests.)
|
||||
|
||||
## Authoring conventions
|
||||
|
||||
- Every XRD's `group` is `compose.openova.io` and `versions[0].name` is `v1alpha1`.
|
||||
- Every XR's plural is `<kind-lowercase>s` (e.g. `xhetznerservers`).
|
||||
- Every XRD declares a `claimNames` block so users can submit namespaced claims (`HetznerServer`) instead of cluster-scoped XRs (`XHetznerServer`).
|
||||
- `defaultCompositionRef` points at the matching `composition-*.yaml` shipped here.
|
||||
- Per principle #4 (no hardcoding): every cloud-specific value (region, server type, image) is a schema field, never a constant in the Composition.
|
||||
|
||||
## Adding a new XRD
|
||||
|
||||
1. Drop `xrd-<resource>.yaml` and `composition-<resource>.yaml` in this directory.
|
||||
2. Reference the matching upstream provider-hcloud kind under `spec.resources[].base`.
|
||||
3. Add the file to `kustomization.yaml`.
|
||||
4. Bump `Chart.yaml` version of `bp-crossplane`.
|
||||
|
||||
The CI (`.github/workflows/blueprint-release.yaml`) re-publishes `bp-crossplane` to GHCR on the next push, and Flux reconciles the new XRDs into every Sovereign on its next pull.
|
||||
44
platform/crossplane/compositions/composition-firewall.yaml
Normal file
44
platform/crossplane/compositions/composition-firewall.yaml
Normal file
@ -0,0 +1,44 @@
|
||||
# Composition: hetzner-firewall.compose.openova.io — default realization for
|
||||
# XHetznerFirewall.
|
||||
#
|
||||
# This composition uses Crossplane's "patches with type: FromCompositeFieldPath"
|
||||
# to splat the user-supplied rules array directly onto the upstream
|
||||
# provider-hcloud Firewall.spec.forProvider.rule list. Each rule's port,
|
||||
# protocol, and source/destination CIDRs come from the XR.
|
||||
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: hetzner-firewall.compose.openova.io
|
||||
labels:
|
||||
catalyst.openova.io/component: crossplane
|
||||
catalyst.openova.io/composition-family: hetzner
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: compose.openova.io/v1alpha1
|
||||
kind: XHetznerFirewall
|
||||
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
|
||||
resources:
|
||||
- name: firewall
|
||||
base:
|
||||
apiVersion: firewall.hcloud.crossplane.io/v1alpha1
|
||||
kind: Firewall
|
||||
spec:
|
||||
forProvider:
|
||||
rule: [] # filled by patch
|
||||
providerConfigRef:
|
||||
name: default-hcloud
|
||||
patches:
|
||||
- fromFieldPath: spec.parameters.name
|
||||
toFieldPath: metadata.name
|
||||
- fromFieldPath: spec.parameters.rules
|
||||
toFieldPath: spec.forProvider.rule
|
||||
- fromFieldPath: spec.providerConfigRef.name
|
||||
toFieldPath: spec.providerConfigRef.name
|
||||
- fromFieldPath: spec.parameters.sovereignFQDN
|
||||
toFieldPath: metadata.labels[catalyst.openova.io/sovereign]
|
||||
- type: ToCompositeFieldPath
|
||||
fromFieldPath: metadata.annotations[crossplane.io/external-name]
|
||||
toFieldPath: status.firewallId
|
||||
@ -0,0 +1,89 @@
|
||||
# Composition for XHetznerLoadBalancer.
|
||||
#
|
||||
# Per docs/INVIOLABLE-PRINCIPLES.md principle #3: Crossplane is the ONLY day-2
|
||||
# IaC. After OpenTofu Phase-0 creates the initial Hetzner load balancer for
|
||||
# the Sovereign's control plane, additional load balancers (e.g. per-Org
|
||||
# vcluster ingress, regional DR replicas) are managed via this Composition.
|
||||
#
|
||||
# Canonical XRD group: compose.openova.io/v1alpha1 (per Pass 42/48 + BLUEPRINT-AUTHORING.md §8).
|
||||
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: hetzner-load-balancer.compose.openova.io
|
||||
labels:
|
||||
catalyst.openova.io/provider: hetzner
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: compose.openova.io/v1alpha1
|
||||
kind: XHetznerLoadBalancer
|
||||
|
||||
resources:
|
||||
- name: load-balancer
|
||||
base:
|
||||
apiVersion: load_balancer.hcloud.crossplane.io/v1alpha1
|
||||
kind: LoadBalancer
|
||||
spec:
|
||||
forProvider:
|
||||
loadBalancerType: lb11
|
||||
algorithm:
|
||||
- type: round_robin
|
||||
labels:
|
||||
"catalyst.openova.io/managed-by": crossplane
|
||||
providerConfigRef:
|
||||
name: default
|
||||
patches:
|
||||
- fromFieldPath: spec.parameters.name
|
||||
toFieldPath: metadata.name
|
||||
- fromFieldPath: spec.parameters.location
|
||||
toFieldPath: spec.forProvider.location
|
||||
- fromFieldPath: spec.parameters.loadBalancerType
|
||||
toFieldPath: spec.forProvider.loadBalancerType
|
||||
- fromFieldPath: spec.parameters.networkID
|
||||
toFieldPath: spec.forProvider.network
|
||||
- fromFieldPath: spec.parameters.sovereignFQDN
|
||||
toFieldPath: spec.forProvider.labels["catalyst.openova.io/sovereign"]
|
||||
|
||||
- name: http-service
|
||||
base:
|
||||
apiVersion: load_balancer_service.hcloud.crossplane.io/v1alpha1
|
||||
kind: LoadBalancerService
|
||||
spec:
|
||||
forProvider:
|
||||
protocol: tcp
|
||||
listenPort: 80
|
||||
destinationPort: 31080
|
||||
providerConfigRef:
|
||||
name: default
|
||||
patches:
|
||||
- fromFieldPath: spec.parameters.name
|
||||
toFieldPath: metadata.name
|
||||
transforms:
|
||||
- type: string
|
||||
string:
|
||||
fmt: "%s-http"
|
||||
- fromFieldPath: status.atProvider.id
|
||||
toFieldPath: spec.forProvider.loadBalancerID
|
||||
policy:
|
||||
fromFieldPath: Required
|
||||
|
||||
- name: https-service
|
||||
base:
|
||||
apiVersion: load_balancer_service.hcloud.crossplane.io/v1alpha1
|
||||
kind: LoadBalancerService
|
||||
spec:
|
||||
forProvider:
|
||||
protocol: tcp
|
||||
listenPort: 443
|
||||
destinationPort: 31443
|
||||
providerConfigRef:
|
||||
name: default
|
||||
patches:
|
||||
- fromFieldPath: spec.parameters.name
|
||||
toFieldPath: metadata.name
|
||||
transforms:
|
||||
- type: string
|
||||
string:
|
||||
fmt: "%s-https"
|
||||
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
80
platform/crossplane/compositions/composition-network.yaml
Normal file
80
platform/crossplane/compositions/composition-network.yaml
Normal file
@ -0,0 +1,80 @@
|
||||
# Composition: hetzner-network.compose.openova.io — default realization for
|
||||
# XHetznerNetwork. Renders to provider-hcloud's Network + NetworkSubnet
|
||||
# managed resources. Applied by Crossplane every time an XHetznerNetwork or
|
||||
# HetznerNetwork claim is created.
|
||||
#
|
||||
# Per docs/INVIOLABLE-PRINCIPLES.md principle #3 this Composition is the
|
||||
# ONLY way day-2 networks get created on a Sovereign. The Phase-0 OpenTofu
|
||||
# module creates the bootstrap network exactly once; everything after that
|
||||
# is XHetznerNetwork → Crossplane → Hetzner.
|
||||
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: hetzner-network.compose.openova.io
|
||||
labels:
|
||||
catalyst.openova.io/component: crossplane
|
||||
catalyst.openova.io/composition-family: hetzner
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: compose.openova.io/v1alpha1
|
||||
kind: XHetznerNetwork
|
||||
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
|
||||
resources:
|
||||
# ── 1. Top-level private network (VPC) ────────────────────────────────
|
||||
- name: network
|
||||
base:
|
||||
apiVersion: network.hcloud.crossplane.io/v1alpha1
|
||||
kind: Network
|
||||
spec:
|
||||
forProvider:
|
||||
ipRange: "" # filled by patch from spec.parameters.ipRange
|
||||
providerConfigRef:
|
||||
name: default-hcloud
|
||||
patches:
|
||||
- fromFieldPath: spec.parameters.name
|
||||
toFieldPath: metadata.name
|
||||
- fromFieldPath: spec.parameters.ipRange
|
||||
toFieldPath: spec.forProvider.ipRange
|
||||
- fromFieldPath: spec.providerConfigRef.name
|
||||
toFieldPath: spec.providerConfigRef.name
|
||||
- fromFieldPath: spec.parameters.sovereignFQDN
|
||||
toFieldPath: metadata.labels[catalyst.openova.io/sovereign]
|
||||
- type: ToCompositeFieldPath
|
||||
fromFieldPath: metadata.annotations[crossplane.io/external-name]
|
||||
toFieldPath: status.networkId
|
||||
|
||||
# ── 2. Subnet inside the network ──────────────────────────────────────
|
||||
- name: subnet
|
||||
base:
|
||||
apiVersion: network.hcloud.crossplane.io/v1alpha1
|
||||
kind: NetworkSubnet
|
||||
spec:
|
||||
forProvider:
|
||||
type: cloud
|
||||
networkZone: "" # filled by patch
|
||||
ipRange: "" # filled by patch
|
||||
networkIdSelector:
|
||||
matchControllerRef: true
|
||||
providerConfigRef:
|
||||
name: default-hcloud
|
||||
patches:
|
||||
- fromFieldPath: spec.parameters.name
|
||||
toFieldPath: metadata.name
|
||||
transforms:
|
||||
- type: string
|
||||
string:
|
||||
fmt: "%s-subnet"
|
||||
- fromFieldPath: spec.parameters.networkZone
|
||||
toFieldPath: spec.forProvider.networkZone
|
||||
- fromFieldPath: spec.parameters.subnetIpRange
|
||||
toFieldPath: spec.forProvider.ipRange
|
||||
- fromFieldPath: spec.providerConfigRef.name
|
||||
toFieldPath: spec.providerConfigRef.name
|
||||
- fromFieldPath: spec.parameters.sovereignFQDN
|
||||
toFieldPath: metadata.labels[catalyst.openova.io/sovereign]
|
||||
- type: ToCompositeFieldPath
|
||||
fromFieldPath: spec.forProvider.ipRange
|
||||
toFieldPath: status.subnetIpRange
|
||||
71
platform/crossplane/compositions/composition-server.yaml
Normal file
71
platform/crossplane/compositions/composition-server.yaml
Normal file
@ -0,0 +1,71 @@
|
||||
# Composition: hetzner-server.compose.openova.io — default realization for
|
||||
# XHetznerServer. Renders to provider-hcloud's Server managed resource.
|
||||
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: hetzner-server.compose.openova.io
|
||||
labels:
|
||||
catalyst.openova.io/component: crossplane
|
||||
catalyst.openova.io/composition-family: hetzner
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: compose.openova.io/v1alpha1
|
||||
kind: XHetznerServer
|
||||
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
|
||||
resources:
|
||||
- name: server
|
||||
base:
|
||||
apiVersion: server.hcloud.crossplane.io/v1alpha1
|
||||
kind: Server
|
||||
spec:
|
||||
forProvider:
|
||||
serverType: "" # filled by patch
|
||||
image: "" # filled by patch
|
||||
location: "" # filled by patch
|
||||
sshKeys: [] # filled by patch
|
||||
firewallIds: [] # filled by patch
|
||||
userData: "" # filled by patch
|
||||
network:
|
||||
- networkId: ""
|
||||
ip: ""
|
||||
providerConfigRef:
|
||||
name: default-hcloud
|
||||
patches:
|
||||
- fromFieldPath: spec.parameters.name
|
||||
toFieldPath: metadata.name
|
||||
- fromFieldPath: spec.parameters.serverType
|
||||
toFieldPath: spec.forProvider.serverType
|
||||
- fromFieldPath: spec.parameters.image
|
||||
toFieldPath: spec.forProvider.image
|
||||
- fromFieldPath: spec.parameters.region
|
||||
toFieldPath: spec.forProvider.location
|
||||
- fromFieldPath: spec.parameters.sshKeyName
|
||||
toFieldPath: spec.forProvider.sshKeys[0]
|
||||
- fromFieldPath: spec.parameters.firewallIds
|
||||
toFieldPath: spec.forProvider.firewallIds
|
||||
- fromFieldPath: spec.parameters.userData
|
||||
toFieldPath: spec.forProvider.userData
|
||||
- fromFieldPath: spec.parameters.networkId
|
||||
toFieldPath: spec.forProvider.network[0].networkId
|
||||
- fromFieldPath: spec.parameters.privateIp
|
||||
toFieldPath: spec.forProvider.network[0].ip
|
||||
- fromFieldPath: spec.parameters.placementGroupName
|
||||
toFieldPath: spec.forProvider.placementGroupName
|
||||
- fromFieldPath: spec.providerConfigRef.name
|
||||
toFieldPath: spec.providerConfigRef.name
|
||||
- fromFieldPath: spec.parameters.sovereignFQDN
|
||||
toFieldPath: metadata.labels[catalyst.openova.io/sovereign]
|
||||
- fromFieldPath: spec.parameters.role
|
||||
toFieldPath: metadata.labels[catalyst.openova.io/role]
|
||||
- type: ToCompositeFieldPath
|
||||
fromFieldPath: metadata.annotations[crossplane.io/external-name]
|
||||
toFieldPath: status.serverId
|
||||
- type: ToCompositeFieldPath
|
||||
fromFieldPath: status.atProvider.ipv4Address
|
||||
toFieldPath: status.publicIPv4
|
||||
- type: ToCompositeFieldPath
|
||||
fromFieldPath: status.atProvider.ipv6Address
|
||||
toFieldPath: status.publicIPv6
|
||||
111
platform/crossplane/compositions/xrd-firewall.yaml
Normal file
111
platform/crossplane/compositions/xrd-firewall.yaml
Normal file
@ -0,0 +1,111 @@
|
||||
# XRD: XHetznerFirewall — Catalyst's canonical Hetzner firewall composite.
|
||||
#
|
||||
# Wraps:
|
||||
# - hcloud_firewall (the firewall and its rules)
|
||||
#
|
||||
# Per docs/BLUEPRINT-AUTHORING.md §8 the canonical XRD group is
|
||||
# compose.openova.io/v1alpha1.
|
||||
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xhetznerfirewalls.compose.openova.io
|
||||
labels:
|
||||
catalyst.openova.io/component: crossplane
|
||||
catalyst.openova.io/composition-family: hetzner
|
||||
spec:
|
||||
group: compose.openova.io
|
||||
names:
|
||||
kind: XHetznerFirewall
|
||||
plural: xhetznerfirewalls
|
||||
claimNames:
|
||||
kind: HetznerFirewall
|
||||
plural: hetznerfirewalls
|
||||
defaultCompositionRef:
|
||||
name: hetzner-firewall.compose.openova.io
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
required: [spec]
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: [parameters]
|
||||
properties:
|
||||
parameters:
|
||||
type: object
|
||||
required: [name, rules]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: |
|
||||
Firewall name in Hetzner Cloud. Convention:
|
||||
catalyst-<sovereign-fqdn-with-dashes>-fw
|
||||
pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
|
||||
sovereignFQDN:
|
||||
type: string
|
||||
description: |
|
||||
FQDN of the owning Sovereign — written as label.
|
||||
rules:
|
||||
type: array
|
||||
description: |
|
||||
Firewall rules. Order does not matter — Hetzner
|
||||
evaluates all rules and accepts on first match.
|
||||
minItems: 1
|
||||
items:
|
||||
type: object
|
||||
required: [direction, protocol]
|
||||
properties:
|
||||
direction:
|
||||
type: string
|
||||
enum: [in, out]
|
||||
protocol:
|
||||
type: string
|
||||
enum: [tcp, udp, icmp, esp, gre]
|
||||
port:
|
||||
type: string
|
||||
description: |
|
||||
Single port (e.g. "443"), range (e.g.
|
||||
"30000-32767"), or omitted for icmp/esp/gre.
|
||||
sourceIps:
|
||||
type: array
|
||||
description: |
|
||||
Source CIDR list for direction=in. Catalyst
|
||||
convention: ["0.0.0.0/0", "::/0"] for public,
|
||||
operator-specific CIDR for SSH (22).
|
||||
items:
|
||||
type: string
|
||||
destinationIps:
|
||||
type: array
|
||||
description: |
|
||||
Destination CIDR list for direction=out.
|
||||
items:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
providerConfigRef:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
default:
|
||||
name: default-hcloud
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
firewallId:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner numeric firewall ID. Servers (XHetznerServer)
|
||||
reference this via firewallIds to apply the rules.
|
||||
additionalPrinterColumns:
|
||||
- name: FIREWALL-ID
|
||||
type: string
|
||||
jsonPath: .status.firewallId
|
||||
- name: AGE
|
||||
type: date
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
141
platform/crossplane/compositions/xrd-loadbalancer.yaml
Normal file
141
platform/crossplane/compositions/xrd-loadbalancer.yaml
Normal file
@ -0,0 +1,141 @@
|
||||
# XRD: XHetznerLoadBalancer — Catalyst's canonical Hetzner load-balancer
|
||||
# composite. Wraps:
|
||||
# - hcloud_load_balancer
|
||||
# - hcloud_load_balancer_network (attaches LB to private network)
|
||||
# - hcloud_load_balancer_target (zero or more — typically the cp/worker servers)
|
||||
# - hcloud_load_balancer_service (zero or more — port mappings)
|
||||
#
|
||||
# The expected use cases on a Sovereign post-Phase-1:
|
||||
# - Adding a regional ingress LB when expanding to a new region
|
||||
# - Adding service-specific LBs (e.g. dedicated TURN/STUN LB for stunner)
|
||||
# - Adopting the Phase-0-provisioned LB by external-name
|
||||
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xhetznerloadbalancers.compose.openova.io
|
||||
labels:
|
||||
catalyst.openova.io/component: crossplane
|
||||
catalyst.openova.io/composition-family: hetzner
|
||||
spec:
|
||||
group: compose.openova.io
|
||||
names:
|
||||
kind: XHetznerLoadBalancer
|
||||
plural: xhetznerloadbalancers
|
||||
claimNames:
|
||||
kind: HetznerLoadBalancer
|
||||
plural: hetznerloadbalancers
|
||||
defaultCompositionRef:
|
||||
name: hetzner-load-balancer.compose.openova.io
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
required: [spec]
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: [parameters]
|
||||
properties:
|
||||
parameters:
|
||||
type: object
|
||||
required: [name, type, region, networkId, algorithm]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: |
|
||||
LB name in Hetzner. Convention:
|
||||
catalyst-<sovereign-fqdn-with-dashes>-lb
|
||||
pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
|
||||
sovereignFQDN:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner LB type slug — lb11, lb21, lb31.
|
||||
enum: [lb11, lb21, lb31]
|
||||
region:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner location.
|
||||
pattern: '^[a-z]+[0-9]?$'
|
||||
algorithm:
|
||||
type: string
|
||||
enum: [round_robin, least_connections]
|
||||
networkId:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner numeric network ID — the LB attaches to this
|
||||
private network so it can reach servers via private IPs.
|
||||
targetServerIds:
|
||||
type: array
|
||||
description: |
|
||||
Hetzner numeric server IDs to add as backend targets.
|
||||
Typically status.serverId from one or more XHetznerServer.
|
||||
items:
|
||||
type: string
|
||||
services:
|
||||
type: array
|
||||
description: |
|
||||
Listener-port mappings. Catalyst convention for the
|
||||
bootstrap LB is two services:
|
||||
80 → 31080 (HTTP, Cilium Gateway NodePort)
|
||||
443 → 31443 (HTTPS, Cilium Gateway NodePort)
|
||||
minItems: 1
|
||||
items:
|
||||
type: object
|
||||
required: [protocol, listenPort, destinationPort]
|
||||
properties:
|
||||
protocol:
|
||||
type: string
|
||||
enum: [tcp, http, https]
|
||||
listenPort:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
destinationPort:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
proxyProtocol:
|
||||
type: boolean
|
||||
default: false
|
||||
providerConfigRef:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
default:
|
||||
name: default-hcloud
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
loadBalancerId:
|
||||
type: string
|
||||
ipv4:
|
||||
type: string
|
||||
description: |
|
||||
Public IPv4 of the LB. catalyst-environment-controller
|
||||
reads this and writes A records via Crossplane DNS XR
|
||||
(or Dynadot for omani.works pool domains).
|
||||
ipv6:
|
||||
type: string
|
||||
additionalPrinterColumns:
|
||||
- name: LB-ID
|
||||
type: string
|
||||
jsonPath: .status.loadBalancerId
|
||||
- name: TYPE
|
||||
type: string
|
||||
jsonPath: .spec.parameters.type
|
||||
- name: REGION
|
||||
type: string
|
||||
jsonPath: .spec.parameters.region
|
||||
- name: PUBLIC-IP
|
||||
type: string
|
||||
jsonPath: .status.ipv4
|
||||
- name: AGE
|
||||
type: date
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
111
platform/crossplane/compositions/xrd-network.yaml
Normal file
111
platform/crossplane/compositions/xrd-network.yaml
Normal file
@ -0,0 +1,111 @@
|
||||
# XRD: XHetznerNetwork — Catalyst's canonical Hetzner private network composite.
|
||||
#
|
||||
# Wraps:
|
||||
# - hcloud_network (the private VPC range)
|
||||
# - hcloud_network_subnet (a subnet inside the network, in a network zone)
|
||||
#
|
||||
# Per docs/BLUEPRINT-AUTHORING.md §8 the canonical XRD group is
|
||||
# compose.openova.io/v1alpha1 — NOT catalyst.openova.io (that's the Catalyst CRD
|
||||
# group used for Sovereign/Organization/Environment/Application/Blueprint).
|
||||
#
|
||||
# Per docs/INVIOLABLE-PRINCIPLES.md principle #4 every cloud-specific value
|
||||
# (CIDR, subnet range, network zone) is a schema field — no hardcoded values.
|
||||
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xhetznernetworks.compose.openova.io
|
||||
labels:
|
||||
catalyst.openova.io/component: crossplane
|
||||
catalyst.openova.io/composition-family: hetzner
|
||||
spec:
|
||||
group: compose.openova.io
|
||||
names:
|
||||
kind: XHetznerNetwork
|
||||
plural: xhetznernetworks
|
||||
claimNames:
|
||||
kind: HetznerNetwork
|
||||
plural: hetznernetworks
|
||||
defaultCompositionRef:
|
||||
name: hetzner-network.compose.openova.io
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
required: [spec]
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: [parameters]
|
||||
properties:
|
||||
parameters:
|
||||
type: object
|
||||
required: [name, ipRange, subnetIpRange, networkZone]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: |
|
||||
Network name as it appears in the Hetzner Cloud console.
|
||||
Convention: catalyst-<sovereign-fqdn-with-dashes>-net
|
||||
pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
|
||||
ipRange:
|
||||
type: string
|
||||
description: |
|
||||
Top-level VPC CIDR. Catalyst default 10.0.0.0/16.
|
||||
pattern: '^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$'
|
||||
subnetIpRange:
|
||||
type: string
|
||||
description: |
|
||||
Subnet CIDR — must be inside ipRange. Catalyst default 10.0.1.0/24.
|
||||
pattern: '^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$'
|
||||
networkZone:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner network zone — eu-central, us-east, us-west, ap-southeast.
|
||||
enum: [eu-central, us-east, us-west, ap-southeast]
|
||||
sovereignFQDN:
|
||||
type: string
|
||||
description: |
|
||||
FQDN of the owning Sovereign — written as a label so
|
||||
operators can grep all resources for one Sovereign.
|
||||
description: |
|
||||
Hetzner private network parameters. All fields are required;
|
||||
no defaults are applied so the caller is forced to make
|
||||
the topology explicit.
|
||||
providerConfigRef:
|
||||
type: object
|
||||
description: |
|
||||
Override the default ProviderConfig (default-hcloud).
|
||||
Useful for multi-account Sovereigns.
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
default:
|
||||
name: default-hcloud
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
networkId:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner numeric network ID, populated after the network
|
||||
exists. Other XRs (XHetznerServer, XHetznerLoadBalancer)
|
||||
reference this to attach themselves to the network.
|
||||
subnetIpRange:
|
||||
type: string
|
||||
additionalPrinterColumns:
|
||||
- name: NETWORK-ID
|
||||
type: string
|
||||
jsonPath: .status.networkId
|
||||
- name: ZONE
|
||||
type: string
|
||||
jsonPath: .spec.parameters.networkZone
|
||||
- name: CIDR
|
||||
type: string
|
||||
jsonPath: .spec.parameters.ipRange
|
||||
- name: AGE
|
||||
type: date
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
149
platform/crossplane/compositions/xrd-server.yaml
Normal file
149
platform/crossplane/compositions/xrd-server.yaml
Normal file
@ -0,0 +1,149 @@
|
||||
# XRD: XHetznerServer — Catalyst's canonical Hetzner server (VM) composite.
|
||||
#
|
||||
# Wraps:
|
||||
# - hcloud_server (the VM, including network attachment)
|
||||
#
|
||||
# The expected use cases on a Sovereign post-Phase-1:
|
||||
# - Adding a worker node to an existing host cluster
|
||||
# - Provisioning an additional control-plane node when scaling to HA
|
||||
# - Provisioning servers for new building blocks (rtz, dmz) added later
|
||||
#
|
||||
# Per docs/INVIOLABLE-PRINCIPLES.md principle #4 every cloud-specific
|
||||
# value (region, server_type, image) is a schema field — no hardcoding.
|
||||
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xhetznerservers.compose.openova.io
|
||||
labels:
|
||||
catalyst.openova.io/component: crossplane
|
||||
catalyst.openova.io/composition-family: hetzner
|
||||
spec:
|
||||
group: compose.openova.io
|
||||
names:
|
||||
kind: XHetznerServer
|
||||
plural: xhetznerservers
|
||||
claimNames:
|
||||
kind: HetznerServer
|
||||
plural: hetznerservers
|
||||
defaultCompositionRef:
|
||||
name: hetzner-server.compose.openova.io
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
required: [spec]
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: [parameters]
|
||||
properties:
|
||||
parameters:
|
||||
type: object
|
||||
required: [name, serverType, image, region, networkId, sshKeyName]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: |
|
||||
Server name in Hetzner Cloud. Convention:
|
||||
catalyst-<sovereign-fqdn-with-dashes>-{cp,w}<index>
|
||||
pattern: '^[a-z0-9][a-z0-9-]{1,62}$'
|
||||
sovereignFQDN:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
description: |
|
||||
Catalyst role label — written to the server's labels
|
||||
and to the catalyst.openova.io/role label. Used by
|
||||
kubectl selectors and observability dashboards.
|
||||
enum: [control-plane, worker, edge]
|
||||
serverType:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner server type slug — cx22, cx32, cx42, cpx21,
|
||||
cpx31, cpx41, ccx13, ccx23, ccx33, ccx43, etc.
|
||||
pattern: '^(cx|cpx|ccx)[0-9]{2}$'
|
||||
image:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner image slug — ubuntu-24.04, ubuntu-22.04,
|
||||
debian-12, fedora-40, etc.
|
||||
pattern: '^[a-z]+-[0-9]+\.?[0-9]*$'
|
||||
region:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner location — fsn1, nbg1, hel1, ash, hil, sin.
|
||||
pattern: '^[a-z]+[0-9]?$'
|
||||
networkId:
|
||||
type: string
|
||||
description: |
|
||||
Hetzner numeric network ID to attach this server to —
|
||||
typically status.networkId from an XHetznerNetwork.
|
||||
privateIp:
|
||||
type: string
|
||||
description: |
|
||||
Static IPv4 inside the network's subnet.
|
||||
pattern: '^([0-9]{1,3}\.){3}[0-9]{1,3}$'
|
||||
sshKeyName:
|
||||
type: string
|
||||
description: |
|
||||
Name of an existing Hetzner SSH key (created by the
|
||||
Phase-0 OpenTofu module) that can SSH into this server.
|
||||
firewallIds:
|
||||
type: array
|
||||
description: |
|
||||
Hetzner numeric firewall IDs to apply to this server.
|
||||
items:
|
||||
type: string
|
||||
userData:
|
||||
type: string
|
||||
description: |
|
||||
cloud-init user-data script. Catalyst leaves this
|
||||
empty for day-2 nodes — the cloud-init that joins
|
||||
a worker to the existing k3s cluster is templated
|
||||
by the catalyst-environment-controller using the
|
||||
bootstrap k3s_token retrieved from OpenBao.
|
||||
placementGroupName:
|
||||
type: string
|
||||
description: |
|
||||
Optional placement-group name — for spread/anti-affinity.
|
||||
providerConfigRef:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
default:
|
||||
name: default-hcloud
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
serverId:
|
||||
type: string
|
||||
publicIPv4:
|
||||
type: string
|
||||
publicIPv6:
|
||||
type: string
|
||||
privateIPv4:
|
||||
type: string
|
||||
additionalPrinterColumns:
|
||||
- name: SERVER-ID
|
||||
type: string
|
||||
jsonPath: .status.serverId
|
||||
- name: ROLE
|
||||
type: string
|
||||
jsonPath: .spec.parameters.role
|
||||
- name: TYPE
|
||||
type: string
|
||||
jsonPath: .spec.parameters.serverType
|
||||
- name: REGION
|
||||
type: string
|
||||
jsonPath: .spec.parameters.region
|
||||
- name: PUBLIC-IP
|
||||
type: string
|
||||
jsonPath: .status.publicIPv4
|
||||
- name: AGE
|
||||
type: date
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
@ -10,6 +10,12 @@ COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /catalyst-api ./cmd/api
|
||||
# catalyst-dns helper — invoked by the OpenTofu module's null_resource.dns_pool
|
||||
# via local-exec at Phase-0 apply time. Lives at /usr/local/bin/catalyst-dns
|
||||
# in the runtime image so the OpenTofu run (which executes inside this same
|
||||
# container — the catalyst-api Pod is also the OpenTofu runner) can shell out
|
||||
# to it. See infra/hetzner/main.tf comments around null_resource.dns_pool.
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /catalyst-dns ./cmd/catalyst-dns
|
||||
|
||||
FROM docker.io/library/alpine:3.20
|
||||
|
||||
@ -28,6 +34,7 @@ RUN apk add --no-cache ca-certificates curl bash \
|
||||
&& chmod +x /usr/local/bin/helm
|
||||
|
||||
COPY --from=build /catalyst-api /catalyst-api
|
||||
COPY --from=build /catalyst-dns /usr/local/bin/catalyst-dns
|
||||
RUN adduser -D -u 65534 nonroot
|
||||
USER 65534:65534
|
||||
EXPOSE 8080
|
||||
|
||||
78
products/catalyst/bootstrap/api/cmd/catalyst-dns/main.go
Normal file
78
products/catalyst/bootstrap/api/cmd/catalyst-dns/main.go
Normal file
@ -0,0 +1,78 @@
|
||||
// catalyst-dns — small Go binary the OpenTofu module's null_resource.dns_pool
|
||||
// invokes via local-exec when domain_mode=pool.
|
||||
//
|
||||
// Per docs/INVIOLABLE-PRINCIPLES.md principle #3: cloud APIs are NOT called
|
||||
// from bespoke Go in the catalyst-api process. The narrow exception is this
|
||||
// binary, which is invoked by OpenTofu (the canonical IaC) as if it were a
|
||||
// terraform-dynadot provider — the Dynadot terraform provider does not
|
||||
// exist on the registry, so we ship our own helper. The contract here is
|
||||
// the same as a terraform provider would expose: receive inputs via env
|
||||
// vars, write the records, exit 0 on success.
|
||||
//
|
||||
// Inputs (env vars):
|
||||
// DYNADOT_API_KEY — Dynadot account API key (account-scoped, covers
|
||||
// every domain owned by the account)
|
||||
// DYNADOT_API_SECRET — Dynadot account API secret
|
||||
// DOMAIN — Pool domain (e.g. omani.works)
|
||||
// SUBDOMAIN — Sovereign subdomain (e.g. omantel)
|
||||
// LB_IP — Hetzner load-balancer IPv4 the records point at
|
||||
//
|
||||
// Output: writes the canonical 6-record set per dynadot.AddSovereignRecords:
|
||||
// *.<SUBDOMAIN>.<DOMAIN> A → <LB_IP>
|
||||
// console.<SUBDOMAIN>.<DOMAIN> A → <LB_IP>
|
||||
// gitea.<SUBDOMAIN>.<DOMAIN> A → <LB_IP>
|
||||
// harbor.<SUBDOMAIN>.<DOMAIN> A → <LB_IP>
|
||||
// admin.<SUBDOMAIN>.<DOMAIN> A → <LB_IP>
|
||||
// api.<SUBDOMAIN>.<DOMAIN> A → <LB_IP>
|
||||
//
|
||||
// Idempotent: re-running with the same inputs writes the same records again
|
||||
// (Dynadot dedupes by (subdomain, type) under add_dns_to_current_setting=yes).
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/openova-io/openova/products/catalyst/bootstrap/api/internal/dynadot"
|
||||
)
|
||||
|
||||
func main() {
|
||||
apiKey := os.Getenv("DYNADOT_API_KEY")
|
||||
apiSecret := os.Getenv("DYNADOT_API_SECRET")
|
||||
domain := os.Getenv("DOMAIN")
|
||||
subdomain := os.Getenv("SUBDOMAIN")
|
||||
lbIP := os.Getenv("LB_IP")
|
||||
|
||||
if apiKey == "" || apiSecret == "" {
|
||||
fail("DYNADOT_API_KEY and DYNADOT_API_SECRET must be set")
|
||||
}
|
||||
if domain == "" {
|
||||
fail("DOMAIN must be set (e.g. omani.works)")
|
||||
}
|
||||
if subdomain == "" {
|
||||
fail("SUBDOMAIN must be set (e.g. omantel)")
|
||||
}
|
||||
if lbIP == "" {
|
||||
fail("LB_IP must be set (the Hetzner load balancer IPv4)")
|
||||
}
|
||||
if !dynadot.IsManagedDomain(domain) {
|
||||
fail(fmt.Sprintf("DOMAIN %q is not in the managed-domain allowlist; refusing to write records", domain))
|
||||
}
|
||||
|
||||
client := dynadot.New(apiKey, apiSecret)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
if err := client.AddSovereignRecords(ctx, domain, subdomain, lbIP); err != nil {
|
||||
fail(fmt.Sprintf("write DNS: %v", err))
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Wrote 6 A records for *.%s.%s → %s via Dynadot\n", subdomain, domain, lbIP)
|
||||
}
|
||||
|
||||
func fail(msg string) {
|
||||
fmt.Fprintf(os.Stderr, "catalyst-dns: %s\n", msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user