diff --git a/decnet/web/router/swarm_mgmt/api_enroll_bundle.py b/decnet/web/router/swarm_mgmt/api_enroll_bundle.py index f3a7128..c099515 100644 --- a/decnet/web/router/swarm_mgmt/api_enroll_bundle.py +++ b/decnet/web/router/swarm_mgmt/api_enroll_bundle.py @@ -250,7 +250,7 @@ def _build_tarball( return buf.getvalue() -_SYSTEMD_UNITS = ("decnet-agent", "decnet-forwarder", "decnet-engine") +_SYSTEMD_UNITS = ("decnet-agent", "decnet-forwarder", "decnet-engine", "decnet-updater") def _render_systemd_unit(name: str, agent_name: str, master_host: str) -> bytes: diff --git a/decnet/web/templates/decnet-updater.service.j2 b/decnet/web/templates/decnet-updater.service.j2 new file mode 100644 index 0000000..3ac5406 --- /dev/null +++ b/decnet/web/templates/decnet-updater.service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description=DECNET self-updater (accepts tarball pushes from master) — {{ agent_name }} +Documentation=https://github.com/anti/DECNET +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory=/opt/decnet +Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.updater.log +ExecStart=/usr/local/bin/decnet updater --updater-dir /etc/decnet/updater --install-dir /opt/decnet --agent-dir /etc/decnet/agent +Restart=on-failure +RestartSec=5 +StandardOutput=append:/var/log/decnet/decnet.updater.log +StandardError=append:/var/log/decnet/decnet.updater.log + +[Install] +WantedBy=multi-user.target diff --git a/decnet/web/templates/enroll_bootstrap.sh.j2 b/decnet/web/templates/enroll_bootstrap.sh.j2 index 526f04a..b1b21ba 100644 --- a/decnet/web/templates/enroll_bootstrap.sh.j2 +++ b/decnet/web/templates/enroll_bootstrap.sh.j2 @@ -58,10 +58,15 @@ echo "[DECNET] installing systemd units..." install -Dm0644 etc/systemd/system/decnet-agent.service /etc/systemd/system/decnet-agent.service install -Dm0644 etc/systemd/system/decnet-forwarder.service /etc/systemd/system/decnet-forwarder.service install -Dm0644 etc/systemd/system/decnet-engine.service /etc/systemd/system/decnet-engine.service -systemctl daemon-reload -systemctl enable --now decnet-agent.service decnet-forwarder.service - if [[ "$WITH_UPDATER" == "true" ]]; then - /usr/local/bin/decnet updater --daemon + install -Dm0644 etc/systemd/system/decnet-updater.service /etc/systemd/system/decnet-updater.service fi -echo "[DECNET] agent {{ agent_name }} enrolled -> {{ master_host }}. Units: decnet-agent, decnet-forwarder active." +systemctl daemon-reload + +ACTIVE_UNITS=(decnet-agent.service decnet-forwarder.service) +if [[ "$WITH_UPDATER" == "true" ]]; then + ACTIVE_UNITS+=(decnet-updater.service) +fi +systemctl enable --now "${ACTIVE_UNITS[@]}" + +echo "[DECNET] agent {{ agent_name }} enrolled -> {{ master_host }}. Units: ${ACTIVE_UNITS[*]} active." diff --git a/tests/api/swarm_mgmt/test_enroll_bundle.py b/tests/api/swarm_mgmt/test_enroll_bundle.py index 040054c..ee5b10d 100644 --- a/tests/api/swarm_mgmt/test_enroll_bundle.py +++ b/tests/api/swarm_mgmt/test_enroll_bundle.py @@ -197,7 +197,9 @@ async def test_systemd_units_shipped_and_installed(client, auth_token): master_host="10.9.8.7")).json()["token"] sh = (await client.get(f"/api/v1/swarm/enroll-bundle/{sh_token}.sh")).text assert "systemctl daemon-reload" in sh - assert "systemctl enable --now decnet-agent.service decnet-forwarder.service" in sh + # Agent + forwarder always enabled; updater conditional on WITH_UPDATER. + assert "decnet-agent.service decnet-forwarder.service" in sh + assert "decnet-updater.service" in sh ini = tf.extractfile("etc/decnet/decnet.ini").read().decode() assert "log-directory = /var/log/decnet" in ini @@ -205,7 +207,7 @@ async def test_systemd_units_shipped_and_installed(client, auth_token): @pytest.mark.anyio -async def test_updater_opt_in_ships_cert_and_starts_daemon(client, auth_token): +async def test_updater_opt_in_ships_cert_and_enables_systemd_unit(client, auth_token): import io, tarfile token = (await _post(client, auth_token, agent_name="up", with_updater=True)).json()["token"] resp = await client.get(f"/api/v1/swarm/enroll-bundle/{token}.tgz") @@ -213,12 +215,20 @@ async def test_updater_opt_in_ships_cert_and_starts_daemon(client, auth_token): names = set(tf.getnames()) assert "home/.decnet/updater/updater.crt" in names assert "home/.decnet/updater/updater.key" in names + assert "etc/systemd/system/decnet-updater.service" in names key_info = tf.getmember("home/.decnet/updater/updater.key") assert (key_info.mode & 0o777) == 0o600 + + updater_unit = tf.extractfile("etc/systemd/system/decnet-updater.service").read().decode() + assert "DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.updater.log" in updater_unit + assert "Restart=on-failure" in updater_unit + sh_token = (await _post(client, auth_token, agent_name="up2", with_updater=True)).json()["token"] sh = (await client.get(f"/api/v1/swarm/enroll-bundle/{sh_token}.sh")).text assert 'WITH_UPDATER="true"' in sh - assert "decnet updater --daemon" in sh + assert "decnet-updater.service" in sh + # Old --daemon path is gone — updater is now a systemd service. + assert "decnet updater --daemon" not in sh @pytest.mark.anyio