#cloud-config
# Generated from dtm deploy/samples/bicep/dual-region/cloud-init.yaml at 911067a4. Do not edit here.
# Hayami DTM node first-boot configuration (customData).
#
# main.bicep in this directory loads this file with loadTextContent(), replaces
# the nine placeholders below (the double-at-sign markers in the
# /etc/dtm/arm-bootstrap.env block) with the values for the node being
# deployed, base64-encodes the result, and passes it as the VM's customData.
# Those nine are the only per-deployment values; everything else is identical
# on every node, in every topology.
#
# ONE body serves every node role. NODE_ROLE branches the secret bootstrap:
#   primary   -> kv-secret-ensure (generate-if-absent; needs Key Vault Secrets
#                                  Officer)
#   secondary -> kv-secret        (read-only GET; retries until the primary has
#                                  published, or, for a day-2 join, until this
#                                  node's own RBAC grant has propagated)
#
# SEEDS (a comma-separated host list, possibly empty) becomes the replication
# seeds array: empty means a single-node primary that reaches Ready
# immediately; non-empty means the node joins the listed members and reseeds
# via anti-entropy.
#
# The initial admin password AND the gossip encryption key are generated ON the
# primary VM and written to Key Vault. Neither is ever a template parameter, a
# deployment artifact, or state, so neither appears in your deployment history.
# The gossip key reaches the server through the DTM_REPLICATION_ENCRYPT_KEY
# environment override so it stays out of the group-readable dtm-server.yaml.
package_update: false

# Durable storage: the dedicated Premium SSD data disk (LUN 0) holds
# /var/lib/dtm, formatted ext4 (noatime + data=writeback, both bbolt-friendly).
# Do NOT use disk_setup/fs_setup -- Azure attaches the data disk
# ASYNCHRONOUSLY, often after cloud-init's early stage, so they fail and the
# disk stays raw -> var-lib-dtm.mount never satisfies -> dtm-server never
# starts. Mount the WHOLE disk with x-systemd.makefs so systemd formats it
# WHEN the device appears; device-timeout allows up to 3 min for the attach;
# nofail keeps boot going. Ownership is fixed post-mount by a dtm-server
# ExecStartPre drop-in (see runcmd), not here. Mirrors deploy/arm/*.
mounts:
  - [/dev/disk/azure/scsi1/lun0, /var/lib/dtm, ext4, "defaults,noatime,data=writeback,nofail,x-systemd.makefs,x-systemd.device-timeout=180s", "0", "2"]

