diff --git a/scripts/setup/machine-setup/lib/dev.sh b/scripts/setup/machine-setup/lib/dev.sh index bb375f2d..23ba640b 100644 --- a/scripts/setup/machine-setup/lib/dev.sh +++ b/scripts/setup/machine-setup/lib/dev.sh @@ -170,3 +170,73 @@ nvim_clone_config() { [[ "$repo" == *LazyVim/starter* ]] && sudo -u "$USERNAME" rm -rf "${dest}/.git" return 0 } + +# ----------------------------------------------------------------------------- +# JavaScript runtimes +# ----------------------------------------------------------------------------- +# +# Three of these are not optional, and it is worth being precise about why, +# because "we run on Bun" suggests Node could go and it cannot: +# +# node pm2 is a Node application (#!/usr/bin/env node), and pm2 supervises +# every process here. officer-pty imports node-pty, a native addon with +# no Linux prebuild — it compiles against the installed Node on every +# machine. Either one alone makes Node load-bearing. +# bun the platform itself and nineteen of the twenty pm2 apps. +# pm2 the process manager the ecosystem files are written for. +# +# Deno is not. Nothing in the platform imports it — checked across the whole +# tree — and it is offered only because it was in the original script and +# somebody may still want it. + +# The current LTS major, asked of nodejs.org rather than hardcoded. The original +# pinned setup_22.x, which ages into "the version we happened to pick" the moment +# a new LTS lands. +node_lts_major() { + curl -fsSL https://nodejs.org/dist/index.json 2>/dev/null | + jq -r '[.[] | select(.lts != false)][0].version // empty' | sed 's/^v//; s/\..*//' +} + +node_lts_label() { + curl -fsSL https://nodejs.org/dist/index.json 2>/dev/null | + jq -r '[.[] | select(.lts != false)][0] | "\(.version) (\(.lts))" // empty' +} + +node_installed_major() { node -v 2>/dev/null | sed 's/^v//; s/\..*//'; } + +install_node() { + local major="$1" + # NodeSource publishes one setup script per major. Checked before it is piped + # into a shell, because a 404 page piped to bash is a confusing way to fail. + curl -fsS -o /dev/null "https://deb.nodesource.com/setup_${major}.x" || { + warn "NodeSource has no setup script for Node ${major}" + return 1 + } + curl -fsSL "https://deb.nodesource.com/setup_${major}.x" | bash - >/dev/null 2>&1 + pkg_install_now nodejs + # Global installs land in /usr/local rather than in a path only root can write, + # so `npm i -g` works the same for the owner and for root. + npm config set prefix /usr/local >/dev/null 2>&1 || true +} + +bun_installed() { command -v bun &>/dev/null; } + +# 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 +# symlink every bun-based sidecar fails to start on reboot and works fine when +# started by hand, which is a miserable thing to debug. +install_bun() { + (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 "$bin" ]] || return 1 + ln -sf "$bin" /usr/local/bin/bun +} + +pm2_installed() { command -v pm2 &>/dev/null; } +install_pm2() { npm install -g pm2 >/dev/null 2>&1; } + +deno_installed() { command -v deno &>/dev/null || [[ -x "${USER_HOME}/.deno/bin/deno" ]]; } +install_deno() { + (cd / && sudo -H -u "$USERNAME" bash -c 'curl -fsSL https://deno.land/install.sh | sh') >/dev/null 2>&1 + [[ -x "${USER_HOME}/.deno/bin/deno" ]] +} diff --git a/scripts/setup/machine-setup/machine-setup.sh b/scripts/setup/machine-setup/machine-setup.sh index 673ebd23..d9a0708d 100755 --- a/scripts/setup/machine-setup/machine-setup.sh +++ b/scripts/setup/machine-setup/machine-setup.sh @@ -1949,13 +1949,112 @@ if ! skip; then step_ok fi +# ============================================================================= +# 25. JavaScript runtimes +# ============================================================================= +# +# Not offered as a choice. Officer does not run without these, so asking would be +# asking whether to install Officer — which was settled by running this script. +# Deno is the exception and is asked, because nothing uses it. + +step "JavaScript runtimes" +if ! skip; then + NODE_LTS="$(node_lts_major)" + NODE_NOW="$(node_installed_major)" + + echo "" + info "JavaScript runtimes — required, so these are installed rather than offered" + 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 " against whatever Node is installed — there is no Linux prebuild." + echo " bun $(bun_installed && bun --version 2>/dev/null || echo 'not installed')" + echo " the platform itself, and nineteen of the twenty pm2 apps." + echo " pm2 $(pm2_installed && echo installed || echo 'not installed')" + echo " supervises all of them, and the ecosystem files are written for it." + echo "" + + # ── node ── + if [[ -z "$NODE_LTS" ]]; then + warn "could not reach nodejs.org to find the current LTS — leaving Node alone" + ERRORS+=("Node: could not determine the current LTS") + elif [[ "$NODE_NOW" == "$NODE_LTS" ]]; then + ok "node v${NODE_NOW} is the current LTS" + else + info " installing Node ${NODE_LTS}${NODE_NOW:+, replacing v$NODE_NOW}..." + # Checked afterwards rather than trusting the installer's exit status: a + # NodeSource run can succeed while apt keeps an older nodejs held back, and + # reporting the version we asked for instead of the one that is there is how + # a machine ends up disagreeing with its own setup log. + if install_node "$NODE_LTS" && [[ "$(node_installed_major)" == "$NODE_LTS" ]]; then + ok "node $(node -v)" + SUMMARY+=("Node: $(node -v) (current LTS)") + else + warn "the Node install did not complete" + ERRORS+=("Node: install failed") + fi + fi + + # ── bun ── + if bun_installed; then + ok "bun $(bun --version 2>/dev/null) already installed" + else + info " installing bun..." + if install_bun && bun_installed; then + ok "bun $(bun --version), symlinked to /usr/local/bin/bun" + SUMMARY+=("Bun: $(bun --version)") + else + warn "the bun install did not complete" + ERRORS+=("Bun: install failed") + fi + fi + + # ── pm2 ── + if pm2_installed; then + ok "pm2 $(pm2 -v 2>/dev/null | tail -1) already installed" + elif command -v npm &>/dev/null; then + info " installing pm2..." + if install_pm2 && pm2_installed; then + ok "pm2 $(pm2 -v | tail -1)" + SUMMARY+=("pm2: $(pm2 -v | tail -1)") + else + warn "the pm2 install did not complete" + ERRORS+=("pm2: install failed") + fi + else + warn "no npm, so pm2 cannot be installed — Node did not install correctly" + ERRORS+=("pm2: not installed, npm missing") + fi + + # ── deno, which is the one genuine choice here ── + echo "" + if deno_installed; then + echo " deno is already installed — left alone" + else + info "Deno?" + echo " Nothing in Officer uses Deno. It was in the original setup script" + echo " for one thing that no longer exists, and there is no reference to" + echo " it anywhere in the platform today." + echo " Offered because you may want it for your own work." + if confirm "Install Deno?" n; then + if install_deno && deno_installed; then + ok "deno installed to ${USER_HOME}/.deno" + SUMMARY+=("Deno: installed (not used by Officer)") + else + warn "the Deno install did not complete" + fi + else + echo " skipped" + fi + fi + step_ok +fi + # ============================================================================= # NOT PORTED YET # ============================================================================= # # Sections still to move across from scripts/setup-old/setup-ubuntu.sh, in order: # -# js runtimes · # dev tools · ufw · zshrc # # And one that is new rather than ported, to come last of all: