# 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 the Sovereign's OWN PowerDNS for wildcard
# TLS post-handover. Closes openova#373; gates #319 (post-handover renewals).
#
# ──────────────────────────────────────────────────────────────────────────
# Why this slot exists
# ──────────────────────────────────────────────────────────────────────────
# The per-Sovereign Gateway in 01-cilium.yaml requests a wildcard
# Certificate from `letsencrypt-dns01-prod` (currently the dynadot-backed
# issuer in bp-cert-manager). That works during onboarding because the
# parent zone (omani.works) is still owned by openova at Dynadot.
#
# Post-handover, the Sovereign owns its delegated subdomain
# (omantel.omani.works) and serves it from its own PowerDNS replicas.
# cert-manager on the handed-over Sovereign MUST solve DNS-01 against
# its own PowerDNS — otherwise renewals require reachback to openova-
# controlled Dynadot credentials = contabo dependency = ADR-0001 §9.4
# (handover DoD) violation.
#
# This slot installs the bp-cert-manager-powerdns-webhook Blueprint and
# wires it to the per-Sovereign PowerDNS endpoint. The Blueprint's
# paired ClusterIssuer (`letsencrypt-dns01-prod-powerdns`) is the
# target the Sovereign's wildcard Certificate flips to once handover
# completes (#319).
#
# ──────────────────────────────────────────────────────────────────────────
# 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.
#   - bp-powerdns     — provides the PowerDNS REST API endpoint the webhook
#                       talks to. Without this the webhook has no upstream
#                       to call. The bp-powerdns slot also creates the
#                       canonical `powerdns-api-credentials` Secret in
#                       openova-system that the ClusterIssuer references.
#
# 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
    - name: bp-powerdns
  chart:
    spec:
      chart: bp-cert-manager-powerdns-webhook
      version: 1.0.2
      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 ──────────────────────────────────────────
    # In-cluster routing — no external network hop, no public TLS, no
    # ingress dependency. Operators that prefer external routing flip
    # this to https://pdns.${SOVEREIGN_FQDN} via the per-cluster
    # values overlay (clusters/<sovereign>/sovereign-overrides.yaml).
    powerdns:
      host: "http://powerdns.powerdns:8081"
      apiKeySecretRef:
        # Canonical Secret produced by bp-powerdns; see
        # docs/PLATFORM-POWERDNS.md "REST API" section.
        name: powerdns-api-credentials
        key: api-key
        # Webhook reads the secret cross-namespace at challenge time
        # via the K8s clientset (NOT Pod secretKeyRef). The chart's
        # secret-reader ClusterRole grants the required RBAC.
        namespace: openova-system

    # ─── 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"
