On an Omarchy laptop bootstrap left two of almost everything: VS Code from both pacman and the tarball (two menu entries, one shared ~/.config/Code profile), and kubectl/helm/k9s/kubectx/yq/jq/node/ansible in ~/.local/bin behind mise and pacman copies that always won on PATH. The zsh helpers (op*, grmb, secrets.env) also never reached the bash login shell. - lib/fetch.sh: add `shadowed`, true when another copy of a tool comes earlier on PATH than ~/.local/bin. Always false where zshrc puts ~/.local/bin first, so VM installs are unchanged. - bin/, node/, ansible/: skip tools that are shadowed. - vscode/: defer to any system `code`, and remove a tarball copy this module installed earlier. - bash/: new module; bash port of the zshrc helpers, sourced from ~/.bashrc via a marked block. No-ops without ~/.bashrc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.8 KiB
Bash
Executable File
70 lines
2.8 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
# node/install.sh — install Node.js (and with it npx) into userland.
|
|
#
|
|
# WHY: the MCP servers in mcp-config/opencode.gateway.json are npm packages and
|
|
# opencode spawns them as `npx -y <pkg>@<version>`. Without npx on PATH every
|
|
# MCP server fails to start with "command not found" — and opencode reports that
|
|
# only as a dead server, so it is easy to misread as a broken config. The MCP
|
|
# ecosystem is overwhelmingly npx-based, so this also unblocks future servers.
|
|
#
|
|
# Node is a tree (bin/node + lib/node_modules/npm), not a single binary, so it
|
|
# cannot use bin/install.sh's install_tarball_bin. It follows the ansible module
|
|
# instead: unpack under ~/.local/opt and symlink the CLIs into ~/.local/bin.
|
|
# Everything is under $HOME — no sudo, and it survives a Kasm redeploy.
|
|
set -eu
|
|
|
|
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
REPO_ROOT="$(dirname -- "$SCRIPT_DIR")"
|
|
|
|
# shellcheck source=../lib/fetch.sh
|
|
. "$REPO_ROOT/lib/fetch.sh"
|
|
|
|
say() { printf '%s\n' "$*" >&2; }
|
|
|
|
# Pinned LTS. Bump deliberately: npx executes whatever the registry serves, so
|
|
# the runtime is part of the supply chain that sees your Gitea/Proxmox tokens.
|
|
NODE_VERSION="v24.18.0"
|
|
|
|
PREFIX="$HOME/.local/opt/node"
|
|
BIN_DIR="$HOME/.local/bin"
|
|
NODE_CLIS="node npm npx"
|
|
|
|
# Another node (mise, nvm, a distro package) already wins on PATH, so ours
|
|
# would never run -- see `shadowed` in lib/fetch.sh. npx comes with it.
|
|
if _winner="$(shadowed node)"; then
|
|
say "node: $_winner comes first on PATH; skipping (a copy in $BIN_DIR would never run)."
|
|
exit 0
|
|
fi
|
|
|
|
case "$(fetch_arch)" in
|
|
x86_64) NODE_ARCH=x64 ;;
|
|
aarch64) NODE_ARCH=arm64 ;;
|
|
*) say "node: unsupported arch $(fetch_arch)"; exit 1 ;;
|
|
esac
|
|
|
|
# Verify it RUNS, not just that the file exists: a Kasm redeploy can leave a
|
|
# present-but-broken tree behind (same failure the ansible venv guards against).
|
|
if [ "$("$PREFIX/bin/node" --version 2>/dev/null || true)" = "$NODE_VERSION" ]; then
|
|
say "node $NODE_VERSION already present at $PREFIX."
|
|
else
|
|
TARBALL="node-$NODE_VERSION-linux-$NODE_ARCH.tar.gz"
|
|
URL="https://nodejs.org/dist/$NODE_VERSION/$TARBALL"
|
|
TMP="$(mktemp -d)"
|
|
say "Installing node $NODE_VERSION ($NODE_ARCH) into $PREFIX..."
|
|
_fetch_dl "$URL" "$TMP/node.tar.gz"
|
|
rm -rf "$PREFIX"
|
|
mkdir -p "$PREFIX"
|
|
# The archive has a single top-level dir; strip it so bin/ lands at $PREFIX/bin.
|
|
tar -xzf "$TMP/node.tar.gz" -C "$PREFIX" --strip-components=1
|
|
rm -rf "$TMP"
|
|
say "Installed node $NODE_VERSION."
|
|
fi
|
|
|
|
# Symlink the CLIs into ~/.local/bin (on PATH via zsh/zshrc). npx must be here:
|
|
# opencode spawns MCP servers with a bare `npx`, resolved from PATH.
|
|
mkdir -p "$BIN_DIR"
|
|
for cli in $NODE_CLIS; do
|
|
[ -x "$PREFIX/bin/$cli" ] && ln -sf "$PREFIX/bin/$cli" "$BIN_DIR/$cli"
|
|
done
|
|
say "Linked node CLIs ($NODE_CLIS) into $BIN_DIR."
|