pipe the installer into bash, and stop inventing a root-owned .local
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>
This commit is contained in:
@@ -106,9 +106,12 @@ export async function provisionClaudeCli(params: { email: string; osUser: string
|
||||
const present = await asMember(params.osUser, ['test', '-x', binPath]);
|
||||
if (present.ok) return { ok: true, binPath, wrote: false };
|
||||
|
||||
// `sh -c` with the pipe inside it, because the pipe has to be interpreted by the member's shell and not by
|
||||
// this process — `runAs` takes an argv, not a command line.
|
||||
const install = await asMember(params.osUser, ['sh', '-c', `set -e; curl -fsSL ${CLAUDE_INSTALL_URL} | sh`]);
|
||||
// Piped into `bash`, not `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 `| sh` died with `Syntax error: "(" unexpected`, which reads like a broken
|
||||
// download rather than the wrong interpreter. Reproduced on the live server: `dash -n` fails there, `bash -n`
|
||||
// is clean.
|
||||
const install = await asMember(params.osUser, ['sh', '-c', `set -e; curl -fsSL ${CLAUDE_INSTALL_URL} | bash`]);
|
||||
|
||||
// The installer's exit code is not the gate — the same lesson as rootless Docker in
|
||||
// `docs/per-user-linux-accounts.md`. What matters is whether the binary is now there and runnable.
|
||||
|
||||
Reference in New Issue
Block a user