feat(hh): real VM unix users + sudo delegation ⛧

Linux-style user permissions inside the sandbox (the original superuser ask):
- Backends are now persistent (docker run -d + exec; multipass instance) so the
  broker can provision accounts and run the shell as a chosen user.
- sbx::provision(): create a real unix account per coven member at launch; the
  OWNER becomes a passwordless superuser (sudo group + /etc/sudoers.d NOPASSWD
  drop-in on multipass). The shared shell runs as the owner's account.
- /sudo <user> and /unsudo <user> (owner-only): real usermod + sudoers.d in the
  VM — delegate/withdraw superuser. ACL frame carries sudoers; roster shows
  ⛧ owner ·  sudoer · ◆ driver · • member.

Verified live on a real Multipass VM: shell runs as owner@vm with
'sudo -n whoami' == root; '/sudo member' gives member 'NOPASSWD: ALL';
teardown purges the instance. Docker provisions accounts + persistent
container (shell as root; sudo pkg absent so drive-grant is the delegation).

Tests: 7 cargo tests pass; clean build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-05-30 19:51:40 -07:00
parent 1dfc614cc5
commit 0e269afce7
4 changed files with 210 additions and 49 deletions
+3 -1
View File
@@ -116,10 +116,12 @@ fn draw_roster(f: &mut Frame, area: ratatui::layout::Rect, app: &App, theme: &Th
.iter()
.map(|u| {
let me = u.username == app.me;
// ⛧ owner/superuser · ◆ may drive · • member
// ⛧ owner · ⚡ sudoer (VM superuser) · ◆ may drive · • member
let owner = app.owner.as_deref() == Some(u.username.as_str());
let mark = if owner {
""
} else if app.sudoers.contains(&u.username) {
""
} else if app.drivers.contains(&u.username) {
""
} else {