feat(swarm): ship host_uuid + swarmctl-port in agent enroll bundle

The rendered /etc/decnet/decnet.ini now carries host-uuid and
swarmctl-port in [agent], which config_ini seeds into DECNET_HOST_UUID
and DECNET_SWARMCTL_PORT. Gives the worker a stable self-identity for
the heartbeat loop — the INI never has to be rewritten because cert
pinning is the real gate (a rotated UUID with a matching CA-signed
cert would still be blocked by SHA-256 fingerprint mismatch against
the stored SwarmHost row).

Also adds DECNET_MASTER_HOST so the agent can find the swarmctl URL
via the INI's existing master-host key.
This commit is contained in:
2026-04-19 21:44:23 -04:00
parent 148e51011c
commit e411063075
2 changed files with 22 additions and 3 deletions

View File

@@ -89,6 +89,13 @@ DECNET_INGEST_LOG_FILE: str | None = os.environ.get("DECNET_INGEST_LOG_FILE", "/
DECNET_SWARM_SYSLOG_PORT: int = _port("DECNET_SWARM_SYSLOG_PORT", 6514) DECNET_SWARM_SYSLOG_PORT: int = _port("DECNET_SWARM_SYSLOG_PORT", 6514)
DECNET_SWARM_MASTER_HOST: str | None = os.environ.get("DECNET_SWARM_MASTER_HOST") DECNET_SWARM_MASTER_HOST: str | None = os.environ.get("DECNET_SWARM_MASTER_HOST")
# Worker-side identity + swarmctl locator, seeded by the enroll bundle's
# /etc/decnet/decnet.ini ([agent] host-uuid / master-host / swarmctl-port).
# The agent heartbeat loop uses these to self-identify to the master.
DECNET_HOST_UUID: str | None = os.environ.get("DECNET_HOST_UUID")
DECNET_MASTER_HOST: str | None = os.environ.get("DECNET_MASTER_HOST")
DECNET_SWARMCTL_PORT: int = _port("DECNET_SWARMCTL_PORT", 8770)
# Ingester batching: how many log rows to accumulate per commit, and the # Ingester batching: how many log rows to accumulate per commit, and the
# max wait (ms) before flushing a partial batch. Larger batches reduce # max wait (ms) before flushing a partial batch. Larger batches reduce
# SQLite write-lock contention; the timeout keeps latency bounded during # SQLite write-lock contention; the timeout keeps latency bounded during

View File

@@ -187,7 +187,12 @@ def _is_excluded(rel: str) -> bool:
return False return False
def _render_decnet_ini(master_host: str, use_ipvlan: bool = False) -> bytes: def _render_decnet_ini(
master_host: str,
host_uuid: str,
use_ipvlan: bool = False,
swarmctl_port: int = 8770,
) -> bytes:
ipvlan_line = f"ipvlan = {'true' if use_ipvlan else 'false'}\n" ipvlan_line = f"ipvlan = {'true' if use_ipvlan else 'false'}\n"
return ( return (
"; Generated by DECNET agent-enrollment bundle.\n" "; Generated by DECNET agent-enrollment bundle.\n"
@@ -199,10 +204,12 @@ def _render_decnet_ini(master_host: str, use_ipvlan: bool = False) -> bytes:
"\n" "\n"
"[agent]\n" "[agent]\n"
f"master-host = {master_host}\n" f"master-host = {master_host}\n"
f"swarmctl-port = {swarmctl_port}\n"
"swarm-syslog-port = 6514\n" "swarm-syslog-port = 6514\n"
"agent-port = 8765\n" "agent-port = 8765\n"
"agent-dir = /etc/decnet/agent\n" "agent-dir = /etc/decnet/agent\n"
"updater-dir = /etc/decnet/updater\n" "updater-dir = /etc/decnet/updater\n"
f"host-uuid = {host_uuid}\n"
).encode() ).encode()
@@ -217,6 +224,7 @@ def _add_bytes(tar: tarfile.TarFile, name: str, data: bytes, mode: int = 0o644)
def _build_tarball( def _build_tarball(
master_host: str, master_host: str,
agent_name: str, agent_name: str,
host_uuid: str,
issued: pki.IssuedCert, issued: pki.IssuedCert,
services_ini: Optional[str], services_ini: Optional[str],
updater_issued: Optional[pki.IssuedCert] = None, updater_issued: Optional[pki.IssuedCert] = None,
@@ -240,7 +248,11 @@ def _build_tarball(
continue continue
tar.add(path, arcname=rel, recursive=False) tar.add(path, arcname=rel, recursive=False)
_add_bytes(tar, "etc/decnet/decnet.ini", _render_decnet_ini(master_host, use_ipvlan)) _add_bytes(
tar,
"etc/decnet/decnet.ini",
_render_decnet_ini(master_host, host_uuid, use_ipvlan),
)
for unit in _SYSTEMD_UNITS: for unit in _SYSTEMD_UNITS:
_add_bytes( _add_bytes(
tar, tar,
@@ -365,7 +377,7 @@ async def create_enroll_bundle(
# 3. Render payload + bootstrap. # 3. Render payload + bootstrap.
tarball = _build_tarball( tarball = _build_tarball(
req.master_host, req.agent_name, issued, req.services_ini, updater_issued, req.master_host, req.agent_name, host_uuid, issued, req.services_ini, updater_issued,
use_ipvlan=req.use_ipvlan, use_ipvlan=req.use_ipvlan,
) )
token = secrets.token_urlsafe(24) token = secrets.token_urlsafe(24)