d6f01862fe67afdb93d97031a1a78232349637d2
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e120dfa36e |
full read: fix the set -e footguns a full run would have hit
Read the whole thing — 2392 lines of entry point and 2700 of libraries — looking for what shellcheck cannot see. shellcheck itself is clean at error level; its warnings are cross-file false positives and one deliberate tilde in a display string. Everything below is a real defect. ── The Git section aborted on any machine where git was not already configured ── `git config --global --get <key>` exits NON-ZERO when the key is simply unset, and `VAR="$(git_get …)"` propagates that under `set -e`. So on a fresh machine — the case this script exists for — the section died at its first assignment, before printing anything, and took the remaining nine sections with it. It passed every earlier test because those harnesses sourced the section under a `bash -c` with no `set -e`. Verified now against a genuinely fresh account with the real script: the section completes and writes a correct .gitconfig. ── An optional step failing aborted the whole run ── Twelve functions ended on a command that can fail — `systemctl enable --now earlyoom`, `systemctl restart systemd-logind`, `chsh`, `sysctl -w`, `chown -R`, the oh-my-zsh installer, and others. Called as plain commands under `set -e`, any one of them failing ends the script, so a masked unit or a container without systemd would abort a 28-section run over an optional improvement. They now return 0 explicitly and the callers verify the outcome instead — which also fixed a lie: the sleep section printed "sleep disabled, logind reloaded" whether or not the restart had worked. It now checks the targets and the logind values and reports honestly. ── chown user:user assumed the primary group is named after the user ── True on Debian and Ubuntu, which create a group per user. Not true for an account from LDAP, or made with `useradd -g users`, or on an image with a shared group — there `install -g <user>` fails with "invalid group" and the step aborts. Proved it against an account whose primary group is `oddgroup`: the old form fails, the new one gets ownership right. Eight call sites now ask `id -gn`. ── Also hardened ── agent_path and current_editor gained `|| true` for the same reason git_get needed it: "nothing is set" is an answer, not a failure. Verified afterwards: shellcheck clean at error level, every section runs standalone without aborting, and the two apparent failures in that sweep are correct behaviour — Timezone and Git refusing an empty answer from /dev/null. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d6a78d7b5a |
put the shell configuration in one place, after everything it configures
The Shell section moves to 26, after Neovim, the runtimes and the agent CLIs. Everything that writes to .zshrc now happens there and only there: the starship init, the PATH for the agent CLIs (moved out of that section), the aliases, and the editor. That ordering is what the editor choice needs — it offers whichever of nvim, vim and nano are actually present, so it has to run after Neovim is installed rather than naming an editor that is not there. Which was the original's mistake in the other direction: it set core.editor to nvim four sections before installing it. The default editor is the setting git's core.editor was deliberately left out in favour of. EDITOR, VISUAL and SUDO_EDITOR go in the account's shell, and the Debian `editor` alternative is set too — an account's shell config cannot reach root or sudoedit, and those are exactly the cases where the wrong editor is most annoying. Recorded a limitation of append_once while cleaning up after it: renaming a marker orphans the block that used the old name, and changing a block's content does nothing because the marker is still found. Both need the old block removed by hand. This run left exactly that — a `local-bin` block superseded by `agent-clis` — in the dev box's .zshrc, now removed. UFW is deliberately still unported and will be last, for the reason the original gave: it is the one step that can cut the connection the run is happening over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
607a962115 |
port the agent CLIs, using Anthropic's installer rather than npm
Claude Code goes in through https://claude.ai/install.sh, matching what the platform already does for members in os-user-claude.ts and chosen there for the auto-update npm does not give. The comment at os-user-claude.ts:28 claiming setup.sh already did this was simply wrong — setup-ubuntu.sh used `npm install -g @anthropic-ai/claude-code`. Two things that installer insists on, both of which a naive port gets wrong and both of which os-user-claude.ts had already found: It REFUSES to run under sudo from a regular user's shell — it checks for uid 0 with SUDO_USER set, because everything it writes goes under $HOME and under sudo that is root's. This script runs as root, so the install has to be done AS the account. It declares #!/bin/bash and uses [[ … =~ … ]], so it must be piped to bash. On Ubuntu /bin/sh is dash and `| sh` fails. Two bugs found by running it rather than reading it: opencode does not install to ~/.local/bin. It goes to ~/.opencode/bin, which is what sidecar/opencode/index.ts:22 hardcodes. The first version looked in the wrong place, reported a working install as missing, and installed it again — the run said "did not complete" while the installer had plainly succeeded. claude on this machine came from npm, so `command -v claude` found it and the section would have left a copy that never updates. It now detects an npm install by resolving the binary into node_modules, says so, and offers to reinstall through the official installer — naming the npm copy and how to remove it rather than deleting something it did not put there. ~/.local/bin and ~/.opencode/bin are both added to the account's PATH. The sidecars do not need it — they check the exact paths — but a user who cannot run `claude` in their own terminal reasonably concludes it was never installed. PI stays optional and says outright that nothing in the platform spawns it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
cb3e7062b9 |
ensure the bun symlink on every run, not only after installing it
The link was made as part of install_bun, so a machine that already had bun never got one. That is this machine: bun 1.3.14 in ~/.bun/bin, no /usr/local/bin/bun, and `bun` resolving to nothing at all for root. Nothing has broken yet only because `pm2 startup` has never been run here — the moment boot persistence is enabled, all twenty ecosystem apps that say `script: 'bun'` fail at boot and work perfectly when started by hand. ensure_bun_symlink now runs whether or not this script did the install, and says which of the three things happened: made it, found it already correct, or could not find bun to link. The last records an error, since a missing link is a reboot-shaped failure rather than a cosmetic one. Safe across upgrades, which was the question: a symlink resolves by path, not by inode, and `bun upgrade` replaces the file at $BUN_INSTALL/bin/bun rather than moving it. Demonstrated by replacing a target with a new file — new inode, link still resolves. It breaks only if the home directory goes, which breaks bun anyway. Also fixed the status line, which reported "not installed" on a machine with bun in the user's home: it asked root's PATH, which is exactly what has no bun before the link exists. bun_version now asks whichever copy is there. This run created the link on this machine — /usr/local/bin/bun -> the account's copy, and root can now run bun. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
00e58931ff |
port the JS runtimes: node, bun and pm2 installed rather than offered
Not a choice. Officer does not run without them, so asking would be asking
whether to install Officer — which was settled by running the script. They are
installed and reported, with the reason each is load-bearing stated once:
node pm2 is a Node application, and officer-pty compiles node-pty against
whatever Node is installed. There is no Linux prebuild, so this is not
an ABI question — it is a build dependency on every machine.
bun the platform itself and nineteen of the twenty pm2 apps.
pm2 supervises all of them, and the ecosystem files are written for it.
Node now tracks the current LTS, asked of nodejs.org, rather than the pinned
setup_22.x the original used — which ages into "the version we happened to pick"
the moment a new LTS lands. Resolves to v24.19.0 (Krypton) today, and NodeSource
publishes setup_24.x, checked with a HEAD request before anything is piped into a
shell.
Deno is the one genuine choice and stays optional, defaulting to no. Nothing in
Officer imports it — verified across the whole tree, the only references left are
in the old setup script — so the prompt says that outright and offers it for the
user's own work rather than pretending it is part of the platform.
bun is installed as the account and then symlinked into /usr/local/bin. pm2
started at boot by systemd has no login shell and therefore no ~/.bun/bin on
PATH; without the symlink every bun-based sidecar fails on reboot and works when
started by hand, which is a miserable thing to debug.
Every install is verified after it runs rather than trusting an exit status. A
NodeSource run can succeed while apt holds an older nodejs back, and reporting
the version asked for instead of the one present is how a machine ends up
disagreeing with its own setup log. Tested with an installer stubbed to succeed
and change nothing: both report failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
0286cc6db6 |
port the Neovim section
Kept as it worked — upstream tarball, symlink, a config repo cloned into the account's ~/.config/nvim — with the defects fixed rather than the design changed. The one that mattered: the asset name. Neovim publishes nvim-linux-x86_64.tar.gz and nvim-linux-arm64.tar.gz. The original mapped aarch64 to "aarch64", which is not a name Neovim has ever published, so on an arm machine it downloaded a 404 and handed the HTML error page to tar. Verified against the release API — same class of bug as lazygit's hardcoded x86_64, and the second one this port has found in an arch mapping. The tarball is now checked with `tar -tzf` before anything is removed, so a bad download says what is wrong instead of failing inside tar. The rest: The tarball went to the working directory, via `curl -LO`, and stayed there if tar failed. It goes to /tmp and is cleaned up. The old /opt install was removed before the new one was known to be good. The download and its sanity check now come first, so a failed fetch leaves the working copy alone. The custom-repo option defaulted to git@gogs:andrepadez/nvim-config.git — a private repository nobody else can clone, and the same mistake as defaulting the login server to a personal headscale. No default now. git clone runs from /, for the reason git config does: the script's working directory is usually under the invoking user's home at 0750, which the target account cannot stat. The ~/.config/nvim/.git removal is now conditional on it being the starter. That is a template and dropping its history is right; a config of the user's own is something they will want to keep pulling. An existing config is left alone and said so, rather than moved to a .bak that silently overwrote the previous .bak. The PATH line the original appended to .zshrc is gone. /usr/local/bin/nvim is symlinked and already on PATH, so it was doing nothing except growing the file on every run. Verified on this host (already current, existing config left alone) and against a fresh account (LazyVim starter cloned, owned correctly, .git dropped). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2b9e16c11e |
port the shell section, and make one starship config serve both audiences
os-user-shell.ts:33 calls scripts/setup/starship.toml "the prompt config the owner's own install uses — one file, both audiences". It was not: the original machine script wrote a DIFFERENT config inline, so the owner got a prompt that only disabled language modules while every member got the repo file with its custom format. Two prompts, one comment claiming otherwise. This deploys the same file the platform does, which makes the comment true. Verified with cmp against a fresh account: byte-identical to what a member gets. Nothing overwrites any more: .config/starship.toml and .tmux.conf go through install_config, so they are written when absent, skipped when identical, and KEPT when they differ — with the cp printed, so taking ours stays the reader's decision. On this host that is what happens: the existing config differs and is left alone. The starship line in .zshrc is marker-wrapped by append_once. Verified over three consecutive runs: one block, not three. The original appended it unguarded every time. The login shell is now its own question. Having zsh on the machine and being handed it at every login are different decisions, and `chsh` made the second one silently. It also adds the shell to /etc/shells first, which chsh requires. .tmux.conf lives here now, with the rest of the dotfiles, rather than in user creation where the original put it only because that is where $USER_HOME first exists. One bug found by running it: install_config returns 2 for "kept yours", which is an outcome rather than a failure — but still non-zero, so calling it as a plain command under `set -e` ended the run before `case $?` could read it. Captured with && / || at both call sites, and the contract is documented where the function is defined so the next caller does not repeat it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
28673869c3 |
port the git section, and stop it overwriting an identity that already exists
Detects first, then asks. If there is a name and email configured it shows them and offers to change them, defaulting to no — the common reason to re-run this script is everything except this. If there is nothing configured it just asks. Default branch is master rather than main, unless the answer says otherwise. Three problems in the original, beyond running unconditionally: prompt_value accepts an empty answer, so pressing Enter wrote `user.name = ""`. An empty name is worse than none: unset makes git refuse to commit and say why, empty makes it commit with a blank author and never mention it. ask_required re-asks instead. core.editor was set to nvim four sections before Neovim is installed, so anything invoking the editor in between failed. Dropped for now rather than moved — it is a preference, and worth deciding separately. Nothing checked whether the writes worked. That last one was not theoretical. Testing against a throwaway account, all three writes failed and the section still printed "OK: written". `git config --global` needs no repository, but git stats the working directory on the way, looking for one — and the script runs from under the invoking user's home, which is 0750, so the target account cannot stat it: fatal: failed to stat '<cwd>': Permission denied The wrappers now run in a subshell from /, which every account can stat, and the caller checks the exit status and reads the value back before claiming success. Also recorded where it is written: docs/agent-git-identity.md says every agent Officer runs commits as the owner, because it runs as the owner. This is not only the human's identity, it is what git log attributes agent commits to — which is worth knowing while choosing it. Verified both paths: this host's existing identity is shown and left alone by default, and a fresh account gets a correct .gitconfig owned by that account with defaultBranch master. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |