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>
This commit is contained in:
2026-08-12 21:11:38 +00:00
co-authored by Claude Opus 5
parent 00e58931ff
commit cb3e7062b9
2 changed files with 50 additions and 8 deletions
+33 -4
View File
@@ -219,7 +219,38 @@ install_node() {
npm config set prefix /usr/local >/dev/null 2>&1 || true npm config set prefix /usr/local >/dev/null 2>&1 || true
} }
bun_installed() { command -v bun &>/dev/null; } # Present anywhere: on PATH for this root shell, or in the account's own
# ~/.bun/bin, which is where the installer puts it and where root cannot see it.
bun_installed() { command -v bun &>/dev/null || [[ -x "${USER_HOME}/.bun/bin/bun" ]]; }
# Asked of whichever copy exists. Before the symlink is made, root's PATH has no
# bun at all, so `bun --version` reports nothing on a machine that plainly has it.
bun_version() {
if command -v bun &>/dev/null; then
bun --version
elif [[ -x "${USER_HOME}/.bun/bin/bun" ]]; then
"${USER_HOME}/.bun/bin/bun" --version
fi
}
# The system-wide link, ensured on every run rather than only after an install.
#
# pm2 started at boot by systemd has no login shell, so ~/.bun/bin is not on its
# PATH — and every one of the twenty ecosystem apps that says `script: 'bun'`
# then fails to start on reboot while working perfectly when started by hand. A
# machine that already had bun before this script ran would never get the link if
# it were only made as part of installing.
#
# Safe across upgrades: a symlink resolves by path, and `bun upgrade` replaces
# the file at $BUN_INSTALL/bin/bun rather than moving it. The link only breaks if
# the home directory goes, which breaks bun anyway.
ensure_bun_symlink() {
local bin="${USER_HOME}/.bun/bin/bun"
[[ -x "$bin" ]] || return 1
[[ "$(readlink -f /usr/local/bin/bun 2>/dev/null)" == "$(readlink -f "$bin")" ]] && return 1
ln -sf "$bin" /usr/local/bin/bun
return 0
}
# Installed as the account, then symlinked system-wide. pm2 started at boot by # Installed as the account, then symlinked system-wide. pm2 started at boot by
# systemd has no login shell and therefore no ~/.bun/bin on PATH — without the # systemd has no login shell and therefore no ~/.bun/bin on PATH — without the
@@ -227,9 +258,7 @@ bun_installed() { command -v bun &>/dev/null; }
# started by hand, which is a miserable thing to debug. # started by hand, which is a miserable thing to debug.
install_bun() { install_bun() {
(cd / && sudo -H -u "$USERNAME" bash -c 'curl -fsSL https://bun.sh/install | bash') >/dev/null 2>&1 (cd / && sudo -H -u "$USERNAME" bash -c 'curl -fsSL https://bun.sh/install | bash') >/dev/null 2>&1
local bin="${USER_HOME}/.bun/bin/bun" [[ -x "${USER_HOME}/.bun/bin/bun" ]]
[[ -x "$bin" ]] || return 1
ln -sf "$bin" /usr/local/bin/bun
} }
pm2_installed() { command -v pm2 &>/dev/null; } pm2_installed() { command -v pm2 &>/dev/null; }
+17 -4
View File
@@ -1967,7 +1967,7 @@ if ! skip; then
echo " node ${NODE_NOW:+v$NODE_NOW }${NODE_NOW:+-> }LTS $(node_lts_label)" echo " node ${NODE_NOW:+v$NODE_NOW }${NODE_NOW:+-> }LTS $(node_lts_label)"
echo " pm2 is a Node application, and officer-pty compiles node-pty" echo " pm2 is a Node application, and officer-pty compiles node-pty"
echo " against whatever Node is installed — there is no Linux prebuild." echo " against whatever Node is installed — there is no Linux prebuild."
echo " bun $(bun_installed && bun --version 2>/dev/null || echo 'not installed')" echo " bun $(bun_installed && bun_version || echo 'not installed')"
echo " the platform itself, and nineteen of the twenty pm2 apps." echo " the platform itself, and nineteen of the twenty pm2 apps."
echo " pm2 $(pm2_installed && echo installed || echo 'not installed')" echo " pm2 $(pm2_installed && echo installed || echo 'not installed')"
echo " supervises all of them, and the ecosystem files are written for it." echo " supervises all of them, and the ecosystem files are written for it."
@@ -1996,18 +1996,31 @@ if ! skip; then
# ── bun ── # ── bun ──
if bun_installed; then if bun_installed; then
ok "bun $(bun --version 2>/dev/null) already installed" ok "bun $(bun_version) already installed"
else else
info " installing bun..." info " installing bun..."
if install_bun && bun_installed; then if install_bun && bun_installed; then
ok "bun $(bun --version), symlinked to /usr/local/bin/bun" ok "bun $(bun_version)"
SUMMARY+=("Bun: $(bun --version)") SUMMARY+=("Bun: $(bun_version)")
else else
warn "the bun install did not complete" warn "the bun install did not complete"
ERRORS+=("Bun: install failed") ERRORS+=("Bun: install failed")
fi fi
fi fi
# Ensured every run, not only after an install. A machine that already had bun
# would otherwise never get the link, and the failure only shows up at the next
# reboot.
if ensure_bun_symlink; then
ok "bun symlinked to /usr/local/bin/bun — pm2 at boot has no ~/.bun on PATH"
SUMMARY+=("Bun: symlinked system-wide")
elif [[ -x /usr/local/bin/bun ]]; then
echo " /usr/local/bin/bun already points at it"
else
warn "bun is not at ${USER_HOME}/.bun/bin/bun — cannot link it system-wide"
ERRORS+=("Bun: no system-wide symlink; pm2 apps will fail at boot")
fi
# ── pm2 ── # ── pm2 ──
if pm2_installed; then if pm2_installed; then
ok "pm2 $(pm2 -v 2>/dev/null | tail -1) already installed" ok "pm2 $(pm2 -v 2>/dev/null | tail -1) already installed"