feat(config): add swarmctl-host to INI, env, CLI; drop hardcoded bind from systemd unit

[swarm] swarmctl-host → DECNET_SWARMCTL_HOST so operators set the bind
address once in decnet.ini; `decnet swarmctl` and the systemd unit both
resolve it via envvar — no --host/--port pinned on ExecStart.
This commit is contained in:
2026-04-30 22:16:00 -04:00
parent 57fecb8071
commit 0b5228eb94
6 changed files with 38 additions and 5 deletions

View File

@@ -74,6 +74,7 @@ _CONFIG_PLACEHOLDER = """\
# master-host = 10.0.0.1
# syslog-port = 6514
# swarmctl-port = 8770
# swarmctl-host = 127.0.0.1
# [logging]
# system-log = /var/log/decnet/decnet.system.log

View File

@@ -16,8 +16,16 @@ from .utils import console, log
def register(app: typer.Typer) -> None:
@app.command()
def swarmctl(
port: int = typer.Option(8770, "--port", help="Port for the swarm controller"),
host: str = typer.Option("127.0.0.1", "--host", help="Bind address for the swarm controller"),
port: int = typer.Option(
8770, "--port",
envvar="DECNET_SWARMCTL_PORT",
help="Port for the swarm controller. Defaults to [swarm] swarmctl-port from /etc/decnet/decnet.ini, else 8770.",
),
host: str = typer.Option(
"127.0.0.1", "--host",
envvar="DECNET_SWARMCTL_HOST",
help="Bind address for the swarm controller. Defaults to [swarm] swarmctl-host from /etc/decnet/decnet.ini, else 127.0.0.1.",
),
daemon: bool = typer.Option(False, "--daemon", "-d", help="Detach to background as a daemon process"),
no_listener: bool = typer.Option(False, "--no-listener", help="Do not auto-spawn the syslog-TLS listener alongside swarmctl"),
tls: bool = typer.Option(False, "--tls", help="Serve over HTTPS with mTLS (required for cross-host worker heartbeats)"),

View File

@@ -39,6 +39,7 @@ Shape::
master-host = 10.0.0.1 # required on agents
syslog-port = 6514
swarmctl-port = 8770
swarmctl-host = 127.0.0.1 # bind address for `decnet swarmctl`
[logging]
system-log = /var/log/decnet/decnet.system.log
@@ -120,6 +121,7 @@ _DOMAIN_MAP: dict[str, dict[str, str]] = {
"master-host": "DECNET_SWARM_MASTER_HOST",
"syslog-port": "DECNET_SWARM_SYSLOG_PORT",
"swarmctl-port": "DECNET_SWARMCTL_PORT",
"swarmctl-host": "DECNET_SWARMCTL_HOST",
},
"logging": {
"system-log": "DECNET_SYSTEM_LOGS",

View File

@@ -114,6 +114,11 @@ DECNET_SWARM_MASTER_HOST: str | None = os.environ.get("DECNET_SWARM_MASTER_HOST"
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)
# Bind address for the master-side swarm controller. Loopback by default —
# operators flip to 0.0.0.0 (or a specific NIC) on production masters where
# workers heartbeat in over mTLS from other hosts. Seeded by [swarm]
# swarmctl-host in /etc/decnet/decnet.ini.
DECNET_SWARMCTL_HOST: str = os.environ.get("DECNET_SWARMCTL_HOST", "127.0.0.1")
# Ingester batching: how many log rows to accumulate per commit, and the
# max wait (ms) before flushing a partial batch. Larger batches reduce

View File

@@ -10,10 +10,12 @@ User={{ user }}
Group={{ group }}
WorkingDirectory={{ install_dir }}
EnvironmentFile=-{{ install_dir }}/.env.local
# Default bind is loopback — the controller is a master-local orchestrator
# reached by the CLI and the web dashboard, not by workers.
# Bind/port resolved from /etc/decnet/decnet.ini ([swarm] swarmctl-host /
# swarmctl-port) — falls back to 127.0.0.1:8770 when the keys are absent.
# Pass --host/--port on the ExecStart line only if you want to pin them
# regardless of what the INI says.
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.swarmctl.log
ExecStart={{ venv_dir }}/bin/decnet swarmctl --host 127.0.0.1 --port 8770
ExecStart={{ venv_dir }}/bin/decnet swarmctl
StandardOutput=append:/var/log/decnet/decnet.swarmctl.log
StandardError=append:/var/log/decnet/decnet.swarmctl.log

View File

@@ -302,3 +302,18 @@ swarmctl-port = 9999
load_ini_config(ini)
# [master] loaded first, [swarm] lost via setdefault
assert os.environ["DECNET_SWARMCTL_PORT"] == "9001"
def test_swarm_section_seeds_swarmctl_host(monkeypatch, tmp_path):
"""[swarm] swarmctl-host → DECNET_SWARMCTL_HOST so the systemd unit and
`decnet swarmctl` CLI both pick up the operator's bind choice from the
INI without anyone passing --host on ExecStart."""
_scrub(monkeypatch, "DECNET_MODE", "DECNET_SWARMCTL_HOST", "DECNET_SWARMCTL_PORT")
ini = _write_ini(tmp_path, """
[swarm]
swarmctl-host = 0.0.0.0
swarmctl-port = 9000
""")
load_ini_config(ini)
assert os.environ["DECNET_SWARMCTL_HOST"] == "0.0.0.0"
assert os.environ["DECNET_SWARMCTL_PORT"] == "9000"