install.sh: support Linux, not just Homebrew - #127
Conversation
scripts/install.sh is advertised in the README as THE one-line install, but
its prereq layer only knew brew: on any Linux box it told the user to
"Install Homebrew first", which is nonsense advice. There is a real Ubuntu
device (VTA) that needs this today, and every Linux cloner of this public
repo hits the same wall.
Prereq layer:
- Detect the platform with `uname -s` and pick a package manager: brew on
Darwin, apt-get / dnf / pacman / zypper on Linux. An unrecognised
`uname -s` now exits non-zero naming the platform and listing the manual
steps, instead of falling through.
- No package manager, or no root and no sudo, prints the exact package
names plus the manual step and exits non-zero. Never a silent fall-through
and never a claim of success.
- Every install is printed before it runs, and sudo is called out
explicitly. Under `curl | bash` there is no TTY to confirm on, so the
installer refuses to run an unattended sudo install: it prints the
commands and exits 1. IAK_ASSUME_YES=1 is the documented opt-in.
- After installing, re-verify node/npm/git/tmux rather than assuming the
package manager did what was wanted.
node 20+ on Debian/Ubuntu: the distro `nodejs` package is below our floor
(Ubuntu 24.04's candidate is 18.19.1, measured), so apt systems default to
NodeSource's official apt repo. That pipes a third-party script into a root
shell, so the installer says so out loud before doing it and points at nvm
and IAK_NODE_SOURCE=distro as alternatives. With IAK_NODE_SOURCE=distro the
re-verify step still refuses to continue on node 18.
Other macOS-only assumptions fixed in the same pass, since a fix in one
place is not a fix:
- The hook-wiring step shelled out to python3. A stock ubuntu:24.04 has no
python3, so the installer needed a second language runtime it never
declared as a prereq. Rewritten in node, which is already a hard prereq
by that point. Verified byte-identical to the python it replaces against
a settings.json with pre-existing unrelated hooks, and it now reports
malformed JSON instead of throwing a traceback.
- `ipconfig getifaddr en0` silently fell back to 127.0.0.1 on Linux, which
is useless when the whole point is a LAN URL for the phone. Linux now
asks the routing table (`ip -4 route get`), with `hostname -I` as backup,
and if it really cannot tell it says the URL will not work and how to
find the real one.
- The `brew install qrencode` tip is now per-manager.
- The macOS Accessibility / osascript block printed unconditionally. It is
Darwin-only now; Linux gets a note that the portable Stop-hook resume
path is what delivers messages there.
- IAK_INSTALL_WATCHDOG installed a launchd LaunchAgent unconditionally. It
is Darwin-gated now; on Linux it says the supervisor is launchd-only and
points at running team-watchdog.mjs directly. A systemd unit is NOT part
of this change.
Two bugs found while testing in the container, both of which produced a
half-done install that exited 0:
- apt's debconf prompted on tzdata and read from stdin. Under `curl | bash`
stdin IS the remaining script, so apt ate the rest of the installer and
bash exited 0 having only installed packages: no clone, no config, no
hooks, and a success exit code. Plan commands now run with stdin closed,
plus DEBIAN_FRONTEND=noninteractive.
- The final report printed "Daemon: tmux session 'iak-mcp'" and a listener
URL even when the daemon had already exited, which it always does on a
fresh install because the starter config has no channel configured. It
now checks the session and says plainly when the daemon is not running.
Also adds IAK_DRY_RUN=1, which prints the platform and prereq plan and
changes nothing.
Tested in stock ubuntu:24.04 containers (no node/git/tmux/python3/curl/sudo
preinstalled): fresh install end to end, a second run for idempotence
("Hooks already present", no package work), and a no-package-manager box.
Negative controls: a fake `uname` reporting Plan9 exits 1; forcing the
distro node (18.19.1) is rejected by the re-verify step; and the new daemon
check was shown reporting both NOT running and running. The Darwin path was
exercised on macOS via IAK_DRY_RUN and a PATH sandbox with a fake brew that
would have failed loudly if called; this machine's own prereqs, config,
hooks and daemon were never touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0364eef6fa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # This used to be a python3 heredoc. A stock ubuntu:24.04 has no python3, so | ||
| # that made the installer need a second language runtime it never declared as | ||
| # a prereq. node 20+ is already guaranteed by this point, so use it. |
There was a problem hiding this comment.
Keep Python available for the installed SessionStart hook
On a fresh Linux installation without python3—the exact environment this rewrite targets—the installer now succeeds and registers session-bootstrap.sh, but that script still invokes python3 to read configuration/input and to emit its final hook JSON. Since the final invocation suppresses stderr and exits 0 afterward, every SessionStart silently produces no context, so the advertised self-arming room-agent behavior never starts. Either port session-bootstrap.sh to Node as well or retain Python as a checked/installed prerequisite.
Useful? React with 👍 / 👎.
scripts/install.shis advertised in the README as THE one-line install, but its prereq layer only knew Homebrew. On Linux it told you to "Install Homebrew (https://brew.sh) first", which is nonsense advice. There is a real Ubuntu box (VTA) that needs this today, and every Linux cloner of this public repo hit the same wall.Do not merge yet — opened for review.
macOS-only assumptions found in install.sh
need_brewprereq install; "Install Homebrew first" on failurepython3— stockubuntu:24.04has noneipconfig getifaddr en0/en1for the LAN URLip -4 route get,hostname -I)brew install qrencodetipIAK_INSTALL_WATCHDOGinstalls a launchd LaunchAgentosascriptGUI-wake scripts (claudemb-wake.sh,claude-gui-*.sh) are macOS-onlyscripts/start-all.shuses launchctlNo BSD
sed -i ''anywhere in the repo. The two hook scripts the installer wires (check-rooms-hook.sh,claudecode-stop-resume.sh) andsession-bootstrap.share already portable.A full Linux port of the wake layer is bigger than this PR should be, so this is scoped to the prereq layer plus everything in install.sh that was trivially fixable. Items 8-10 are the remainder.
Two bugs found while testing, both producing a half-done install that exited 0
apt-getprompted via debconf ontzdataand read from stdin. Undercurl | bashstdin is the remaining script text, so apt consumed the rest of the installer and bash exited 0 having only installed packages: no clone, no config, no hooks, success exit code. Plan commands now run with stdin closed, plusDEBIAN_FRONTEND=noninteractive. The evidence was<STDIN> line 254in a debconf perl warning.Daemon: tmux session 'iak-mcp'and a listener URL unconditionally, but the daemon exits immediately on a fresh install (no mcp.confirmations channels configured) because the starter config has no room. It now checks the session and says so plainly.node 20+ on Debian/Ubuntu
Measured, not assumed: on Ubuntu 24.04
apt-cache policy nodejsgives candidate 18.19.1, below our floor of 20. Soapt-getsystems default to NodeSource's official apt repo. That means piping a third-party script into a root shell, so the installer prints the line and a "Heads up" namingdeb.nodesource.combefore running it, and points at nvm andIAK_NODE_SOURCE=distroas alternatives. WithIAK_NODE_SOURCE=distrothe post-install re-verify still refuses to continue on node 18 rather than proceeding into a broken install.Consent and honesty rules
curl | bashleaves no TTY, so the installer refuses to run an unattended sudo install: it prints the commands and exits 1.IAK_ASSUME_YES=1is the documented opt-in, after downloading and reading the script.uname -s: exit non-zero naming the platform.IAK_DRY_RUN=1prints the platform and prereq plan and changes nothing.Testing
Real
ubuntu:24.04containers, nothing preinstalled (no node, git, tmux, python3, curl or sudo), script piped intobashso stdin is the script exactly as undercurl | bash.172.17.0.2correctly (not loopback). Exit 0.Prereqs OK: node v22.23.2, npm 10.9.8, git, tmux/Already up to date./Hooks already present.No package work, config preserved, exit 0.Package manager: none detected, lists node 20+/git/tmux and the manual step, exit 1.IAK_DRY_RUN=1and a PATH sandbox containing a fakebrewthat exits 99 loudly if invoked. It was never invoked: the no-TTY refusal fires first. Plan on Darwin isbrew install git tmux+brew install node, same as before. The installer was never run for real on this MacBook — its prereqs,~/.claude/settings.json, config and daemon were not touched.Hook-wiring rewrite verified by differential test: old python and new node run against the same
settings.json(unrelatedmodel/permissionskeys, an unrelatedUserPromptSubmithook, an unrelatedPreToolUsehook, and one of our three hooks already present) produce byte-identical output apart from a trailing newline, and both are idempotent on a second run.Negative controls
unamereportingPlan9→Unsupported platform: 'uname -s' reported 'Plan9', exit 1. (First attempt did not fire — SIP blocked the fake binary and it still reported Darwin — so it was rebuilt and re-run.)IAK_NODE_SOURCE=distroon Ubuntu 24.04 installs 18.19.1 →node is still missing or below 20 after the install step. found: v18.19.1, exit 1.Daemon: NOT runningon a fresh config andDaemon: running in tmux session 'iak-mcp'once a channel was configured.No credential was read, printed or committed; the starter config ships
api_key: ""and the test containers never held a real key.🤖 Generated with Claude Code