fix(init): template the polkit rule on --group too

polkit rule 50-decnet-workers.rules hardcoded isInGroup("decnet"),
so when 'decnet init --group anti' installed systemd units as
User=anti / Group=anti, the API (running as anti) could no longer
systemctl start/stop decnet-*.service — polkit fell back to
'interactive authentication required', which in a daemon context is
a hard fail:

  START FAILED · COLLECTOR — Failed to start decnet-collector.service:
  Access denied as the requested operation requires interactive
  authentication.

Rename the rule to .j2, parameterise the group on {{ group }}, and
route _install_polkit through _render_template /
_write_rendered_if_changed. Now the polkit rule matches whatever
group was passed to 'decnet init'.

Test fixture updated to seed the .j2 variant.
This commit is contained in:
2026-04-24 01:07:16 -04:00
parent 08436433ef
commit e4ccf30133
3 changed files with 26 additions and 9 deletions

View File

@@ -379,13 +379,23 @@ def _install_units(
def _install_polkit(
deploy: Path, rules_dir: Path, *, force: bool, dry_run: bool
deploy: Path, rules_dir: Path, *, group: str, force: bool, dry_run: bool
) -> str:
src = deploy / "polkit" / "50-decnet-workers.rules"
"""Render the group-scoped polkit rule to /etc/polkit-1/rules.d/.
The rule has to reference the same POSIX group passed via --group —
otherwise the API (running as that user) can't
systemctl start/stop decnet-*.service without an interactive auth
prompt that never gets answered in a daemon context.
"""
src = deploy / "polkit" / "50-decnet-workers.rules.j2"
if not src.is_file():
raise RuntimeError(f"missing polkit rule at {src}")
return _copy_if_changed(
src, rules_dir / src.name,
raise RuntimeError(f"missing polkit rule template at {src}")
rendered = _render_template(src, {"group": group})
# 50-decnet-workers.rules.j2 → 50-decnet-workers.rules
dst_name = src.name[: -len(".j2")]
return _write_rendered_if_changed(
src, rules_dir / dst_name, rendered,
mode=0o644, force=force, dry_run=dry_run,
)
@@ -755,7 +765,8 @@ def register(app: typer.Typer) -> None:
_step(
"install polkit rule",
lambda: _install_polkit(
deploy, polkit_dir, force=force, dry_run=dry_run,
deploy, polkit_dir, group=group,
force=force, dry_run=dry_run,
),
)
_step(