create docker's storage ourselves, so the strip is not a race

host verified green's reprovision: claude 2.1.228 installs and runs as the
member, the file browser reads their home, rootless Docker runs and sees 0
containers while the owner has 8. First end-to-end proof of any of this.

One thing came out dirty. ~/.local/share/docker carried the home's inherited
default ACLs after a "successful" strip, because the strip was guarded on
existsSync and only the daemon creates that directory. On a first run the guard
was false and the strip no-opped; the retry then started the daemon, which
created the directory and inherited the defaults. The run meant to clean it up
was the one that made it, and the guard could not tell "nothing to strip" from
"nothing there yet".

Now created by us before the daemon exists — member-owned, 700, nothing to
inherit — and the strip is unconditional afterwards, repairing an account
provisioned before this and no-opping on a clean one. A guard that depends on
another process having got there first is a race however it is written; the fix
is owning the order rather than testing for it.

Third bug of this class tonight: an implicit parent directory, a strip guarded
on another process's work, and an installer piped into the wrong shell. All
three were invisible until a real member account existed, which is the argument
for making the second one sooner than feels necessary.

Left alone deliberately: .local being unreadable by the platform (a decision
about intent, not a defect, and the owner's), and the -u 70 + bind mount
observation, whose probe host already distrusts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 00:08:55 +00:00
co-authored by Claude Opus 5
parent e99949b1ac
commit 893130940e
2 changed files with 99 additions and 6 deletions
+33 -6
View File
@@ -124,6 +124,33 @@ export async function provisionRootlessDocker(params: { osUser: string; uid: num
const already = existsSync(dockerSocketFor(params.uid));
// Docker's storage, created by us and created CLEAN, before the daemon exists to create it dirty.
//
// The strip below used to be the whole story, guarded on the directory existing — which is false on a first
// run, because only the daemon creates it. So on a fresh account the strip no-opped, the daemon then made
// the directory itself and inherited the home's default ACLs, and the only cure was a retry: the very run
// that was supposed to clean it up was the one that created it. Verified on green's first provision, where
// it came out carrying `default:other::---` after a "successful" strip.
//
// A guard that depends on another process having got there first is a race however it is written, so the
// fix is ownership of the order: make it ourselves, with the member's uid and no defaults to inherit. Same
// shape as creating `~/.local` explicitly rather than letting `install -d` invent it as root.
const dockerStorage = `${params.home}/.local/share/docker`;
const madeStorage = await sudo([
'install',
'-d',
'-o',
String(params.uid),
'-g',
String(params.uid),
'-m',
'700',
dockerStorage,
]);
if (!madeStorage.ok) {
return { ok: false, error: `could not create ${dockerStorage}: ${madeStorage.out}` };
}
// ── The setup tool's exit code is deliberately NOT the gate ──
//
// It writes `~/.config/systemd/user/docker.service` and then runs `systemctl --user start docker.service`
@@ -168,12 +195,12 @@ export async function provisionRootlessDocker(params: { osUser: string; uid: num
// access ACLs on the home itself are untouched, which is what the file browser depends on. Losing the
// platform's reach into Docker's internal storage is no loss: it is image layers and volume data, read
// through `docker` or not at all.
const dockerData = `${params.home}/.local/share/docker`;
if (existsSync(dockerData)) {
const stripped = await sudo(['setfacl', '-R', '-k', dockerData]);
if (!stripped.ok) {
return { ok: false, error: `could not clear inherited ACLs from ${dockerData}: ${stripped.out}` };
}
// Unconditional now, and no longer the thing that has to win a race: the directory is ours from above, so
// this is repair for an account provisioned before that existed, and a no-op on a clean one. The guard it
// replaces could not tell "nothing to strip" from "nothing there yet", and answered the same way to both.
const stripped = await sudo(['setfacl', '-R', '-k', dockerStorage]);
if (!stripped.ok) {
return { ok: false, error: `could not clear inherited ACLs from ${dockerStorage}: ${stripped.out}` };
}
// Proof, not assumption: ask their daemon who it is. `docker version --format` on the SERVER half only