Give back only the groups the mesh added, and say when a new login is needed (hq ADR 0251, issue 247)
mesh/delivery-group group feat/module-groups checking: 0 of 2 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed

A module puts the operator's account in a group by declaring the account with that group alone.
The node-engine now records each group it added, takes back only those when nothing declared still
asks for them, refuses a group the machine lacks before usermod runs, and states each such account
as its module's resource of kind account: relogin needed while the running session lacks the group.
This commit is contained in:
jochen
2026-10-08 12:02:31 +02:00
parent ef8378a990
commit 73a5cd1ad2
13 changed files with 1096 additions and 31 deletions
+17 -22
View File
@@ -28,8 +28,11 @@ import (
//
// previous is this resource's record, which carries the shell the account had before the mesh
// first changed it, so removal can give it back (novox/hq ADR 0176 §2, issue 228).
//
// wanted is every group the whole declaration asks an account to be in (novox/hq ADR 0251), so a group
// this resource stops asking for is kept while another resource asks for it.
func applyUser(ctx context.Context, sys system.System, r *declaration.User, run Runner,
previous store.Applied) (Outcome, error) {
previous store.Applied, wanted Wanted) (Outcome, error) {
out := begin(r)
out.Action = "unchanged"
// What was found is carried from the record for as long as the resource is recorded — for this
@@ -82,26 +85,8 @@ func applyUser(ctx context.Context, sys system.System, r *declaration.User, run
// Groups before the shell, so that a failure here comes before the shell is changed: a record
// is written only for an apply that worked, and a shell changed by a failed one would be read
// next time as the account's own, and the one it replaced lost.
if len(r.Groups) > 0 {
in, err := system.GroupsOf(ctx, system.Runner(run), r.Name)
if err != nil {
return out, err
}
already := map[string]bool{}
for _, g := range in {
already[g] = true
}
for _, want := range r.Groups {
if already[want] {
continue
}
if err := sys.AddUserToGroup(ctx, system.Runner(run), r.Name, want); err != nil {
return out, err
}
if out.Action == "unchanged" {
out.Action = "updated"
}
}
if err := applyGroups(ctx, sys, r, run, previous, wanted, &out); err != nil {
return out, err
}
// The shell, only when it differs. Absent means the host asserts nothing — a field that
@@ -203,7 +188,11 @@ func onOff(on bool) string {
// uninstalled since would break the very logins the giving back is for. Otherwise it is left, and
// the outcome says why. Never errNoRemoval: an orphaned login that failed removal stopped the
// whole apply, on every apply after.
func removeUser(ctx context.Context, sys system.System, a store.Applied, run Runner) (string, string, error) {
//
// And every group the mesh put the account in, while no other declared resource asks for it (novox/hq
// ADR 0251), on the same rule: never fatal, and never a group the account was in before.
func removeUser(ctx context.Context, sys system.System, a store.Applied, run Runner,
wanted Wanted) (string, string, error) {
const kept = "the account is kept; the host never deletes a login"
login, exists, err := system.LookUpUser(ctx, system.Runner(run), a.Target)
if err != nil {
@@ -222,6 +211,12 @@ func removeUser(ctx context.Context, sys system.System, a store.Applied, run Run
action = "restored"
}
}
if gave, said := giveGroupsBack(ctx, sys, a, run, wanted); said != "" {
detail += "; " + said
if gave {
action = "restored"
}
}
return action, detail, nil
}