write_files:
  # Per-deployment values the module splices in. 0600 root:root -- none are
  # secret (the vault name plus this node's deployment facts + cluster role +
  # seed list) but they are kept tidy and root-only. Sourced by dtm-bootstrap.
  - path: /etc/dtm/arm-bootstrap.env
    permissions: '0600'
    owner: root:root
    content: |
      VAULT=@@VAULT@@
      export KV_DNS_SUFFIX=@@KV_DNS_SUFFIX@@
      PRIVATE_IP=@@PRIVATE_IP@@
      NODE_ID=@@NODE_ID@@
      REGION=@@REGION@@
      NODE_ROLE=@@NODE_ROLE@@
      SEEDS=@@SEEDS@@
      DISCOVERY=@@DISCOVERY@@
      TF_MIRROR=@@TF_MIRROR@@

  # kv-secret-ensure: generate-if-absent a Key Vault secret via the VM's
  # managed identity (IMDS token -> KV data plane). PRIMARY node only -- it
  # holds Key Vault Secrets Officer (the `set` action). Semantics:
  #   - GET the secret; if present (200) print it and NEVER overwrite.
  #   - if absent (404) generate a value, PUT it, print the stored value.
  #   - any other error -> exit non-zero so the caller retries.
  # KIND selects the generator:
  #   password (default) -> secrets.token_urlsafe(33): ~44 URL-safe chars.
  #   gossip             -> STANDARD base64 of 32 random bytes (AES-256). MUST
  #                         be standard base64 (dtm-server decodes with
  #                         base64.StdEncoding; internal/replication/manager.go).
  - path: /usr/local/sbin/kv-secret-ensure
    permissions: '0755'
    owner: root:root
    content: |
      #!/usr/bin/env python3
      """Ensure a Key Vault secret exists; generate + store it if absent."""
      import base64
      import json
      import os
      import secrets
      import sys
      import urllib.error
      import urllib.request

      API_VERSION = "7.4"

      # Key Vault DNS suffix for THIS Azure environment. The Bicep module
      # fills it from environment().suffixes.keyvaultDns into
      # /etc/dtm/arm-bootstrap.env. The literal suffix is deliberately not
      # written anywhere in this file: hardcoding it would pin the node to the
      # public cloud and fail in every sovereign cloud.
      ARM_ENV = "/etc/dtm/arm-bootstrap.env"


      def _kv_dns_suffix():
          """Suffix from the env, else straight out of arm-bootstrap.env.

          Reading the file matters because this script is also run directly,
          by callers that have not sourced that file first. Depending on the
          caller to export the variable would fail those with an error that
          points somewhere else entirely.
          """
          value = os.environ.get("KV_DNS_SUFFIX", "")
          if not value:
              try:
                  with open(ARM_ENV) as handle:
                      for line in handle:
                          line = line.strip()
                          if line.startswith("export KV_DNS_SUFFIX="):
                              value = line.split("=", 1)[1].strip().strip("\"'")
                              break
              except OSError:
                  pass
          if not value:
              sys.exit(f"KV_DNS_SUFFIX unset and not found in {ARM_ENV}")
          # Exactly one leading dot, so the data-plane host below is always
          # <vault><suffix> and never the two concatenated without one.
          return "." + value.lstrip(".")


      KV_DNS = _kv_dns_suffix()
      KV_RESOURCE = "https://" + KV_DNS.lstrip(".")


      def imds_token(resource):
          req = urllib.request.Request(
              f"http://169.254.169.254/metadata/identity/oauth2/token"
              f"?api-version=2018-02-01&resource={resource}",
              headers={"Metadata": "true"},
          )
          with urllib.request.urlopen(req, timeout=30) as resp:
              return json.load(resp)["access_token"]


      def kv_get(vault, name, token):
          req = urllib.request.Request(
              f"https://{vault}{KV_DNS}/secrets/{name}?api-version={API_VERSION}",
              headers={"Authorization": f"Bearer {token}"},
          )
          with urllib.request.urlopen(req, timeout=30) as resp:
              return json.load(resp)["value"]


      def kv_put(vault, name, value, token):
          body = json.dumps({"value": value}).encode("utf-8")
          req = urllib.request.Request(
              f"https://{vault}{KV_DNS}/secrets/{name}?api-version={API_VERSION}",
              data=body, method="PUT",
              headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
          )
          with urllib.request.urlopen(req, timeout=30) as resp:
              return json.load(resp)["value"]


      def generate(kind):
          if kind == "gossip":
              # AES-256 key, STANDARD base64 (base64.StdEncoding in dtm-server).
              return base64.standard_b64encode(secrets.token_bytes(32)).decode("ascii")
          # Default: URL-safe password (EnvironmentFile + shell safe).
          return secrets.token_urlsafe(33)


      def main():
          if len(sys.argv) not in (3, 4):
              print("Usage: kv-secret-ensure <vault> <secret> [password|gossip]", file=sys.stderr)
              sys.exit(2)
          vault, name = sys.argv[1], sys.argv[2]
          kind = sys.argv[3] if len(sys.argv) == 4 else "password"
          try:
              token = imds_token(KV_RESOURCE)
              try:
                  value = kv_get(vault, name, token)
                  print(f"kv-secret-ensure: {name} present - reusing", file=sys.stderr)
                  sys.stdout.write(value)
                  return
              except urllib.error.HTTPError as e:
                  if e.code != 404:
                      raise
              stored = kv_put(vault, name, generate(kind), token)
              print(f"kv-secret-ensure: {name} absent - generated + stored", file=sys.stderr)
              sys.stdout.write(stored)
          except urllib.error.HTTPError as e:
              print(f"kv-secret-ensure: HTTP {e.code} for {name}", file=sys.stderr)
              sys.exit(1)
          except Exception as e:
              print(f"kv-secret-ensure: failed for {name}: {e}", file=sys.stderr)
              sys.exit(1)


      if __name__ == "__main__":
          main()

  # kv-secret: READ-ONLY GET of a Key Vault secret via the VM's managed
  # identity. SECONDARY / joining nodes use this -- their MI holds only Key
  # Vault Secrets User (read), NOT Officer, so they cannot create a secret even
  # by mistake. A 404 (primary has not published the secret yet) or a not-yet-
  # propagated RBAC grant exits non-zero so the dtm-bootstrap retry loop waits.
  - path: /usr/local/sbin/kv-secret
    permissions: '0755'
    owner: root:root
    content: |
      #!/usr/bin/env python3
      """Fetch a Key Vault secret value using the VM's managed identity (read-only)."""
      import json
      import os
      import sys
      import urllib.error
      import urllib.request

      API_VERSION = "7.4"

      # Key Vault DNS suffix for THIS Azure environment. The Bicep module
      # fills it from environment().suffixes.keyvaultDns into
      # /etc/dtm/arm-bootstrap.env. The literal suffix is deliberately not
      # written anywhere in this file: hardcoding it would pin the node to the
      # public cloud and fail in every sovereign cloud.
      ARM_ENV = "/etc/dtm/arm-bootstrap.env"


      def _kv_dns_suffix():
          """Suffix from the env, else straight out of arm-bootstrap.env.

          Reading the file matters because this script is also run directly,
          by callers that have not sourced that file first. Depending on the
          caller to export the variable would fail those with an error that
          points somewhere else entirely.
          """
          value = os.environ.get("KV_DNS_SUFFIX", "")
          if not value:
              try:
                  with open(ARM_ENV) as handle:
                      for line in handle:
                          line = line.strip()
                          if line.startswith("export KV_DNS_SUFFIX="):
                              value = line.split("=", 1)[1].strip().strip("\"'")
                              break
              except OSError:
                  pass
          if not value:
              sys.exit(f"KV_DNS_SUFFIX unset and not found in {ARM_ENV}")
          # Exactly one leading dot, so the data-plane host below is always
          # <vault><suffix> and never the two concatenated without one.
          return "." + value.lstrip(".")


      KV_DNS = _kv_dns_suffix()
      KV_RESOURCE = "https://" + KV_DNS.lstrip(".")


      def imds_token(resource):
          req = urllib.request.Request(
              f"http://169.254.169.254/metadata/identity/oauth2/token"
              f"?api-version=2018-02-01&resource={resource}",
              headers={"Metadata": "true"},
          )
          with urllib.request.urlopen(req, timeout=30) as resp:
              return json.load(resp)["access_token"]


      def kv_get(vault, name, token):
          req = urllib.request.Request(
              f"https://{vault}{KV_DNS}/secrets/{name}?api-version={API_VERSION}",
              headers={"Authorization": f"Bearer {token}"},
          )
          with urllib.request.urlopen(req, timeout=30) as resp:
              return json.load(resp)["value"]


      def main():
          if len(sys.argv) != 3:
              print("Usage: kv-secret <vault> <secret>", file=sys.stderr)
              sys.exit(2)
          vault, name = sys.argv[1], sys.argv[2]
          try:
              token = imds_token(KV_RESOURCE)
              value = kv_get(vault, name, token)
              sys.stdout.write(value)
          except urllib.error.HTTPError as e:
              print(f"kv-secret: HTTP {e.code} for {name}", file=sys.stderr)
              sys.exit(1)
          except Exception as e:
              print(f"kv-secret: failed for {name}: {e}", file=sys.stderr)
              sys.exit(1)


      if __name__ == "__main__":
          main()

  # dtm-bootstrap: first-boot secret bootstrap + config render, run by the
  # dtm-bootstrap.service oneshot below (NOT inline in runcmd). Decoupling it
  # from cloud-final means the retry-until-ready loop (RBAC propagation on both
  # roles + the secondary's wait for the primary to publish) can run for as long
  # as it takes (systemd Restart) without blocking cloud-init past its timeout,
  # which would otherwise leave the node permanently unbootstrapped once
  # cloud-init gave up. Idempotent: every step guards
  # on its target file, so a systemd retry after partial success is a no-op.
  - path: /usr/local/sbin/dtm-bootstrap
    permissions: '0755'
    owner: root:root
    content: |
      #!/bin/bash
      set -euo pipefail
      # shellcheck disable=SC1091  # arm-bootstrap.env written by cloud-init; absent at lint time.
      . /etc/dtm/arm-bootstrap.env

      # fetch_secret <name> [password|gossip] -> value on stdout.
      # PRIMARY generates-if-absent (kv-secret-ensure, needs Officer);
      # every other role reads only (kv-secret). On a reader a not-yet-available
      # secret makes the fetch exit non-zero, which under `set -e` fails the
      # whole script so dtm-bootstrap.service retries (Restart=on-failure).
      fetch_secret() {
        local name="$1" kind="${2:-password}"
        if [ "$NODE_ROLE" = "primary" ]; then
          /usr/local/sbin/kv-secret-ensure "$VAULT" "$name" "$kind"
        else
          /usr/local/sbin/kv-secret "$VAULT" "$name"
        fi
      }

      install -d -m 0750 -o root -g dtm /etc/dtm

      # 1) Cluster secrets: initial admin password + gossip encrypt
      #    key. PRIMARY generates+stores them; readers fetch the SAME values so
      #    whichever node wins auth.EnsureDefaultAdmin creates the admin user
      #    with a matching password and all nodes share ONE gossip key. Written
      #    together to a 0600 root:root env file (systemd parses it as root
      #    before dropping to the dtm user); the gossip key rides the
      #    DTM_REPLICATION_ENCRYPT_KEY override so it stays OUT of the group-
      #    readable dtm-server.yaml.
      if [ ! -f /etc/dtm/dtm-server.env ]; then
        if pw=$(fetch_secret dtm-initial-admin-password password) \
           && gossip_key=$(fetch_secret dtm-gossip-key gossip); then
          umask 077
          # Single-quote-escape (' -> '\'') so the systemd EnvironmentFile
          # parser preserves a literal value containing #, ;, etc. sed, not
          # bash param expansion, since /bin/sh on 24.04 is dash.
          pw_escaped=$(printf '%s' "$pw" | sed "s/'/'\\''/g")
          gossip_escaped=$(printf '%s' "$gossip_key" | sed "s/'/'\\''/g")
          {
            printf "DTM_INITIAL_ADMIN_PASSWORD='%s'\n" "$pw_escaped"
            printf "DTM_REPLICATION_ENCRYPT_KEY='%s'\n" "$gossip_escaped"
          } > /etc/dtm/dtm-server.env
          chown root:root /etc/dtm/dtm-server.env
          chmod 0600 /etc/dtm/dtm-server.env
          echo "dtm-bootstrap: wrote /etc/dtm/dtm-server.env"
        else
          echo "dtm-bootstrap: cluster secrets not ready ($NODE_ROLE) - will retry" >&2
          exit 1
        fi
      fi

      # 2) dtm-server.yaml. Replication ENABLED, but the gossip key is
      #    deliberately NOT rendered here: this file is 0640 root:dtm
      #    and the dtm group is not a secret boundary -- the key reaches
      #    dtm-server only via the DTM_REPLICATION_ENCRYPT_KEY override in the
      #    0600 root:root env file written above. seeds is built from SEEDS
      #    (each host rendered as "<host>:7946"); empty SEEDS -> seeds: []
      #    (single-node primary, Ready immediately), non-empty -> the node
      #    joins the listed members and reseeds via anti-entropy.
      if [ ! -f /etc/dtm/dtm-server.yaml ]; then
        seeds_yaml=""
        IFS=',' read -ra seed_hosts <<< "$SEEDS"
        for host in "${seed_hosts[@]}"; do
          [ -n "$host" ] || continue
          if [ -n "$seeds_yaml" ]; then
            seeds_yaml="${seeds_yaml}, "
          fi
          seeds_yaml="${seeds_yaml}\"${host}:7946\""
        done
        {
          printf 'listen:\n'
          printf '  dns_udp: "%s:53"\n' "$PRIVATE_IP"
          printf '  dns_tcp: "%s:53"\n' "$PRIVATE_IP"
          printf '  dot:\n    enabled: false\n    listen_addr: ":853"\n'
          printf '  doh:\n    enabled: false\n    listen_addr: ":443"\n'
          printf '  api: ":8443"\n'
          # Cluster-managed certificate paths, so the API listener has a
          # per-handshake certificate reloader instead of one pinned at boot.
          # Without them a certificate uploaded through the admin API and
          # replicated to this node cannot take effect until the service
          # restarts, and a client that verifies TLS has nothing to trust.
          #
          # Under /var/lib/dtm, not /etc/dtm: dtm-server runs as dtm:dtm,
          # /etc/dtm is 0750 root:dtm and its AppArmor profile grants
          # '/etc/dtm/** r' only, while /var/lib/dtm is 0700 dtm:dtm with
          # '/var/lib/dtm/** rwk'.
          printf '  tls:\n    managed: true\n    cert_file: /var/lib/dtm/api-tls.crt\n    key_file: /var/lib/dtm/api-tls.key\n'
          printf 'forwarders:\n  - 168.63.129.16\n'
          printf 'node:\n  id: "%s"\n  region: "%s"\n' "$NODE_ID" "$REGION"
          printf 'storage:\n  type: bbolt\n  path: /var/lib/dtm/dtm.db\n'
          printf 'cache:\n  enabled: true\n  max_size: 50000\n'
          printf 'replication:\n'
          printf '  enabled: true\n'
          printf '  bind_addr: "%s"\n' "$PRIVATE_IP"
          printf '  bind_port: 7946\n'
          printf '  seeds: [%s]\n' "$seeds_yaml"
          printf '  push_pull_interval_seconds: 10\n'
          # encrypt_key deliberately absent: injected via the
          # DTM_REPLICATION_ENCRYPT_KEY override in the 0600 dtm-server.env.
          printf 'cross_region_poll_interval_seconds: 5\n'
          # DISCOVERY is 'true'/'false' from the module's
          # regionDiscoveryEnabled param; anything unexpected (including
          # unset, for an env file written by an older template) falls
          # back to the historical always-on behaviour.
          if [ "${DISCOVERY:-true}" = "false" ]; then
            printf 'azure:\n  region_discovery_enabled: false\n'
          else
            printf 'azure:\n  region_discovery_enabled: true\n'
          fi
          # Serve the DTM Terraform provider from this cluster's own
          # /terraform/* endpoints. TF_MIRROR is 'true'/'false' from the
          # module's enableTerraformMirror param; anything unexpected
          # (including unset, from an env file written by an older template)
          # falls back to FALSE - those routes are unauthenticated by
          # protocol, so the safe default is the one that does not expose
          # them. Flip it later without redeploying via
          # PUT /api/v1/config/terraform-mirror or the console.
          #
          # The key is written ONLY when true. dtm-server parses this file
          # strictly, so a key an older build does not know is a fatal
          # "config is newer than this binary" - and the image version is a
          # separate deployment choice from the template version. Omitted
          # means false, which is the default.
          if [ "${TF_MIRROR:-false}" = "true" ]; then
            printf 'terraform_mirror_enabled: true\n'
          fi
        } > /etc/dtm/dtm-server.yaml
        chown root:dtm /etc/dtm/dtm-server.yaml
        chmod 0640 /etc/dtm/dtm-server.yaml
        echo "dtm-bootstrap: wrote /etc/dtm/dtm-server.yaml"
      fi

      # 3) Self-signed dtm-ui TLS cert so the admin UI (:8080) comes up. The
      #    image baked /etc/dtm/tls as root:dtm-ui 0750; dtm-ui.service stays
      #    down until cert.pem exists.
      if [ ! -f /etc/dtm/tls/cert.pem ]; then
        openssl req -x509 -newkey rsa:4096 -nodes -days 730 \
          -keyout /etc/dtm/tls/key.pem -out /etc/dtm/tls/cert.pem \
          -subj "/CN=dtm-ui" -addext "subjectAltName=IP:${PRIVATE_IP}" >/dev/null 2>&1
        chown root:dtm-ui /etc/dtm/tls/cert.pem
        chmod 0640 /etc/dtm/tls/cert.pem
        chown dtm-ui:dtm-ui /etc/dtm/tls/key.pem
        chmod 0400 /etc/dtm/tls/key.pem
        echo "dtm-bootstrap: generated self-signed dtm-ui TLS cert"
      fi

      # 4) systemd drop-in: point dtm-ui at the TLS cert/key.
      install -d -m 0755 /etc/systemd/system/dtm-ui.service.d
      {
        printf '[Service]\n'
        printf 'ExecStart=\n'
        printf 'ExecStart=/usr/local/bin/dtm-ui -listen :8080 -api https://127.0.0.1:8443 -tls-cert /etc/dtm/tls/cert.pem -tls-key /etc/dtm/tls/key.pem\n'
      } > /etc/systemd/system/dtm-ui.service.d/10-tls.conf

      systemctl daemon-reload || true
      echo "dtm-bootstrap: all secrets present"

  # dtm-bootstrap.service: oneshot that runs dtm-bootstrap until it succeeds.
  # Named dtm-bootstrap.service so the image's dtm-server.service /
  # dtm-ui.service (Wants=/After=dtm-bootstrap.service) order after it and
  # auto-activate once it reaches active. NO Before=dtm-server/dtm-ui (that
  # historical edge deadlocked with the ExecStartPost restart). Restart=on-
  # failure + RestartSec=60 is the durable retry that covers slow Key Vault
  # RBAC role-assignment propagation and a reader's wait for the primary, both
  # of which can outlast any fixed inline retry budget.
  - path: /etc/systemd/system/dtm-bootstrap.service
    permissions: '0644'
    owner: root:root
    content: |
      [Unit]
      Description=DTM secret bootstrap (Key Vault -> /etc/dtm)
      After=network-online.target
      Wants=network-online.target
      # Bounded retry budget: ~60 attempts over an hour covers first-deploy
      # RBAC propagation and a reader's wait for the primary to publish the
      # cluster secrets, both of which can outlast the old ~5.5-min inline
      # runcmd loop and leave the node permanently unbootstrapped; beyond that it is a
      # real fault worth surfacing as `failed`. MUST live in [Unit] (systemd
      # ignores StartLimit* in [Service]).
      StartLimitIntervalSec=3600
      StartLimitBurst=60

      [Service]
      Type=oneshot
      RemainAfterExit=yes
      ExecStart=/usr/local/sbin/dtm-bootstrap
      # Cap each attempt so a hung urllib request (network blackhole) is killed
      # and retried via Restart= rather than parking in `activating` forever.
      TimeoutStartSec=300
      # Re-trigger the consumers AFTER the env file + config + TLS cert exist.
      ExecStartPost=/bin/systemctl --no-block restart dtm-server.service
      ExecStartPost=/bin/systemctl --no-block restart dtm-ui.service
      Restart=on-failure
      RestartSec=60s

      # Hardening baseline (mirrors the Terraform dtm-bootstrap unit). Runs as
      # root with KV access; writes ONLY /etc/dtm + /etc/dtm/tls. /var/lib/dtm
      # is deliberately excluded so a script fault cannot corrupt bbolt state.
      NoNewPrivileges=yes
      ProtectSystem=strict
      ReadWritePaths=/etc/dtm /etc/dtm/tls /etc/systemd/system
      ProtectHome=yes
      PrivateTmp=yes
      ProtectKernelTunables=yes
      ProtectKernelModules=yes
      ProtectKernelLogs=yes
      ProtectControlGroups=yes
      ProtectClock=yes
      ProtectHostname=yes
      # AF_NETLINK is required by glibc getaddrinfo() for the KV hostname
      # resolution the python helpers perform over HTTPS.
      RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
      RestrictRealtime=yes
      RestrictSUIDSGID=yes
      LockPersonality=yes
      RestrictNamespaces=yes
      SystemCallArchitectures=native
      UMask=0027

      [Install]
      WantedBy=multi-user.target

