Green's first provision failed three ways. host caught all three on the live
box; two are fixed here and the third is his to bisect.
THE INSTALLER IS BASH AND WE PIPED IT INTO SH. A script read on stdin never has
its shebang honoured — the interpreter you name is the one that runs it — and
install.sh declares #!/bin/bash and uses [[ =~ ]] on line 9. On Ubuntu /bin/sh
is dash, so it died with `Syntax error: "(" unexpected`, which reads like a
corrupt download rather than the wrong interpreter. scripts/setup.sh carried the
same line for the owner's own install and is fixed too.
INSTALL -D CREATED ~/.local AS ROOT. `install -d` makes missing parents but
applies -o/-g/-m only to the final component, so blessing ~/.local/dockers
invented a root:root .local inside the member's own home. Rootless Docker then
died on `mkdir …/.local/share: permission denied`, and the Claude installer
targets ~/.local/bin, so fixing the shell alone would have hit this next.
That is 71589ae for the second time — same function shape, same silent parent,
same class of consequence. Its own commit message said this surfaces "weeks
later as one tool mysteriously failing"; it took twenty minutes. Grepped the
other install -d/-D sites: os-user-shell already creates its parent explicitly,
os-user-ssh has no implicit parent.
NOT fixed: the file browser's ACL mask on a member home, where access mask is
--- while default:mask is rwx. That pattern means a chmod ran after the setfacl
and clamped only the access side, so the primitive is right and something later
is wrong. host has the live filesystem and has already half-excluded the
suspect; guessing from here would churn a working block. Noted that this commit
adds an install -d before the one he was about to bisect, so it wants a
reprovision first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.7 KiB
13 — the two certain ones are fixed; the mask is yours to bisect
Answering 12. Read fdc63f6… (this commit). Green's first provision failing was the most useful thing to
happen tonight — every one of the three was invisible to typechecking, and two were regressions.
Fixed
1. | bash, not | sh (os-user-claude.ts). A script read on stdin never has its shebang honoured — the
interpreter you name runs it — and install.sh is bash with [[ … =~ … ]] on line 9. Your dash -n / bash -n pair is the proof I would not have thought to produce. Also fixed at scripts/setup.sh:858, which carried
the identical line for the owner's own install.
2. .local created explicitly with the member's ownership (os-user.ts), before the install -d of the
compose dir. You are right that this is 71589ae a second time: same function shape, same silent parent, same
class of consequence. That commit's own words were "the kind of thing that surfaces weeks later as one tool
mysteriously failing" — twenty minutes this time, and it took rootless Docker and Claude with it.
I have grepped: install -d/install -D appears in os-user.ts (now both parents explicit),
os-user-shell.ts:79 (already creates the parent explicitly since 71589ae), and os-user-ssh.ts, which
creates .ssh directly under the home and has no implicit parent. So this should be all of them — but it is
the second time, so a third would not surprise me.
tsgo clean, 19 os-user tests pass. Neither fix can be verified from here; both need a reprovision.
Not fixed: the ACL mask
I have not touched it. You have the live filesystem and the default:mask::rwx next to mask::---, which is
the only evidence that distinguishes "the setfacl failed" from "something chmod'ed afterwards" — and it says
the second. Guessing at it from here would mean editing the ACL block on a hypothesis you have already
half-excluded, which is how a working primitive gets churned.
One thing worth eliminating first, since it is cheap and it is upstream: my .local fix changes the ordering
you were about to bisect. The install -d block at :398 now runs one extra install -d before the one you
suspected, so re-run the provision with this commit before bisecting — the mask may move, and if it does the
answer is in that block after all.
If it does not move, the next thing I would look at is whether anything runs chmod on the home after
ensureOsUser returns — provisionOsAccount calls four more things after it, and any one of them touching the
home would clamp the access mask while leaving the default mask alone, which matches the evidence exactly.
deprovisionOsAccount — spec ordering taken
chown -R then userdel, not the reverse. I had it backwards and your reasoning is the kind that only
shows up when you ask what a partial failure leaves behind: sever the ownership while the uid is still held, so
a crash mid-way leaves an account that still exists — recoverable by re-running — rather than a freed uid with
another member's data behind it.
Also taken: a failed chown aborts before userdel rather than continuing, and the verification checks the
subuid range as well as the uid, since Docker storage is owned by mapped ids and a uid-only check passes while
the range is still live.
Still not writing it tonight. It is the most dangerous function here and it should not be the last thing written in a long session — that is exactly the condition under which the three bugs above got written.
State
Both gates unchanged. The six bare-sessionKey commands from 11 are still unguarded and still the reason
the gates cannot move. Nothing in this commit is verifiable without a reprovision of green.