# bp-cert-manager-powerdns-webhook — Catalyst bootstrap-kit Blueprint #49.
# (Slot 36 was reserved in the W2.K0 forward-declared DAG for `bp-stunner`;
# this Phase-2 webhook lands at slot 49 — first free slot after the W2.K4
# forward declarations end at 48. Source of truth: scripts/expected-
# bootstrap-deps.yaml.)
# DNS-01 ACME solver against contabo's central PowerDNS (authoritative for
# omani.works) for wildcard TLS on *.${SOVEREIGN_FQDN}. Supersedes
# bp-cert-manager-dynadot-webhook (slot 49b, dropped in this PR).
# Closes openova#373.
#
# ──────────────────────────────────────────────────────────────────────────
# Why this slot exists
# ──────────────────────────────────────────────────────────────────────────
# The per-Sovereign Gateway in 01-cilium.yaml requests a wildcard
# Certificate covering `*.${SOVEREIGN_FQDN}` — e.g. `*.otechN.omani.works`.
# omani.works itself is registered at Dynadot but is delegated to
# ns1/2/3.openova.io which run on contabo's PowerDNS in the
# openova-system namespace. Dynadot is NOT the API-level authority for
# omani.works subdomains; contabo PowerDNS is.
#
# When Let's Encrypt validates a DNS-01 challenge for `*.otechN.omani.works`,
# its resolvers walk the public DNS chain: Dynadot → ns1/2/3.openova.io
# (contabo PowerDNS). Until pool-domain-manager has committed the per-
# Sovereign NS delegation into contabo PowerDNS (and that delegation has
# propagated), the Sovereign's own PowerDNS is INVISIBLE on the public
# chain — LE queries contabo, gets NXDOMAIN, and the cert never issues.
#
# Caught live on otech43–46: manual workaround was to seed the challenge
# TXT record directly in contabo PowerDNS. This blueprint automates that
# write path: every Sovereign's cert-manager webhook calls contabo's
# PowerDNS API at https://pdns.openova.io to PATCH the challenge TXT
# record, regardless of whether the Sovereign's own DNS delegation has
# sealed yet.
#
# ──────────────────────────────────────────────────────────────────────────
# Wiring
# ──────────────────────────────────────────────────────────────────────────
# Wrapper chart: platform/cert-manager-powerdns-webhook/chart/
# Catalyst-curated values: platform/cert-manager-powerdns-webhook/chart/values.yaml
# Reconciled by: Flux on the new Sovereign's k3s control plane.
#
# dependsOn:
#   - bp-cert-manager — provides the cert-manager.io CRDs + controllers.
#                       Without this the ClusterIssuer + Certificate
#                       resources templated by this blueprint can't apply.
#
# Note: this slot does NOT depend on bp-powerdns. The webhook calls
# contabo's central PowerDNS (https://pdns.openova.io) — an out-of-cluster
# endpoint — not the Sovereign's local PowerDNS. The Sovereign's
# bp-powerdns slot (11) is still installed (it backs the Sovereign's own
# subzone for app-level records via bp-external-dns), but it is NOT in
# the cert-issuance path.
#
# Credentials: the chart's apiKeySecretRef points at a Secret named
# `powerdns-api-credentials` in the cert-manager namespace. That Secret's
# `api-key` value MUST match the API key configured on contabo's central
# PowerDNS. It is provisioned onto every Sovereign by cloud-init at
# control-plane boot time (mirrors the dynadot-api-credentials seeding
# pattern; see infra/hetzner/cloudinit-control-plane.tftpl).
#
# Per docs/INVIOLABLE-PRINCIPLES.md #4 ("never hardcode") every URL/zone
# is operator-overridable. ${SOVEREIGN_FQDN} is substituted by Flux
# envsubst at the per-Sovereign apply time; contabo's bootstrap path
# does NOT include this template (per ADR-0001 §9.4 contabo stays on
# the legacy Traefik + per-host HTTP-01 stack).

---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: bp-cert-manager-powerdns-webhook
  namespace: flux-system
spec:
  type: oci
  interval: 15m
  url: oci://ghcr.io/openova-io
  secretRef:
    name: ghcr-pull
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: bp-cert-manager-powerdns-webhook
  namespace: flux-system
spec:
  interval: 15m
  releaseName: cert-manager-powerdns-webhook
  # Co-located with cert-manager so the webhook's serving Certificate
  # (issued by the chart's selfSigned + CA Issuers) and APIService
  # caBundle injection live in the same namespace cert-manager itself
  # watches. Mirrors upstream chart convention.
  targetNamespace: cert-manager
  dependsOn:
    - name: bp-cert-manager
  chart:
    spec:
      chart: bp-cert-manager-powerdns-webhook
      version: 1.0.4
      sourceRef:
        kind: HelmRepository
        name: bp-cert-manager-powerdns-webhook
        namespace: flux-system
  # Event-driven install: the chart's ClusterIssuer template uses a
  # post-install Helm hook that runs AFTER cert-manager's CRDs land,
  # so blocking on Helm `--wait` for the leaf Certificate to reach
  # Ready is unnecessary. Replaces blanket spec.timeout band-aids.
  install:
    disableWait: true
    remediation:
      retries: 3
  upgrade:
    disableWait: true
    remediation:
      retries: 3
  values:
    # ─── PowerDNS API endpoint ──────────────────────────────────────────
    # The chart's default value (https://pdns.openova.io — contabo's
    # central PowerDNS, authoritative for omani.works) is correct for
    # every Sovereign in the omani.works pool, so no override is needed
    # here. Operators provisioning a Sovereign in a non-omani.works pool
    # add a `powerdns: { host: "https://pdns.<other-pool>" }` override
    # in their per-cluster overlay.

    # ─── Paired ClusterIssuer ───────────────────────────────────────────
    # Operator opts in here; the chart's default render skips this
    # resource (skip-render pattern, lesson from #387 follow-up #402).
    clusterIssuer:
      enabled: true
      name: letsencrypt-dns01-prod-powerdns
      email: "ops@${SOVEREIGN_FQDN}"
      acmeServer: "https://acme-v02.api.letsencrypt.org/directory"