runcmd:
  - |
    #!/bin/bash
    # DTM first-boot: restore data-disk ownership, then hand the secret
    # bootstrap to dtm-bootstrap.service (background, so a slow Key Vault /
    # RBAC-propagation retry never blocks cloud-final). Idempotent.
    set -eu
    # shellcheck disable=SC1091  # arm-bootstrap.env written above by cloud-init; absent at lint time.
    . /etc/dtm/arm-bootstrap.env

    # The data-disk mount is device-triggered (x-systemd.makefs on the
    # async attach), so it can land AFTER this runcmd -- a chown here would hit
    # the underlying mountpoint, not the freshly-formatted disk. Instead drop in
    # a dtm-server ExecStartPre chown that runs (as root via '+') after
    # var-lib-dtm.mount is active. runcmd is unsandboxed so it can write the
    # unit dir; the daemon-reload below loads it before dtm-bootstrap's
    # ExecStartPost restarts dtm-server. (dtm-bootstrap itself cannot: its
    # ProtectSystem=strict profile deliberately cannot write /var/lib/dtm.)
    install -d -m 0755 /etc/systemd/system/dtm-server.service.d
    {
      printf '[Service]\n'
      printf 'ExecStartPre=+/usr/bin/chown dtm:dtm /var/lib/dtm\n'
      printf 'ExecStartPre=+/usr/bin/chmod 0700 /var/lib/dtm\n'
    } > /etc/systemd/system/dtm-server.service.d/20-datadir-owner.conf

    install -d -m 0750 -o root -g dtm /etc/dtm

    # Enqueue the bootstrap oneshot; --no-block so runcmd never waits on the
    # KV fetch or the RBAC-propagation / wait-for-primary retry loop.
    systemctl daemon-reload || true
    systemctl --no-block enable --now dtm-bootstrap.service || \
      echo "dtm-bootstrap: first enqueue failed - systemd will retry" >&2

    # Belt-and-braces: the consumers are also kicked by dtm-bootstrap's
    # ExecStartPost once it succeeds; these early calls are harmless no-ops
    # (ConditionPathExists not yet met) if the secrets have not landed.
    systemctl --no-block start dtm-server.service || true
    systemctl --no-block start dtm-ui.service || true
