fix(init): gate userdel/groupdel on --purge to avoid nuking the operator
Every plain `decnet deinit` ran userdel + groupdel unconditionally. In dev the operator may pass `--user $USER --group $USER` to avoid file ownership churn against a source checkout — at which point deinit would cheerfully delete their own login account. Move user/group removal behind --purge, matching the existing behaviour for /var/lib/decnet + /var/log/decnet. Help text updated: --purge now clearly advertises that it also wipes the service user/group, with an explicit warning to only run it when `decnet init` created the account in the first place. Test updated: plain --deinit must NOT invoke userdel/groupdel; --deinit --purge must.
This commit is contained in:
@@ -501,14 +501,18 @@ def register(app: typer.Typer) -> None:
|
||||
deinit: bool = typer.Option(
|
||||
False, "--deinit",
|
||||
help="Undo a previous init: stop + disable decnet.target, remove "
|
||||
"unit files, polkit rule, tmpfiles.d entry, /etc/decnet, and "
|
||||
"the decnet user/group. Preserves /var/lib/decnet and "
|
||||
"/var/log/decnet — pass --purge to remove those too.",
|
||||
"unit files, polkit rule, tmpfiles.d entry, /etc/decnet. "
|
||||
"Preserves /var/lib/decnet, /var/log/decnet, and the "
|
||||
"service user/group — pass --purge to remove those too.",
|
||||
),
|
||||
purge: bool = typer.Option(
|
||||
False, "--purge",
|
||||
help="With --deinit, also wipe /var/lib/decnet and "
|
||||
"/var/log/decnet. Destructive — operator data is gone.",
|
||||
help="With --deinit, also wipe /var/lib/decnet, "
|
||||
"/var/log/decnet, AND the service user/group. "
|
||||
"Destructive — operator data is gone, and if --user "
|
||||
"points at your own login account, that account goes "
|
||||
"with it. Only use when the user/group was created by "
|
||||
"`decnet init` in the first place.",
|
||||
),
|
||||
user: str = typer.Option(
|
||||
"decnet", "--user",
|
||||
@@ -664,14 +668,25 @@ def register(app: typer.Typer) -> None:
|
||||
f"{pfx / 'var/log/decnet'} (operator data); "
|
||||
"re-run with --purge to remove.[/]"
|
||||
)
|
||||
_step(
|
||||
f"remove user {user!r}",
|
||||
lambda: _remove_user(user, dry_run=dry_run),
|
||||
)
|
||||
_step(
|
||||
f"remove group {group!r}",
|
||||
lambda: _remove_group(group, dry_run=dry_run),
|
||||
)
|
||||
# User / group removal is also gated on --purge. In dev the
|
||||
# operator may have passed their own login user via
|
||||
# `--user $USER` to avoid ownership churn; an unconditional
|
||||
# `userdel anti` during deinit would nuke their account.
|
||||
if purge:
|
||||
_step(
|
||||
f"remove user {user!r}",
|
||||
lambda: _remove_user(user, dry_run=dry_run),
|
||||
)
|
||||
_step(
|
||||
f"remove group {group!r}",
|
||||
lambda: _remove_group(group, dry_run=dry_run),
|
||||
)
|
||||
else:
|
||||
console.print(
|
||||
f"[dim]preserved user {user!r} and group {group!r}; "
|
||||
"re-run with --purge to remove (only do this if "
|
||||
"they were created by `decnet init`).[/]"
|
||||
)
|
||||
console.print("[bold green]DECNET deinit complete.[/]")
|
||||
return
|
||||
|
||||
|
||||
@@ -387,11 +387,13 @@ def test_deinit_removes_units_polkit_tmpfiles_and_preserves_data(
|
||||
assert (prefix / "var/lib/decnet").exists()
|
||||
assert (prefix / "var/log/decnet/events.jsonl").read_text() == "{}\n"
|
||||
|
||||
# systemctl disable + daemon-reload + userdel + groupdel were invoked.
|
||||
# systemctl disable + daemon-reload invoked.
|
||||
assert ["systemctl", "disable", "--now", "decnet.target"] in subprocess_calls
|
||||
assert ["systemctl", "daemon-reload"] in subprocess_calls
|
||||
assert ["userdel", "decnet"] in subprocess_calls
|
||||
assert ["groupdel", "decnet"] in subprocess_calls
|
||||
# User / group are PRESERVED without --purge — an operator who
|
||||
# passed --user $USER during dev must not lose their login account.
|
||||
assert ["userdel", "decnet"] not in subprocess_calls
|
||||
assert ["groupdel", "decnet"] not in subprocess_calls
|
||||
|
||||
|
||||
def test_deinit_purge_wipes_data_dirs(
|
||||
@@ -406,6 +408,9 @@ def test_deinit_purge_wipes_data_dirs(
|
||||
assert result.exit_code == 0, result.output
|
||||
assert not (prefix / "var/lib/decnet").exists()
|
||||
assert not (prefix / "var/log/decnet").exists()
|
||||
# --purge also removes the service user/group.
|
||||
assert ["userdel", "decnet"] in subprocess_calls
|
||||
assert ["groupdel", "decnet"] in subprocess_calls
|
||||
|
||||
|
||||
def test_deinit_is_idempotent_on_clean_host(
|
||||
|
||||
Reference in New Issue
Block a user