stop creating the container bind-mount directory too

~/.local/dockers existed only so a rootless container's inner uid could traverse to a
bind source. No daemon, no need. Commented out with its reasoning intact in
confineUserTree.

.local itself stays — it is not Docker's. The claude installer targets ~/.local/bin,
and the comment above it records that directory being created root-owned and blocking
the install.

Nothing to change in deprovisionOsAccount: it never called os-user-docker.ts. Its
only Docker-shaped part is capturing /etc/subuid before userdel, which already treats
a missing entry as normal and is worth keeping for any rootless tooling.

Verified: transpiles. No test referenced composeDir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 11:58:06 +00:00
co-authored by Claude Opus 5
parent 7b32f5bc2f
commit 41cdd1e63a
+43 -31
View File
@@ -369,7 +369,17 @@ export async function confineUserTree(params: {
} }
} }
// ── One directory where container bind mounts can live ── // ── One directory where container bind mounts can live: DISABLED 2026-08-13, code kept ──
//
// `~/.local/dockers` existed only for rootless Docker, and members no longer get a daemon — see the
// commented-out step in api/users/provision-os.ts. Creating a blessed bind-mount directory for a daemon
// that is not there is a promise about container storage the platform no longer keeps.
//
// `.local` itself STAYS. It is not Docker's: the `claude` installer targets `~/.local/bin`, and the
// paragraph below is the record of that directory being created root-owned and blocking it.
//
// The reasoning is kept in full because turning containers back on means uncommenting the block, not
// rediscovering why 711 and why the ACLs come off.
// //
// The default ACLs above are inherited by everything created in the home afterwards, including // The default ACLs above are inherited by everything created in the home afterwards, including
// `default:other::---`. A rootless container's INNER uid is neither the service user nor the member — // `default:other::---`. A rootless container's INNER uid is neither the service user nor the member —
@@ -416,37 +426,39 @@ export async function confineUserTree(params: {
]); ]);
if (!madeLocalDir.ok) return { ok: false, error: `could not create ${localDir}: ${madeLocalDir.out}` }; if (!madeLocalDir.ok) return { ok: false, error: `could not create ${localDir}: ${madeLocalDir.out}` };
const composeDir = join(home, '.local', 'dockers'); // const composeDir = join(home, '.local', 'dockers');
const madeComposeDir = await run([ // const madeComposeDir = await run([
'sudo', // 'sudo',
'-n', // '-n',
'install', // 'install',
'-d', // '-d',
'-o', // '-o',
String(params.uid), // String(params.uid),
'-g', // '-g',
String(params.gid), // String(params.gid),
// 711, not 700, and this is the whole point of the directory. A container's inner uid is `other`, so it // // 711, not 700, and this is the whole point of the directory. A container's inner uid is `other`, so
// needs `x` HERE to reach a bind source inside — 700 blocks the path before any ACL matters. `r` stays // // it needs `x` HERE to reach a bind source inside — 700 blocks the path before any ACL matters. `r`
// off, so nothing can list it. Safe because the home above is 700: no other account can traverse this // // stays off, so nothing can list it. Safe because the home above is 700: no other account can
// far in the first place, and the only uids that get here are the member's own containers. // // traverse this far in the first place, and the only uids that get here are the member's own
'-m', // // containers.
'711', // '-m',
composeDir, // '711',
]); // composeDir,
if (!madeComposeDir.ok) return { ok: false, error: `could not create ${composeDir}: ${madeComposeDir.out}` }; // ]);
// if (!madeComposeDir.ok) return { ok: false, error: `could not create ${composeDir}: ${madeComposeDir.out}` };
// `-b`, not `-k`: remove ACCESS entries as well as defaults, leaving plain POSIX modes.
// //
// `-k` alone left `mask::---` behind — inherited named entries with every permission masked off, reading // // `-b`, not `-k`: remove ACCESS entries as well as defaults, leaving plain POSIX modes.
// as `user:pastilhas:rwx #effective:---`. An ACL that says one thing and means another is worse than no // //
// ACL, and container storage is the one place in the home that wants ordinary mode bits and nothing else. // // `-k` alone left `mask::---` behind — inherited named entries with every permission masked off,
// // // reading as `user:pastilhas:rwx #effective:---`. An ACL that says one thing and means another is worse
// AFTER the recursive grant above, or what it just set is re-inherited here. // // than no ACL, and container storage is the one place in the home that wants ordinary mode bits and
const stripped = await run(['sudo', '-n', 'setfacl', '-R', '-b', composeDir]); // // nothing else.
if (!stripped.ok) { // //
return { ok: false, error: `could not clear inherited ACLs from ${composeDir}: ${stripped.out}` }; // // AFTER the recursive grant above, or what it just set is re-inherited here.
} // const stripped = await run(['sudo', '-n', 'setfacl', '-R', '-b', composeDir]);
// if (!stripped.ok) {
// return { ok: false, error: `could not clear inherited ACLs from ${composeDir}: ${stripped.out}` };
// }
return { ok: true }; return { ok: true };
} catch (ex) { } catch (ex) {