Documentation · Releases · v.G.1.4

Iris

Iris — v.G.1.4

One server, many hostnames. Each container picks its own.

The pre-v.G.1.4 model pinned exactly one DNS hostname per managed server — every chainweb container on that box shared it. v.G.1.4 unpins that. A managed server can now carry as many hostnames as the operator wants, mixing manual A-records and DuckDNS-backed dynamic hostnames in any combination, and each chainweb container at install time picks which hostname it uses for its --p2p-hostname advertisement and TLS subject. Existing single-DNS operators do nothing — the upgrade backfills their existing hostname into the new table marked PRIMARY, and the chainweb container's FK is auto-pointed at it. DuckDNS systemd-timer continuity, certificate issuance via DNS-01-DuckDNS, and parent-walk inheritance for Tunnelees + self-containers are all preserved end-to-end.

What landed

Multiple hostnames per server

The Server DNS card on a non-Tunnelee, non-self-container node becomes a list view. The operator can attach any number of hostnames to a server — manual A-records and DuckDNS-backed dynamic hostnames in any mix. Adding a second hostname does not remove the first.

One row per hostname is shown with its kind badge (DuckDNS auto-updated or Manual A-record) and the per-row actions: Set primary, Remove, and (for DuckDNS rows) the inline DuckDnsInstallPanel the previous version already shipped — now scoped per-row instead of per-node.

Gold PRIMARY pill chip

Exactly one row at a time carries the PRIMARY pill chip — gold on gold-tinted background, aligned right of the hostname. The primary row is what the InstallWizard pre-selects for new chainweb containers, and what the cluster listing surfaces as the "server hostname" column. Clicking Set primary on another row promotes it and demotes the old primary in a single transaction; the chip moves with it.

Per-container DNS picker at install time

InstallWizard Step-6 (Identity) replaces its free-text --p2p-hostname input with a <select> picker populated from the canonical parent server’s hostname list. Each option shows the hostname plus its kind badge; the row marked PRIMARY is pre-selected. The operator can override the default and pick a non-primary hostname for the new container.

An info row underneath the picker spells out the cert path so the operator sees it at install time: “cert will be issued for <hostname> via HTTP-01 / DNS-01-DuckDNS. The picked hostname becomes the cert subject (CN) and the value passed into --p2p-hostname; both are threaded through body.p2pHostname on the install POST.

DuckDNS continuity guarantee

Operators who configured a DuckDNS hostname before the multi-DNS upgrade keep their setup working unchanged. No operator action required. The systemd refresh timer at /etc/ah-duckdns/ continues to run, the subdomain continues to update, and certificate issuance continues to use DNS-01 via DuckDNS. The backfill migration converts the existing nodes.dns_hostname + nodes.duckdns_subdomain + nodes.duckdns_token_id triple into a single row in the new node_dns_hostnamestable, marked PRIMARY, with the same vault-sealed token. Each chainweb container’s new chainweb_dns_hostname_id FK is auto-pointed at that backfilled row.

Inheritance preserved for Tunnelees + self-containers

Only standalone-master / Tunneler / self-container-parent rows own hostname lists. Tunnelees and self-containers leave their own list empty and READ the canonical parent’s list — the same parent-walk semantics the v.G.0.2 (Prometheus) era introduced, just over a list now instead of a single column. On a child node’s Server DNS card, rows render read-only with an (inherited from <parent label>) annotation and a deep-link up to the parent. The InstallWizard Step-6 picker on a child container shows the parent’s hostname list as the source.

Destructive migration drops legacy single-DNS columns

Migration 075_drop_legacy_dns_columns.sql drops the five legacy single-DNS columns from the nodes table: dns_hostname, dns_hostname_set_at, dns_hostname_set_by, duckdns_subdomain, duckdns_token_id. This is the second of a deliberate two-migration split: Migration A (074) added the node_dns_hostnames table and the chainweb_dns_hostname_id FK column on nodes, and backfilled both non-destructively. The read-path switch in Phase 2 routed every consumer through the new table. Migration B (075) is the cleanup. Pre-v.G.1.4 database dumps are not forward-compatible without re-running the migration chain in order — the legacy columns simply do not exist in the post-075 schema. The legacy singular endpoint /api/admin/nodes/[id]/dns-hostname is retired in the same release; the plural collection endpoints under /dns-hostnames are the only DNS write surface from v.G.1.4 forward.

