feat(server-s3): one Linux user per account (S3) — experimental

Server mode can now run each account as its own Linux user (ah-<name>)
when `agenthub server install-system` has set S3 up. The dashboard
reaches a member only through sudo -u / write-as / pane-launch, reads a
member's files only through owner- and type-checked opens, and hooks
are authenticated by peer uid. Local mode and server mode without S3
are unchanged.

EXPERIMENTAL: tested with fake sudo and fake accounts only. It has not
yet been verified on a real install with real member accounts, sudo or
a real Codex login. Do not offer it to real users yet.

- S3 core: provisioning, per-member tmux servers, the proxy key
  gateway, shared tools in /opt, dropped capabilities for children
  (P0–P5)
- P4: plan and quota per member home
- P6: ~/.claude.json seed; opt-in plugin baseline installed as the
  member; managed-settings spike script (not run)
- P7: Codex account pool (`agenthub accounts add --tool codex`)
- Roles merged with main's admin / analyst / developer: analysts write
  only their own rows, under S3 as well
- Per-account prefs: one implementation (Chat UI + onboarding)
- Security reviews F1–F49 (five rounds), each fix with a regression
  test: symlink/TOCTOU-safe member reads, no host writes in a member
  home, credentials and the data dir never served, owner and folder
  account must agree, pane tokens bound to the pane's uid, bounded
  sudo/tmux/git calls and member reads (no hang or crash from a member),
  async route rejections answer 500 instead of exiting
- Docs: docs/SERVER-MODE.md (S3 model, accepted ceilings, operator
  runbook), docs/plans/server-mode/S3-PLAN.md, CLAUDE.md S3 rules

