clay-server 2.44.1-beta.1 → 2.44.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/daemon.js +26 -3
- package/lib/os-users.js +69 -2
- package/lib/project-http.js +4 -2
- package/lib/project-sessions.js +10 -1
- package/lib/public/index.html +15 -0
- package/lib/public/modules/app-messages.js +6 -1
- package/lib/public/modules/server-settings.js +34 -0
- package/lib/server.js +2 -0
- package/lib/terminal.js +4 -1
- package/lib/yoke/adapters/claude.js +4 -1
- package/package.json +1 -1
package/lib/daemon.js
CHANGED
|
@@ -28,7 +28,8 @@ var path = require("path");
|
|
|
28
28
|
var { loadConfig, saveConfig, socketPath, generateSlug, syncClayrc, removeFromClayrc, writeCrashInfo, readCrashInfo, clearCrashInfo, isPidAlive, clearStaleConfig, REAL_HOME } = require("./config");
|
|
29
29
|
var { createIPCServer } = require("./ipc");
|
|
30
30
|
var { createServer, generateAuthToken } = require("./server");
|
|
31
|
-
var
|
|
31
|
+
var osUsersMod = require("./os-users");
|
|
32
|
+
var { checkAclSupport, grantProjectAccess, revokeProjectAccess, provisionAllUsers, provisionLinuxUser, grantAllUsersAccess, deactivateLinuxUser, ensureProjectsDir } = osUsersMod;
|
|
32
33
|
var usersModule = require("./users");
|
|
33
34
|
var { createWorktree, removeWorktree, isWorktree } = require("./worktree");
|
|
34
35
|
var mates = require("./mates");
|
|
@@ -52,6 +53,9 @@ if (process.env.SUDO_USER) console.log("[daemon] SUDO_USER: " + process.env.SUDO
|
|
|
52
53
|
console.log("[daemon] UID: " + (typeof process.getuid === "function" ? process.getuid() : "N/A"));
|
|
53
54
|
|
|
54
55
|
// --- OS users mode: check required system dependencies ---
|
|
56
|
+
// Supplementary-group inheritance (issue #367): default on so sessions behave
|
|
57
|
+
// like a normal login; operators can disable via config / the settings toggle.
|
|
58
|
+
osUsersMod.setInheritGroups(config.inheritGroups !== false);
|
|
55
59
|
if (config.osUsers) {
|
|
56
60
|
var checkExec = require("child_process").execFileSync;
|
|
57
61
|
var missing = [];
|
|
@@ -63,6 +67,11 @@ if (config.osUsers) {
|
|
|
63
67
|
console.error("[daemon] Install with: sudo apt install " + missing.map(function (m) { return m.split(" ")[0]; }).join(" "));
|
|
64
68
|
process.exit(78); // EX_CONFIG
|
|
65
69
|
}
|
|
70
|
+
if (config.inheritGroups !== false) {
|
|
71
|
+
console.log("[daemon] Sessions inherit each user's supplementary groups (docker, etc.); disable in Settings > User Groups.");
|
|
72
|
+
} else {
|
|
73
|
+
console.log("[daemon] Sessions run with primary group only (supplementary groups dropped); enable in Settings > User Groups.");
|
|
74
|
+
}
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
// --- TLS ---
|
|
@@ -216,7 +225,8 @@ var relay = createServer({
|
|
|
216
225
|
if (uidGid) {
|
|
217
226
|
fs.chmodSync(targetDir, 0o700);
|
|
218
227
|
execFileSync("chown", ["-R", linuxUser + ":" + linuxUser, targetDir]);
|
|
219
|
-
|
|
228
|
+
var gitInit = osUsersMod.wrapSpawnAsUser("git", ["init"], { cwd: targetDir, uid: uidGid.uid, gid: uidGid.gid, env: { PATH: "/usr/local/bin:/usr/bin:/bin" } });
|
|
229
|
+
execFileSync(gitInit.command, gitInit.args, gitInit.options);
|
|
220
230
|
} else {
|
|
221
231
|
execFileSync("git", ["init"], { cwd: targetDir });
|
|
222
232
|
}
|
|
@@ -296,7 +306,8 @@ var relay = createServer({
|
|
|
296
306
|
return;
|
|
297
307
|
}
|
|
298
308
|
}
|
|
299
|
-
var
|
|
309
|
+
var cloneSpawn = osUsersMod.wrapSpawnAsUser("git", ["clone", cloneUrl, targetDir], spawnOpts);
|
|
310
|
+
var proc = spawn(cloneSpawn.command, cloneSpawn.args, cloneSpawn.options);
|
|
300
311
|
var stderrBuf = "";
|
|
301
312
|
proc.stderr.on("data", function (chunk) { stderrBuf += chunk.toString(); });
|
|
302
313
|
// 5 minute timeout
|
|
@@ -689,6 +700,8 @@ var relay = createServer({
|
|
|
689
700
|
headless: !!config.headless,
|
|
690
701
|
keepAwake: !!config.keepAwake,
|
|
691
702
|
autoContinueOnRateLimit: !!config.autoContinueOnRateLimit,
|
|
703
|
+
osUsers: !!config.osUsers,
|
|
704
|
+
inheritGroups: config.inheritGroups !== false,
|
|
692
705
|
chatLayout: config.chatLayout || "channel",
|
|
693
706
|
matesEnabled: config.matesEnabled !== false,
|
|
694
707
|
pinEnabled: !!config.pinHash,
|
|
@@ -804,6 +817,16 @@ var relay = createServer({
|
|
|
804
817
|
console.log("[daemon] Keep awake:", want, "(web)");
|
|
805
818
|
return { ok: true, keepAwake: want };
|
|
806
819
|
},
|
|
820
|
+
onSetInheritGroups: function (value) {
|
|
821
|
+
var want = !!value;
|
|
822
|
+
config.inheritGroups = want;
|
|
823
|
+
saveConfig(config);
|
|
824
|
+
// Apply live so subsequent session/terminal spawns pick it up without a
|
|
825
|
+
// daemon restart; already-running sessions keep their current groups.
|
|
826
|
+
osUsersMod.setInheritGroups(want);
|
|
827
|
+
console.log("[daemon] Inherit supplementary groups:", want, "(web)");
|
|
828
|
+
return { ok: true, inheritGroups: want };
|
|
829
|
+
},
|
|
807
830
|
onShutdown: function () {
|
|
808
831
|
console.log("[daemon] Shutdown requested via web UI");
|
|
809
832
|
gracefulShutdown();
|
package/lib/os-users.js
CHANGED
|
@@ -14,6 +14,68 @@ function isSafeLinuxUsername(username) {
|
|
|
14
14
|
return typeof username === "string" && /^[a-z_][a-z0-9_-]*[$]?$/.test(username);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// --- Supplementary group inheritance (issue #367) ---
|
|
18
|
+
// Node's child_process { uid, gid } options call setuid/setgid but never
|
|
19
|
+
// initgroups(), so spawned sessions drop all supplementary groups (docker,
|
|
20
|
+
// video, wheel, ...). When inheritGroups is enabled we route the spawn through
|
|
21
|
+
// `setpriv --init-groups` so the child gets the same groups as a normal login.
|
|
22
|
+
var _inheritGroups = true; // default: behave like a normal login shell
|
|
23
|
+
var _setprivAvail = null;
|
|
24
|
+
var _warnedNoSetpriv = false;
|
|
25
|
+
|
|
26
|
+
function setInheritGroups(value) {
|
|
27
|
+
_inheritGroups = !!value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getInheritGroups() {
|
|
31
|
+
return _inheritGroups;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function setprivAvailable() {
|
|
35
|
+
if (_setprivAvail !== null) return _setprivAvail;
|
|
36
|
+
try {
|
|
37
|
+
execFileSync("setpriv", ["--help"], { stdio: "ignore", timeout: 5000 });
|
|
38
|
+
_setprivAvail = true;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
_setprivAvail = false;
|
|
41
|
+
}
|
|
42
|
+
return _setprivAvail;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Wrap a spawn/exec invocation so the child runs as the target user WITH its
|
|
47
|
+
* supplementary groups initialized (initgroups), which Node's uid/gid options
|
|
48
|
+
* do not do. Accepts (command, args, options) where options may carry
|
|
49
|
+
* uid/gid, and returns a normalized { command, args, options } to pass to
|
|
50
|
+
* spawn()/execFileSync()/pty.spawn().
|
|
51
|
+
*
|
|
52
|
+
* - Same-user spawns (no uid/gid in options) are returned unchanged.
|
|
53
|
+
* - When inheritGroups is disabled, or setpriv is unavailable, falls back to
|
|
54
|
+
* Node's uid/gid options (primary group only) so behavior degrades safely.
|
|
55
|
+
* Requires the parent process to be root, which is the case in os-users mode.
|
|
56
|
+
*/
|
|
57
|
+
function wrapSpawnAsUser(command, args, options) {
|
|
58
|
+
args = args || [];
|
|
59
|
+
options = options || {};
|
|
60
|
+
var uid = options.uid;
|
|
61
|
+
var gid = options.gid;
|
|
62
|
+
if (uid == null || gid == null) return { command: command, args: args, options: options };
|
|
63
|
+
|
|
64
|
+
if (_inheritGroups && setprivAvailable()) {
|
|
65
|
+
var wrappedArgs = ["--reuid", String(uid), "--regid", String(gid), "--init-groups", "--", command].concat(args);
|
|
66
|
+
var newOpts = Object.assign({}, options);
|
|
67
|
+
delete newOpts.uid;
|
|
68
|
+
delete newOpts.gid;
|
|
69
|
+
return { command: "setpriv", args: wrappedArgs, options: newOpts };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (_inheritGroups && !_warnedNoSetpriv) {
|
|
73
|
+
_warnedNoSetpriv = true;
|
|
74
|
+
console.warn("[os-users] inheritGroups enabled but `setpriv` not found; supplementary groups will be dropped. Install util-linux to restore them.");
|
|
75
|
+
}
|
|
76
|
+
return { command: command, args: args, options: options };
|
|
77
|
+
}
|
|
78
|
+
|
|
17
79
|
/**
|
|
18
80
|
* Resolve Linux user info from username via getent passwd.
|
|
19
81
|
* Returns { uid, gid, home, user, shell } or throws on failure.
|
|
@@ -84,13 +146,14 @@ function fsAsUser(op, args, osUserInfo) {
|
|
|
84
146
|
"var buf = fs.readFileSync(f);",
|
|
85
147
|
"process.stdout.write(buf.toString('base64'));",
|
|
86
148
|
].join(" ");
|
|
87
|
-
var
|
|
149
|
+
var binSpawn = wrapSpawnAsUser(process.execPath, ["-e", script], {
|
|
88
150
|
encoding: "utf8",
|
|
89
151
|
timeout: 10000,
|
|
90
152
|
uid: osUserInfo.uid,
|
|
91
153
|
gid: osUserInfo.gid,
|
|
92
154
|
maxBuffer: 50 * 1024 * 1024,
|
|
93
155
|
});
|
|
156
|
+
var binOutput = execFileSync(binSpawn.command, binSpawn.args, binSpawn.options);
|
|
94
157
|
return { buffer: Buffer.from(binOutput.trim(), "base64") };
|
|
95
158
|
} else if (op === "write") {
|
|
96
159
|
script = [
|
|
@@ -104,12 +167,13 @@ function fsAsUser(op, args, osUserInfo) {
|
|
|
104
167
|
throw new Error("Unknown fsAsUser operation: " + op);
|
|
105
168
|
}
|
|
106
169
|
|
|
107
|
-
var
|
|
170
|
+
var fsSpawn = wrapSpawnAsUser(process.execPath, ["-e", script], {
|
|
108
171
|
encoding: "utf8",
|
|
109
172
|
timeout: 10000,
|
|
110
173
|
uid: osUserInfo.uid,
|
|
111
174
|
gid: osUserInfo.gid,
|
|
112
175
|
});
|
|
176
|
+
var output = execFileSync(fsSpawn.command, fsSpawn.args, fsSpawn.options);
|
|
113
177
|
|
|
114
178
|
return JSON.parse(output.trim());
|
|
115
179
|
}
|
|
@@ -554,6 +618,9 @@ module.exports = {
|
|
|
554
618
|
grantAllUsersAccess: grantAllUsersAccess,
|
|
555
619
|
installClaudeCli: installClaudeCli,
|
|
556
620
|
claudeAvailableForUser: claudeAvailableForUser,
|
|
621
|
+
setInheritGroups: setInheritGroups,
|
|
622
|
+
getInheritGroups: getInheritGroups,
|
|
623
|
+
wrapSpawnAsUser: wrapSpawnAsUser,
|
|
557
624
|
deactivateLinuxUser: deactivateLinuxUser,
|
|
558
625
|
ensureProjectsDir: ensureProjectsDir,
|
|
559
626
|
isHomeDirectory: isHomeDirectory,
|
package/lib/project-http.js
CHANGED
|
@@ -335,7 +335,8 @@ function attachHTTP(ctx) {
|
|
|
335
335
|
});
|
|
336
336
|
}
|
|
337
337
|
console.log("[skill-install] spawning: npx skills add " + url + " --skill " + skill + " --yes " + scopeFlag + " (cwd: " + spawnCwd + ")");
|
|
338
|
-
var
|
|
338
|
+
var skillAddSpawn = require("./os-users").wrapSpawnAsUser("npx", ["skills", "add", url, "--skill", skill, "--yes", scopeFlag], skillSpawnOpts);
|
|
339
|
+
var child = spawn(skillAddSpawn.command, skillAddSpawn.args, skillAddSpawn.options);
|
|
339
340
|
var stdoutBuf = "";
|
|
340
341
|
var stderrBuf = "";
|
|
341
342
|
child.stdout.on("data", function (chunk) {
|
|
@@ -427,11 +428,12 @@ function attachHTTP(ctx) {
|
|
|
427
428
|
if (uninstallUserInfo) {
|
|
428
429
|
// Run rm as target user to respect permissions
|
|
429
430
|
var rmScript = "var fs = require('fs'); fs.rmSync(" + JSON.stringify(resolved) + ", { recursive: true, force: true });";
|
|
430
|
-
|
|
431
|
+
var rmSpawn = require("./os-users").wrapSpawnAsUser(process.execPath, ["-e", rmScript], {
|
|
431
432
|
uid: uninstallUserInfo.uid,
|
|
432
433
|
gid: uninstallUserInfo.gid,
|
|
433
434
|
timeout: 10000,
|
|
434
435
|
});
|
|
436
|
+
execFileSync(rmSpawn.command, rmSpawn.args, rmSpawn.options);
|
|
435
437
|
} else {
|
|
436
438
|
fs.rmSync(resolved, { recursive: true, force: true });
|
|
437
439
|
}
|
package/lib/project-sessions.js
CHANGED
|
@@ -1700,7 +1700,7 @@ function attachSessions(ctx) {
|
|
|
1700
1700
|
|
|
1701
1701
|
// --- Daemon config / server management (admin-only in multi-user mode) ---
|
|
1702
1702
|
if (msg.type === "get_daemon_config" || msg.type === "set_pin" || msg.type === "set_keep_awake" ||
|
|
1703
|
-
msg.type === "set_auto_continue" || msg.type === "set_image_retention" || msg.type === "shutdown_server" || msg.type === "restart_server") {
|
|
1703
|
+
msg.type === "set_auto_continue" || msg.type === "set_inherit_groups" || msg.type === "set_image_retention" || msg.type === "shutdown_server" || msg.type === "restart_server") {
|
|
1704
1704
|
if (usersModule.isMultiUser()) {
|
|
1705
1705
|
var _wsUser = ws._clayUser;
|
|
1706
1706
|
if (!_wsUser || _wsUser.role !== "admin") {
|
|
@@ -1744,6 +1744,15 @@ function attachSessions(ctx) {
|
|
|
1744
1744
|
return true;
|
|
1745
1745
|
}
|
|
1746
1746
|
|
|
1747
|
+
if (msg.type === "set_inherit_groups") {
|
|
1748
|
+
if (typeof opts.onSetInheritGroups === "function") {
|
|
1749
|
+
var igResult = opts.onSetInheritGroups(msg.value);
|
|
1750
|
+
sendTo(ws, { type: "set_inherit_groups_result", ok: igResult.ok, inheritGroups: igResult.inheritGroups });
|
|
1751
|
+
send({ type: "inherit_groups_changed", inheritGroups: igResult.inheritGroups });
|
|
1752
|
+
}
|
|
1753
|
+
return true;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1747
1756
|
if (msg.type === "get_claude_allow_list") {
|
|
1748
1757
|
var galUid = ws._clayUser ? ws._clayUser.id : null;
|
|
1749
1758
|
var galManaged = [];
|
package/lib/public/index.html
CHANGED
|
@@ -1151,6 +1151,7 @@
|
|
|
1151
1151
|
</optgroup>
|
|
1152
1152
|
<optgroup label="Server">
|
|
1153
1153
|
<option value="keep-awake" class="hidden" id="settings-keep-awake-opt">Keep Awake</option>
|
|
1154
|
+
<option value="inherit-groups" class="hidden" id="settings-inherit-groups-opt">User Groups</option>
|
|
1154
1155
|
<option value="restart">Restart</option>
|
|
1155
1156
|
<option value="shutdown">Shutdown</option>
|
|
1156
1157
|
</optgroup>
|
|
@@ -1177,6 +1178,7 @@
|
|
|
1177
1178
|
<div class="settings-nav-separator"></div>
|
|
1178
1179
|
<div class="settings-nav-category">Server</div>
|
|
1179
1180
|
<button class="settings-nav-item hidden" data-section="keep-awake" id="settings-keep-awake-nav"><span>Keep Awake</span></button>
|
|
1181
|
+
<button class="settings-nav-item hidden settings-admin-only" data-section="inherit-groups" id="settings-inherit-groups-nav"><span>User Groups</span></button>
|
|
1180
1182
|
<button class="settings-nav-item" data-section="restart"><span>Restart</span></button>
|
|
1181
1183
|
<button class="settings-nav-item settings-nav-danger" data-section="shutdown"><span>Shutdown</span></button>
|
|
1182
1184
|
</div>
|
|
@@ -1430,6 +1432,19 @@
|
|
|
1430
1432
|
</label>
|
|
1431
1433
|
</div>
|
|
1432
1434
|
</div>
|
|
1435
|
+
<div class="server-settings-section hidden settings-admin-only" data-section="inherit-groups" id="settings-inherit-groups-section">
|
|
1436
|
+
<h2>User Groups</h2>
|
|
1437
|
+
<div class="settings-card">
|
|
1438
|
+
<label class="settings-toggle-row">
|
|
1439
|
+
<div>
|
|
1440
|
+
<span class="settings-label">Inherit supplementary groups</span>
|
|
1441
|
+
<div class="settings-hint">Give each session the user's full group membership (docker, video, etc.), matching a normal login. Disable to run sessions with only the primary group. Requires <code>setpriv</code> (util-linux). Applies to new sessions.</div>
|
|
1442
|
+
</div>
|
|
1443
|
+
<input type="checkbox" id="settings-inherit-groups">
|
|
1444
|
+
<span class="toggle-track"><span class="toggle-thumb"></span></span>
|
|
1445
|
+
</label>
|
|
1446
|
+
</div>
|
|
1447
|
+
</div>
|
|
1433
1448
|
<div class="server-settings-section" data-section="restart">
|
|
1434
1449
|
<h2>Restart</h2>
|
|
1435
1450
|
<p class="settings-hint" style="margin-bottom: 16px;">Restart the Clay daemon process. All active sessions will be briefly interrupted and clients will automatically reconnect.</p>
|
|
@@ -25,7 +25,7 @@ import { startThinking, appendThinking, stopThinking, resetThinkingGroup, create
|
|
|
25
25
|
import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
|
|
26
26
|
import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate, handleFsSearch } from './filebrowser.js';
|
|
27
27
|
import { isProjectSettingsOpen, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './project-settings.js';
|
|
28
|
-
import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
|
|
28
|
+
import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleInheritGroupsChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
|
|
29
29
|
import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
|
|
30
30
|
import { attachTuiView, detachTuiView, setTuiSuspendedView, tuiHandleTermOutput, tuiHandleTermResized, tuiHandleTermExited, tuiHandleTermClosed } from './session-tui-view.js';
|
|
31
31
|
import { handleTuiTranscriptState } from './tui-grab.js';
|
|
@@ -1705,6 +1705,11 @@ export function processMessage(msg) {
|
|
|
1705
1705
|
handleKeepAwakeChanged(msg);
|
|
1706
1706
|
break;
|
|
1707
1707
|
|
|
1708
|
+
case "set_inherit_groups_result":
|
|
1709
|
+
case "inherit_groups_changed":
|
|
1710
|
+
handleInheritGroupsChanged(msg);
|
|
1711
|
+
break;
|
|
1712
|
+
|
|
1708
1713
|
case "set_auto_continue_result":
|
|
1709
1714
|
case "auto_continue_changed":
|
|
1710
1715
|
handleAutoContinueChanged(msg);
|
|
@@ -157,6 +157,17 @@ export function initServerSettings(appCtx) {
|
|
|
157
157
|
});
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// Inherit supplementary groups toggle (OS-users mode)
|
|
161
|
+
var inheritGroupsToggle = document.getElementById("settings-inherit-groups");
|
|
162
|
+
if (inheritGroupsToggle) {
|
|
163
|
+
inheritGroupsToggle.addEventListener("change", function () {
|
|
164
|
+
var ws = ctx.ws;
|
|
165
|
+
if (ws && ws.readyState === 1) {
|
|
166
|
+
ws.send(JSON.stringify({ type: "set_inherit_groups", value: this.checked }));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
160
171
|
// Image retention select
|
|
161
172
|
var imageRetentionSelect = document.getElementById("settings-image-retention");
|
|
162
173
|
if (imageRetentionSelect) {
|
|
@@ -482,6 +493,10 @@ export function updateDaemonConfig(config) {
|
|
|
482
493
|
var keepAwakeToggle = document.getElementById("settings-keep-awake");
|
|
483
494
|
if (keepAwakeToggle) keepAwakeToggle.checked = !!config.keepAwake;
|
|
484
495
|
|
|
496
|
+
// Inherit supplementary groups (only relevant in OS-users mode)
|
|
497
|
+
var inheritGroupsToggle = document.getElementById("settings-inherit-groups");
|
|
498
|
+
if (inheritGroupsToggle) inheritGroupsToggle.checked = config.inheritGroups !== false;
|
|
499
|
+
|
|
485
500
|
// Image retention
|
|
486
501
|
var imageRetentionSelect = document.getElementById("settings-image-retention");
|
|
487
502
|
if (imageRetentionSelect && config.imageRetentionDays !== undefined) {
|
|
@@ -517,6 +532,20 @@ export function updateDaemonConfig(config) {
|
|
|
517
532
|
if (keepAwakeNav) keepAwakeNav.classList.add("hidden");
|
|
518
533
|
if (keepAwakeOpt) keepAwakeOpt.classList.add("hidden");
|
|
519
534
|
}
|
|
535
|
+
|
|
536
|
+
// Show inherit-groups section/nav only in OS-users mode
|
|
537
|
+
var inheritGroupsSection = document.getElementById("settings-inherit-groups-section");
|
|
538
|
+
var inheritGroupsNav = document.getElementById("settings-inherit-groups-nav");
|
|
539
|
+
var inheritGroupsOpt = document.getElementById("settings-inherit-groups-opt");
|
|
540
|
+
if (config.osUsers) {
|
|
541
|
+
if (inheritGroupsSection) inheritGroupsSection.classList.remove("hidden");
|
|
542
|
+
if (inheritGroupsNav) inheritGroupsNav.classList.remove("hidden");
|
|
543
|
+
if (inheritGroupsOpt) inheritGroupsOpt.classList.remove("hidden");
|
|
544
|
+
} else {
|
|
545
|
+
if (inheritGroupsSection) inheritGroupsSection.classList.add("hidden");
|
|
546
|
+
if (inheritGroupsNav) inheritGroupsNav.classList.add("hidden");
|
|
547
|
+
if (inheritGroupsOpt) inheritGroupsOpt.classList.add("hidden");
|
|
548
|
+
}
|
|
520
549
|
}
|
|
521
550
|
|
|
522
551
|
export function handleSetPinResult(msg) {
|
|
@@ -532,6 +561,11 @@ export function handleKeepAwakeChanged(msg) {
|
|
|
532
561
|
if (keepAwakeToggle) keepAwakeToggle.checked = !!msg.keepAwake;
|
|
533
562
|
}
|
|
534
563
|
|
|
564
|
+
export function handleInheritGroupsChanged(msg) {
|
|
565
|
+
var inheritGroupsToggle = document.getElementById("settings-inherit-groups");
|
|
566
|
+
if (inheritGroupsToggle) inheritGroupsToggle.checked = msg.inheritGroups !== false;
|
|
567
|
+
}
|
|
568
|
+
|
|
535
569
|
export function handleAutoContinueChanged(msg) {
|
|
536
570
|
// Auto-continue is now per-user; server broadcast no longer updates UI
|
|
537
571
|
}
|
package/lib/server.js
CHANGED
|
@@ -153,6 +153,7 @@ function createServer(opts) {
|
|
|
153
153
|
var onGetDaemonConfig = opts.onGetDaemonConfig || null;
|
|
154
154
|
var onSetPin = opts.onSetPin || null;
|
|
155
155
|
var onSetKeepAwake = opts.onSetKeepAwake || null;
|
|
156
|
+
var onSetInheritGroups = opts.onSetInheritGroups || null;
|
|
156
157
|
var onSetImageRetention = opts.onSetImageRetention || null;
|
|
157
158
|
var onShutdown = opts.onShutdown || null;
|
|
158
159
|
var onRestart = opts.onRestart || null;
|
|
@@ -1257,6 +1258,7 @@ function createServer(opts) {
|
|
|
1257
1258
|
onGetDaemonConfig: onGetDaemonConfig,
|
|
1258
1259
|
onSetPin: onSetPin,
|
|
1259
1260
|
onSetKeepAwake: onSetKeepAwake,
|
|
1261
|
+
onSetInheritGroups: onSetInheritGroups,
|
|
1260
1262
|
onSetImageRetention: onSetImageRetention,
|
|
1261
1263
|
onSetUpdateChannel: onSetUpdateChannel,
|
|
1262
1264
|
updateChannel: onGetDaemonConfig ? (onGetDaemonConfig().updateChannel || "stable") : "stable",
|
package/lib/terminal.js
CHANGED
|
@@ -39,7 +39,10 @@ function createTerminal(cwd, cols, rows, osUserInfo, opts) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
var args = osUserInfo ? ["-l"] : [];
|
|
42
|
-
|
|
42
|
+
// Route through setpriv when group inheritance is enabled so the terminal
|
|
43
|
+
// gets the user's supplementary groups (docker, etc.), not just primary gid.
|
|
44
|
+
var ptySpawn = require("./os-users").wrapSpawnAsUser(shell, args, spawnOpts);
|
|
45
|
+
var term = pty.spawn(ptySpawn.command, ptySpawn.args, ptySpawn.options);
|
|
43
46
|
|
|
44
47
|
if (opts && opts.initialInput) {
|
|
45
48
|
try { term.write(opts.initialInput); } catch (e) {}
|
|
@@ -517,13 +517,16 @@ function spawnWorker(linuxUser, workerScriptPath, cwd) {
|
|
|
517
517
|
console.log("[yoke/claude] Spawning worker: uid=" + userInfo.uid + " gid=" + userInfo.gid + " cwd=" + cwd + " socket=" + socketPath);
|
|
518
518
|
console.log("[yoke/claude] Worker script: " + workerScriptPath);
|
|
519
519
|
console.log("[yoke/claude] Node: " + process.execPath);
|
|
520
|
-
|
|
520
|
+
// Route through setpriv when group inheritance is enabled so the worker
|
|
521
|
+
// (and the agent CLI it spawns) inherits the user's supplementary groups.
|
|
522
|
+
var workerSpawn = require("../../os-users").wrapSpawnAsUser(process.execPath, [workerScriptPath, socketPath], {
|
|
521
523
|
uid: userInfo.uid,
|
|
522
524
|
gid: userInfo.gid,
|
|
523
525
|
env: workerEnv,
|
|
524
526
|
cwd: cwd,
|
|
525
527
|
stdio: ["ignore", "pipe", "pipe"],
|
|
526
528
|
});
|
|
529
|
+
worker.process = spawn(workerSpawn.command, workerSpawn.args, workerSpawn.options);
|
|
527
530
|
|
|
528
531
|
worker.process.stdout.on("data", function(data) {
|
|
529
532
|
console.log("[sdk-worker:" + linuxUser + "] " + data.toString().trim());
|
package/package.json
CHANGED