High-level architecture

New node_dns_hostnames table

A 1:N child of nodes, modelled after the existing host_drives + firewall_rules precedents already in the schema. Each row owns one hostname on one node with these columns:

  • id — TEXT PK
  • node_id — FK into nodes(id) ON DELETE CASCADE
  • hostname — the FQDN
  • kind — CHECK-constrained enum, one of 'manual-a-record' or 'duckdns' (extensible to 'cloudflare' / 'route53' without further schema churn)
  • is_primary— INT 0/1, the resolver’s default pick
  • duckdns_subdomain + duckdns_token_id — populated when kind='duckdns'; the token id points into the existing libsodium-sealed vault
  • actor + timestamp columns ( created_at, created_by, updated_at) for the audit trail
  • UNIQUE(node_id, hostname) and indexes on node_id and (node_id, is_primary)

Plus a parent-side FK column on nodes: chainweb_dns_hostname_id REFERENCES node_dns_hostnames(id)— the per-container pointer that says “this chainweb container picked that hostname.” Mirrors the existing nodes.data_drive_id precedent from the host-drives migration.

FK chain for cert + identity resolution

Cert issuance and --p2p-hostname emission both follow the same chain at install time: chainweb_dns_hostname_id node_dns_hostnames.duckdns_token_id unseal(). The install handler resolves the operator-picked row, threads its hostname into both flags['p2p-hostname'] and the certbot job’s domain payload field, and for DuckDNS-kind rows additionally unseals the vault token for DNS-01 challenge issuance. Each chainweb container gets its own cert subject — two containers picking the same hostname each get their own independent cert, preserving backup/restore symmetry.

Audit trail — three new actions

The audit log gains three new actions alongside the existing node.dns_hostname_change and node.duckdns_install:

  • node.dns_hostname_add — actor + node id + hostname + kind
  • node.dns_hostname_remove — same fields plus the deletion target
  • node.dns_hostname_set_primary — actor + node id + the promoted hostname id (and the demoted one in the detail)

All three are info severity, permanent retention. The legacy actions stay registered for back-compat but stop firing once every reader has switched to the new table.

Operator notes

  • No operator action required for the upgrade. Migration A (074) backfills your existing single-DNS hostname into the new table marked PRIMARY, and Migration B (075) drops the legacy columns once every reader has switched. Your existing chainweb containers continue running with their existing hostname unchanged.
  • Removing the last hostname requires a confirmation step. A server with zero hostnames cannot host new chainweb containers — the install path requires a hostname. The UI surfaces a modal before allowing the last-hostname remove.
  • Removing a hostname currently in use is refused. If any chainweb container’s chainweb_dns_hostname_id still references the hostname, the remove returns 409 with a deep-link list of the referencing containers. Either repoint those containers via the InstallWizard first (which writes a new install body with the new hostname id), or remove the containers, then remove the hostname.
  • Pre-v.G.1.4 database dumps need the migration chain. The 074 + 075 migration pair must run in order. Restoring an older dump bypasses the backfill and the column drops will fail. Run npm run migrate after a restore to bring the schema forward.
  • UFW / firewall is orthogonal. The Cerberus firewall rules from v.G.1.1 bind to ports + protocols, never to hostnames. Adding or removing a hostname does not touch the firewall ruleset; the stoa-tunnel 300-port envelope is independent of how many hostnames live on the server.

Out of scope (deliberately)

  • Cert reuse via bind-mount when two containers share a hostname — deferred. Each container gets its own cert in its own data dir; this preserves backup/restore symmetry with no operator-visible downside.
  • Cloudflare / Route53 provider integrations — the kind enum reserves slots for them but no implementation lands in v.G.1.4. Manual A-records and DuckDNS are the two supported paths today.
  • Public-DNS A-record management. The hub declares which hostnames a managed server claims; the operator handles the actual A-record provisioning at their DNS provider out-of-band. DuckDNS is the one exception (the hub owns the dynamic update via the systemd timer), and that existed pre-v.G.1.4 too.

Further reading