Tests: server 2838/2841 (3 need 127.0.0.1:3000, held on this box);
client 1162/1162; tsc clean; headers check passes.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-10-09 14:57:10 +07:00
parent b7f3735b29
commit 84e2a9f0ac
123 changed files with 12051 additions and 605 deletions
@@ -0,0 +1,95 @@
{
"brand": "AgentHub",
"progress": "{{n}} of {{total}}",
"skip": "Skip setup",
"back": "Back",
"continue": "Continue",
"finish": "Add your first workspace",
"agent": {
"eyebrow": "Welcome to AgentHub",
"title": "Pick your default agent",
"body": "AgentHub works with every CLI agent. Choose the one you'll reach for most. Switch any time.",
"detected": "Detected on this machine · {{count}}",
"none": "No AI tool is installed yet. An admin installs them in Settings → AI Tools; you can finish setup and pick one later.",
"more": "Show {{count}} more agents",
"less": "Show fewer",
"notInstalled": "not installed",
"permission": "Start new sessions in",
"permissionHint": "Bypass permissions is never a default: it stays a red switch you turn on for one session.",
"modes": {
"auto": "Auto",
"acceptEdits": "Accept edits",
"default": "Ask first",
"plan": "Plan"
}
},
"look": {
"title": "Make it feel like home",
"body": "Pick the look you want to stare at for hours.",
"modes": {
"system": "System",
"dark": "Dark",
"light": "Light"
},
"modeHints": {
"system": "Match OS",
"dark": "Easy on the eyes",
"light": "Bright & crisp"
},
"darkThemes": "Dark theme",
"lightThemes": "Light theme",
"language": "Language",
"font": "Terminal font",
"more": "More options — themes, fonts, the pet — in Settings → Appearance."
},
"pet": {
"title": "Pick a companion",
"body": "A small pet sits in the corner and reacts to what your agents do. Hide it any time."
},
"notify": {
"title": "Set up notifications",
"body": "AgentHub tells you when agents are done or need you.",
"enable": "Desktop notifications",
"enableHint": "Your browser asks for permission once.",
"blocked": "Notifications are blocked for this site — allow them in the browser's site settings.",
"sound": "Notification sound",
"preview": "Play",
"test": "Send test notification"
},
"tour": {
"progress": "Step {{n}} of {{total}}",
"skip": "Skip tour",
"next": "Next",
"done": "Done",
"goWorkspaces": "Go to Workspaces",
"add": {
"title": "Create your first workspace",
"body": "A workspace is one project folder where your agents work. Click here to add one."
},
"folder": {
"title": "Choose the project folder",
"body": "Type a path or Browse to the folder of your project. A git repository gets a Changes panel too."
},
"create": {
"title": "Create it",
"body": "Pick the AI it starts on, then click Add workspace."
},
"prompt": {
"title": "Start your first session",
"body": "Tell the agent what to do and press Enter. AI, model and permission mode sit right here."
},
"session": {
"title": "Your session is live",
"body": "Watch it work and type into it like a terminal. Changes and Files are in the panel on the right; every session of this workspace is listed in the sidebar."
},
"finish": {
"title": "You're all set",
"body": "Run setup again or change any of it in Settings."
}
},
"rerun": {
"label": "First-run setup",
"description": "Go through the welcome steps and the tour again.",
"button": "Run setup again"
}
}
+13 -7
View File
@@ -34,7 +34,7 @@
"bubbles": "Speech bubbles",
"bubblesDesc": "Let the pet comment on your sessions in a bubble",
"sizeDesc": "{{min}}–{{max}} px; the preview is capped at 120 px",
"chooseDesc": "Featured pets come from codex-pets.net and are downloaded into this install the first time you pick one. Your choice and size are saved in this browser.",
"chooseDesc": "Featured pets come from codex-pets.net and are downloaded into this install the first time you pick one. Your choice and size are saved to your account.",
"uploadHint": "An image (PNG, JPEG, GIF, WebP — no SVG) is shown still; a .codex-pet.zip (pet.json + a 1536×1872 spritesheet.webp) is animated by mood.",
"panel": {
"label": "Sagi companion",
@@ -189,7 +189,7 @@
"alert": "Alert",
"forType": "Sound for this type"
},
"scopeNote": "These notification settings are saved in this browser only. Alert rules and webhooks below are server-wide: they apply to everyone using this dashboard.",
"scopeNote": "These notification settings are saved to your account. Alert rules and webhooks below are server-wide: they apply to everyone using this dashboard.",
"insecureOrigin": "This page is served from {{origin}}, which is not a secure origin. Browsers only show notifications on HTTPS or localhost — open the dashboard through one of those to get them.",
"unsupported": "This browser does not support notifications.",
"deniedHelp": "Notifications are blocked for this site. To allow them again, open the site settings (the icon left of the address bar), set Notifications to Allow, then reload this page.",
@@ -309,6 +309,7 @@
"label": "Start AgentHub automatically",
"desc_systemd": "Run the dashboard as a systemd --user service, so it starts at boot without Claude Code being opened. Turning it off keeps the running server up.",
"desc_launchd": "Run the dashboard as a LaunchAgent, so it starts at login without Claude Code being opened. Turning it off keeps the running server up.",
"desc_systemd-system": "Run the dashboard as the system service agenthub.service (server mode, one Linux user per account), so it starts with the machine.",
"unavailable": "Not available here: needs systemd --user on Linux, or a GUI login on macOS.",
"foreign": "A service named agenthub that AgentHub did not write already exists — AgentHub leaves it alone."
},
@@ -662,7 +663,12 @@
"test": "Test",
"testing": "Testing…",
"testOk": "answered — {{model}} is reachable with this key.",
"testFailed": "no answer ({{status}}): {{message}}"
"testFailed": "no answer ({{status}}): {{message}}",
"personal": "Personal",
"fieldScope": "Who can use it",
"scopeShared": "Everyone (shared)",
"scopePersonal": "Only me (personal)",
"fieldScopeHint": "A shared profile's key never reaches anyone's browser. A personal one is listed, edited and used by you alone."
},
"aiTools": {
"title": "AI Tools",
@@ -697,7 +703,7 @@
},
"appearance": {
"title": "Appearance",
"description": "Colour mode and theme. Saved in this browser only.",
"description": "Colour mode and theme. Saved to your account, so every browser you sign in to gets them.",
"mode": "Mode",
"modes": {
"dark": "Dark",
@@ -882,7 +888,7 @@
},
"language": {
"title": "Language",
"description": "The language of the whole dashboard, remembered in this browser.",
"description": "The language of the whole dashboard, saved to your account.",
"label": "Interface language",
"names": {
"en": "English",
@@ -891,7 +897,7 @@
},
"terminalFont": {
"title": "Terminal font",
"description": "The font of the workspace terminals. Takes effect the next time a terminal opens. Saved in this browser only.",
"description": "The font of the workspace terminals. Takes effect the next time a terminal opens. Saved to your account.",
"label": "Font",
"names": {
"jetbrains": "JetBrains Mono",
@@ -1001,7 +1007,7 @@
},
"prefsBackup": {
"title": "This browser's preferences",
"description": "Theme, language, notification settings, pet, sidebar and workspace layout are kept in this browser. Save them to a file to move them to another browser, or restore them from one.",
"description": "Theme, language, notification settings and pet follow your account; the sidebar and workspace layout are kept in this browser. Save them to a file to move them to another browser, or restore them from one.",
"export": "Export preferences",
"import": "Import preferences…",
"importTitle": "Import preferences?",