c83ddf62a0
A member's tmux server keeps the version it started with until their last pane closes, but the dashboard asked it with TMUX_BIN, the newest tmux: a 3.8 client on a 3.6 server spun it at 100% CPU (three members needed a manual kill on 2026-10-15). - /opt/agenthub/libexec/tmux-member (install-system, in sudoers): runs a member's tmux call with the binary of THEIR running server (/proc/<pid>/exe, found from the socket in /proc/net/unix), else TMUX_BIN. It runs as the member and execs only a process they own. sudoArgv uses it once installed (linux-accounts readConf), TMUX_BIN until then. - Users → Members' tmux (GET /api/users/tmux): each member server's version beside the installed one, sessions, agents working. An admin restarts one (POST /api/users/:id/tmux-restart) or every idle outdated one (POST /api/users/tmux-restart-idle), with their password again, audited per member; a server that will not stop answers 409 ETMUXSTUCK naming its pid for root. - server-s3-system.test.js: `delete` is a provision action since Delete user, so the "unknown action" case now uses `purge`. Operators: run `sudo agenthub server install-system` once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
239 lines
11 KiB
JSON
239 lines
11 KiB
JSON
{
|
||
"title": "Users",
|
||
"description": "Every account on this server, with its workspaces, sessions and recent usage.",
|
||
"name": "User",
|
||
"role": "Role",
|
||
"workspaces": "Workspaces",
|
||
"sessions": "Sessions",
|
||
"last7d": "7 days",
|
||
"last30d": "30 days",
|
||
"storage": "Storage",
|
||
"storageHint": "{{files}} files · measured {{at}}",
|
||
"storagePartial": "at least — part of the folder could not be read in time",
|
||
"storageNone": "No home folder of their own",
|
||
"storageMeasuring": "Measuring…",
|
||
"disabled": "disabled",
|
||
"you": "you",
|
||
"tokens": "{{n}} tokens",
|
||
"actions": "Actions",
|
||
"roles": {
|
||
"admin": "Admin",
|
||
"analyst": "Analyst",
|
||
"developer": "Developer"
|
||
},
|
||
"roleHint": {
|
||
"admin": "Everything, including this server's configuration and every user's data",
|
||
"analyst": "Sees every user's data; works only in their own workspaces, changes no settings",
|
||
"developer": "Only their own workspaces; changes no settings"
|
||
},
|
||
"add": "Add user",
|
||
"username": "Username",
|
||
"usernameHint": "Letters, digits, '.', '_' or '-'",
|
||
"password": "Password",
|
||
"passwordHint": "No minimum length",
|
||
"newPassword": "New password",
|
||
"changeRole": "Change role",
|
||
"disable": "Disable",
|
||
"enable": "Enable",
|
||
"resetPassword": "Reset password",
|
||
"assignWorkspace": "Give a workspace",
|
||
"workspace": "Workspace",
|
||
"noOwner": "no owner",
|
||
"ownedBy": "owned by {{name}}",
|
||
"noWorkspaces": "There are no workspaces yet.",
|
||
"yourPassword": "Your password",
|
||
"yourPasswordHint": "Type your own password to confirm this change.",
|
||
"save": "Save",
|
||
"dialog": {
|
||
"add": "Add a user",
|
||
"role": "Change {{name}}'s role",
|
||
"disable": "Disable {{name}}?",
|
||
"disableBody": "{{name}} is signed out everywhere and cannot sign in until enabled again. Their workspaces and history stay.",
|
||
"enable": "Enable {{name}}?",
|
||
"password": "Reset {{name}}'s password",
|
||
"passwordBody": "{{name}} is signed out everywhere and signs in with the new password.",
|
||
"workspace": "Give {{name}} a workspace",
|
||
"workspaceBody": "The workspace's sessions and costs move to {{name}}. A developer sees only their own workspaces."
|
||
},
|
||
"done": {
|
||
"created": "{{name}} added",
|
||
"role": "{{name}} is now {{role}}",
|
||
"disabled": "{{name}} disabled",
|
||
"enabled": "{{name}} enabled",
|
||
"password": "{{name}}'s password reset",
|
||
"workspace": "Workspace given to {{name}}",
|
||
"updated": "{{name}} updated",
|
||
"deleted": "{{name}} was deleted."
|
||
},
|
||
"errors": {
|
||
"EBADIMAGE": "Only a PNG, JPEG, GIF or WebP picture",
|
||
"ETOOBIG": "The picture must be at most 1 MB",
|
||
"EREAUTH": "Your password is wrong.",
|
||
"ETOOMANY": "Too many wrong passwords. Try again in 15 minutes.",
|
||
"ESELF": "You cannot change your own role or disable yourself.",
|
||
"EEXISTS": "That username is taken.",
|
||
"EBADUSER": "Username: letters, digits, '.', '_' or '-' (up to 64).",
|
||
"EBADPASSWORD": "Password cannot be empty.",
|
||
"EBADEMAIL": "That email address does not look valid.",
|
||
"EBADFIELD": "One of the values is not valid.",
|
||
"ENOLANE": "That workspace no longer exists.",
|
||
"ENOTOWNED": "That workspace is not this user's any more.",
|
||
"EOWNERFOLDER": "That workspace's folder belongs to another user's account.",
|
||
"EBADROLE": "Unknown role.",
|
||
"ELOCALMODE": "Accounts are managed in server mode only.",
|
||
"ELASTADMIN": "The last admin cannot be deleted."
|
||
},
|
||
"readOnly": "Analysts can view accounts but not change them.",
|
||
"auditTitle": "Audit log",
|
||
"auditDescription": "Account changes made here, and each time an admin or analyst opened a session a developer owns.",
|
||
"auditEmpty": "Nothing recorded yet.",
|
||
"activeCount": "{{n}} active",
|
||
"cost30d": "Cost · 30 days",
|
||
"displayName": "Display name",
|
||
"displayNameHint": "Shown beside the username. Optional.",
|
||
"email": "Email",
|
||
"edit": {
|
||
"button": "Edit user",
|
||
"title": "Edit {{name}}",
|
||
"created": "Created",
|
||
"lastLogin": "Last sign-in",
|
||
"never": "Never",
|
||
"logins": "Active sign-ins",
|
||
"profile": "Profile",
|
||
"access": "Access",
|
||
"status": "Status",
|
||
"active": "Active",
|
||
"statusHint": "A disabled user cannot sign in; their workspaces and history stay.",
|
||
"selfRole": "You cannot change your own role.",
|
||
"selfDisable": "You cannot disable yourself.",
|
||
"password": "Password",
|
||
"passwordHint": "Leave blank to keep the current one. A new one signs them out everywhere.",
|
||
"show": "Show password",
|
||
"hide": "Hide password",
|
||
"copy": "Copy password",
|
||
"generate": "Generate",
|
||
"loading": "Loading…",
|
||
"noOwned": "Owns no workspace.",
|
||
"undo": "Undo",
|
||
"removeWorkspace": "Remove {{name}}",
|
||
"removeWorkspaceHint": "The workspace is left with no owner",
|
||
"addWorkspace": "Add workspace",
|
||
"chooseWorkspace": "Choose a workspace…",
|
||
"sessions": "Sign-ins",
|
||
"signOut": "Sign out everywhere",
|
||
"signOutHint": "Ends this user's {{n}} active sign-in(s); they sign in again with their password.",
|
||
"noChanges": "No changes yet."
|
||
},
|
||
"pool": {
|
||
"title": "Account pool",
|
||
"description": "Claude and Codex accounts every user's new sessions run on — the least-used one is picked, and a login is written into the user's home by the server, so nobody signs in per user.",
|
||
"refresh": "Re-read quotas",
|
||
"add": "Add account",
|
||
"empty": "No accounts yet. Until you add one, each user's session needs that user's own sign-in.",
|
||
"account": "Account",
|
||
"tool": "Tool",
|
||
"livePanes": "Live",
|
||
"quota": "Quota",
|
||
"spend": "5 hours",
|
||
"spend7d": "{{cost}} · {{tokens}} tokens in 7 days",
|
||
"tokens": "{{tokens}} tokens",
|
||
"added": "added {{at}}",
|
||
"expired": "expired",
|
||
"full": "full",
|
||
"kind": {
|
||
"token": "Claude · setup-token",
|
||
"login": "Claude · login",
|
||
"apikey": "Codex · API key",
|
||
"chatgpt": "Codex · ChatGPT"
|
||
},
|
||
"name": "Name",
|
||
"nameHint": "Any label you will recognise: letters, digits, '.', '_' or '-'",
|
||
"remove": "Remove {{name}}",
|
||
"removeHint": "New sessions stop using this account. Sessions already running on it keep running.",
|
||
"removeButton": "Remove",
|
||
"done": {
|
||
"added": "{{name}} added to the pool",
|
||
"removed": "{{name}} removed from the pool"
|
||
},
|
||
"errors": {
|
||
"EREAUTH": "Your password is wrong.",
|
||
"ETOOMANY": "Too many wrong passwords. Try again in 15 minutes.",
|
||
"EBADNAME": "Name: 1–64 letters, digits, '.', '_' or '-'.",
|
||
"EBADTOKEN": "That is not a valid token for this tool.",
|
||
"EEXISTS": "An account with that name is already in the pool.",
|
||
"ENOACCOUNT": "That account is no longer in the pool.",
|
||
"ELOCALMODE": "In local mode the pool is changed from the host's shell (agenthub accounts).",
|
||
"EACTIVE": "This server's Claude Code keeps that login in the macOS Keychain. Switch it to another profile first.",
|
||
"ENOTFOUND": "That saved login no longer exists.",
|
||
"ESTALE": "That login was already refreshed elsewhere. Log in again and save it anew.",
|
||
"EUPSTREAM": "Anthropic could not be reached. Try again."
|
||
},
|
||
"source": "From",
|
||
"sources": {
|
||
"profile": "A saved login (Switch account profiles)",
|
||
"credentials": "Paste .credentials.json / Keychain",
|
||
"token": "claude setup-token"
|
||
},
|
||
"sourceHint": {
|
||
"profile": "The login is moved into the pool: the profile leaves the switcher, and the server writes it into each user's home when their session starts.",
|
||
"credentials": "From ~/.claude/.credentials.json, or on a Mac `security find-generic-password -s \"Claude Code-credentials\" -w`. Do not keep using that login where you copied it from — two copies log each other out.",
|
||
"token": "A one-year token from `claude setup-token`, given to every session as an environment variable."
|
||
},
|
||
"profile": "Profile",
|
||
"profileHint": "Saved in Settings → Claude config → Switch account. The one this server's own Claude Code is logged into can be moved too: the server keeps running on it and refreshes it for everyone.",
|
||
"chooseProfile": "Choose a saved login…",
|
||
"noProfiles": "No saved logins — save one in Settings → Claude config → Switch account",
|
||
"loadingProfiles": "Loading…",
|
||
"activeHere": "in use on this server — keeps working",
|
||
"nameFromProfile": "Leave blank to use the profile's name",
|
||
"secret": {
|
||
"credentials": "Credentials JSON",
|
||
"token": "Token",
|
||
"codex": "API key or auth.json"
|
||
},
|
||
"secretHint": {
|
||
"credentials": "The whole file: {\"claudeAiOauth\": {…}}",
|
||
"token": "The sk-ant-… that `claude setup-token` printed.",
|
||
"codex": "An OpenAI API key (sk-…), or the whole ~/.codex/auth.json after `codex login` with a ChatGPT account."
|
||
},
|
||
"refreshFailed": "refresh failed"
|
||
},
|
||
"delete": {
|
||
"button": "Delete user",
|
||
"title": "Delete {{name}}?",
|
||
"body": "This deletes {{name}} for good: their login, {{count}} workspace(s), and their Linux account with the whole home folder (/srv/agenthub/users/{{user}}) — every file and all source code in it. It cannot be undone. Session history and the audit log stay.",
|
||
"confirm": "Delete for good",
|
||
"needsPassword": "Enter your own password below first."
|
||
},
|
||
"tmux": {
|
||
"title": "Members' tmux",
|
||
"description": "Each member's own tmux server keeps the version it started with until its last pane closes. Installed: tmux {{version}}.",
|
||
"refresh": "Refresh",
|
||
"notMatched": "Run `sudo agenthub server install-system` once: until then an older member server is asked with the installed tmux, which can leave it stuck at 100% CPU.",
|
||
"empty": "No member has a tmux server running.",
|
||
"user": "User",
|
||
"version": "tmux",
|
||
"sessions": "Sessions",
|
||
"agents": "Agents",
|
||
"stuck": "not answering",
|
||
"outdated": "older than installed",
|
||
"since": "since {{at}}",
|
||
"working_one": "{{count}} working",
|
||
"working_other": "{{count}} working",
|
||
"idle": "idle",
|
||
"restart": "Restart {{name}}'s tmux",
|
||
"restartIdle_one": "Restart {{count}} idle member",
|
||
"restartIdle_other": "Restart {{count}} idle members",
|
||
"restartIdleTitle": "Restart idle members' tmux",
|
||
"restartHint": "Every pane of {{name}} closes ({{count}} sessions). Their Claude sessions resume from the workspace, and the next start runs the installed tmux.",
|
||
"restartHintBusy": "An agent of {{name}} is working right now and will be stopped. Every pane of theirs closes ({{count}} sessions); their Claude sessions resume from the workspace.",
|
||
"restartIdleHint": "Restarts the tmux of every member on an older version with no agent working: {{names}}. Their panes close; their Claude sessions resume from the workspace.",
|
||
"restartButton": "Restart",
|
||
"done": "{{name}}'s tmux restarted",
|
||
"doneIdle": "Restarted: {{names}}",
|
||
"errors": {
|
||
"ETMUXSTUCK": "The server did not stop. Run the `sudo kill -9` command shown, as root, on the server."
|
||
}
|
||
}
|
||
}
|