Install claude and pi through mise

Provisioning was still installing `claude` and `pi` through a separate
npm-global prefix even after the guest images had switched to `mise` for
Node and opencode. That left two competing install paths and made the
runtime layout harder to reason about.

Switch the Debian and Void image setup flows to install `claude` and `pi`
as `mise` npm tools, assert their shims exist after `mise reshim`, and
symlink `node`, `npm`, `opencode`, `claude`, and `pi` directly from the
mise shim directory into `/usr/local/bin`.

Update the imagebuild test expectations and bump the Void rootfs default
size to 4G so the larger default toolset still fits reliably.
This commit is contained in:
Thales Maciel 2026-04-13 18:29:02 -03:00
parent 5e26fd7544
commit 797a9de1ce
No known key found for this signature in database
GPG key ID: 33112E6833C34679
4 changed files with 60 additions and 55 deletions

View file

@ -23,21 +23,20 @@ import (
)
const (
defaultMiseVersion = "v2025.12.0"
defaultMiseInstallPath = "/usr/local/bin/mise"
defaultMiseActivateLine = `eval "$(/usr/local/bin/mise activate bash)"`
defaultNodeTool = "node@22"
defaultOpenCodeTool = "github:anomalyco/opencode"
defaultClaudeCodePackage = "@anthropic-ai/claude-code"
defaultPiPackage = "@mariozechner/pi-coding-agent"
defaultNPMGlobalPrefix = "/root/.local/share/banger/npm-global"
defaultTPMRepo = "https://github.com/tmux-plugins/tpm"
defaultResurrectRepo = "https://github.com/tmux-plugins/tmux-resurrect"
defaultContinuumRepo = "https://github.com/tmux-plugins/tmux-continuum"
defaultTMUXPluginDir = "/root/.tmux/plugins"
defaultTMUXResurrectDir = "/root/.tmux/resurrect"
tmuxManagedBlockStart = "# >>> banger tmux plugins >>>"
tmuxManagedBlockEnd = "# <<< banger tmux plugins <<<"
defaultMiseVersion = "v2025.12.0"
defaultMiseInstallPath = "/usr/local/bin/mise"
defaultMiseActivateLine = `eval "$(/usr/local/bin/mise activate bash)"`
defaultNodeTool = "node@22"
defaultOpenCodeTool = "github:anomalyco/opencode"
defaultClaudeCodeTool = "npm:@anthropic-ai/claude-code"
defaultPiTool = "npm:@mariozechner/pi-coding-agent"
defaultTPMRepo = "https://github.com/tmux-plugins/tpm"
defaultResurrectRepo = "https://github.com/tmux-plugins/tmux-resurrect"
defaultContinuumRepo = "https://github.com/tmux-plugins/tmux-continuum"
defaultTMUXPluginDir = "/root/.tmux/plugins"
defaultTMUXResurrectDir = "/root/.tmux/resurrect"
tmuxManagedBlockStart = "# >>> banger tmux plugins >>>"
tmuxManagedBlockEnd = "# <<< banger tmux plugins <<<"
)
type imageBuildSpec struct {
@ -307,26 +306,28 @@ func buildModulesCommand(modulesBase string) string {
func appendMiseSetup(script *bytes.Buffer) {
const (
nodeShimPath = "/root/.local/share/mise/shims/node"
npmShimPath = "/root/.local/share/mise/shims/npm"
nodeShimPath = "/root/.local/share/mise/shims/node"
npmShimPath = "/root/.local/share/mise/shims/npm"
claudeShimPath = "/root/.local/share/mise/shims/claude"
piShimPath = "/root/.local/share/mise/shims/pi"
)
claudePath := filepath.ToSlash(filepath.Join(defaultNPMGlobalPrefix, "bin", "claude"))
piPath := filepath.ToSlash(filepath.Join(defaultNPMGlobalPrefix, "bin", "pi"))
fmt.Fprintf(script, "curl -fsSL https://mise.run | MISE_INSTALL_PATH=%s MISE_VERSION=%s sh\n", shellQuote(defaultMiseInstallPath), shellQuote(defaultMiseVersion))
fmt.Fprintf(script, "%s use -g %s\n", shellQuote(defaultMiseInstallPath), shellQuote(defaultNodeTool))
fmt.Fprintf(script, "%s use -g %s\n", shellQuote(defaultMiseInstallPath), shellQuote(defaultOpenCodeTool))
fmt.Fprintf(script, "%s use -g %s\n", shellQuote(defaultMiseInstallPath), shellQuote(defaultClaudeCodeTool))
fmt.Fprintf(script, "%s use -g %s\n", shellQuote(defaultMiseInstallPath), shellQuote(defaultPiTool))
fmt.Fprintf(script, "%s reshim\n", shellQuote(defaultMiseInstallPath))
fmt.Fprintf(script, "if [[ ! -e %s ]]; then echo 'node shim not found after mise install' >&2; exit 1; fi\n", shellQuote(nodeShimPath))
fmt.Fprintf(script, "if [[ ! -e %s ]]; then echo 'npm shim not found after mise install' >&2; exit 1; fi\n", shellQuote(npmShimPath))
fmt.Fprintf(script, "if [[ ! -e %s ]]; then echo 'opencode shim not found after mise install' >&2; exit 1; fi\n", shellQuote(opencode.ShimPath))
fmt.Fprintf(script, "mkdir -p %s\n", shellQuote(defaultNPMGlobalPrefix))
fmt.Fprintf(script, "NPM_CONFIG_PREFIX=%s %s install -g %s %s\n", shellQuote(defaultNPMGlobalPrefix), shellQuote(npmShimPath), shellQuote(defaultClaudeCodePackage), shellQuote(defaultPiPackage))
fmt.Fprintf(script, "if [[ ! -e %s ]]; then echo 'claude binary not found after npm install' >&2; exit 1; fi\n", shellQuote(claudePath))
fmt.Fprintf(script, "if [[ ! -e %s ]]; then echo 'pi binary not found after npm install' >&2; exit 1; fi\n", shellQuote(piPath))
fmt.Fprintf(script, "if [[ ! -e %s ]]; then echo 'claude shim not found after mise install' >&2; exit 1; fi\n", shellQuote(claudeShimPath))
fmt.Fprintf(script, "if [[ ! -e %s ]]; then echo 'pi shim not found after mise install' >&2; exit 1; fi\n", shellQuote(piShimPath))
fmt.Fprintf(script, "ln -snf %s %s\n", shellQuote(nodeShimPath), shellQuote("/usr/local/bin/node"))
fmt.Fprintf(script, "ln -snf %s %s\n", shellQuote(npmShimPath), shellQuote("/usr/local/bin/npm"))
fmt.Fprintf(script, "ln -snf %s %s\n", shellQuote(opencode.ShimPath), shellQuote(opencode.GuestBinaryPath))
fmt.Fprintf(script, "ln -snf %s %s\n", shellQuote(claudePath), shellQuote("/usr/local/bin/claude"))
fmt.Fprintf(script, "ln -snf %s %s\n", shellQuote(piPath), shellQuote("/usr/local/bin/pi"))
fmt.Fprintf(script, "ln -snf %s %s\n", shellQuote(claudeShimPath), shellQuote("/usr/local/bin/claude"))
fmt.Fprintf(script, "ln -snf %s %s\n", shellQuote(piShimPath), shellQuote("/usr/local/bin/pi"))
script.WriteString("mkdir -p /etc/profile.d\n")
script.WriteString("cat > /etc/profile.d/mise.sh <<'EOF'\n")
fmt.Fprintf(script, "if [ -n \"${BASH_VERSION:-}\" ] && [ -x %s ]; then\n", shellQuote(defaultMiseInstallPath))

View file

@ -20,17 +20,19 @@ func TestBuildProvisionScriptInstallsDefaultTools(t *testing.T) {
"curl -fsSL https://mise.run | MISE_INSTALL_PATH='/usr/local/bin/mise' MISE_VERSION='v2025.12.0' sh",
"'/usr/local/bin/mise' use -g 'node@22'",
"'/usr/local/bin/mise' use -g 'github:anomalyco/opencode'",
"'/usr/local/bin/mise' use -g 'npm:@anthropic-ai/claude-code'",
"'/usr/local/bin/mise' use -g 'npm:@mariozechner/pi-coding-agent'",
"'/usr/local/bin/mise' reshim",
"if [[ ! -e '/root/.local/share/mise/shims/node' ]]; then echo 'node shim not found after mise install' >&2; exit 1; fi",
"if [[ ! -e '/root/.local/share/mise/shims/npm' ]]; then echo 'npm shim not found after mise install' >&2; exit 1; fi",
"if [[ ! -e '/root/.local/share/mise/shims/opencode' ]]; then echo 'opencode shim not found after mise install' >&2; exit 1; fi",
"mkdir -p '/root/.local/share/banger/npm-global'",
"NPM_CONFIG_PREFIX='/root/.local/share/banger/npm-global' '/root/.local/share/mise/shims/npm' install -g '@anthropic-ai/claude-code' '@mariozechner/pi-coding-agent'",
"if [[ ! -e '/root/.local/share/banger/npm-global/bin/claude' ]]; then echo 'claude binary not found after npm install' >&2; exit 1; fi",
"if [[ ! -e '/root/.local/share/banger/npm-global/bin/pi' ]]; then echo 'pi binary not found after npm install' >&2; exit 1; fi",
"if [[ ! -e '/root/.local/share/mise/shims/claude' ]]; then echo 'claude shim not found after mise install' >&2; exit 1; fi",
"if [[ ! -e '/root/.local/share/mise/shims/pi' ]]; then echo 'pi shim not found after mise install' >&2; exit 1; fi",
"ln -snf '/root/.local/share/mise/shims/node' '/usr/local/bin/node'",
"ln -snf '/root/.local/share/mise/shims/npm' '/usr/local/bin/npm'",
"ln -snf '/root/.local/share/mise/shims/opencode' '/usr/local/bin/opencode'",
"ln -snf '/root/.local/share/banger/npm-global/bin/claude' '/usr/local/bin/claude'",
"ln -snf '/root/.local/share/banger/npm-global/bin/pi' '/usr/local/bin/pi'",
"ln -snf '/root/.local/share/mise/shims/claude' '/usr/local/bin/claude'",
"ln -snf '/root/.local/share/mise/shims/pi' '/usr/local/bin/pi'",
"cat > /etc/profile.d/mise.sh <<'EOF'",
"if [ -n \"${BASH_VERSION:-}\" ] && [ -x '/usr/local/bin/mise' ]; then",
`eval "$(/usr/local/bin/mise activate bash)"`,

View file

@ -95,9 +95,8 @@ MISE_VERSION="v2025.12.0"
MISE_INSTALL_PATH="/usr/local/bin/mise"
MISE_ACTIVATE_LINE='eval "$(/usr/local/bin/mise activate bash)"'
NODE_TOOL="node@22"
CLAUDE_CODE_PACKAGE="@anthropic-ai/claude-code"
PI_PACKAGE="@mariozechner/pi-coding-agent"
NPM_GLOBAL_PREFIX="/root/.local/share/banger/npm-global"
CLAUDE_CODE_TOOL="npm:@anthropic-ai/claude-code"
PI_TOOL="npm:@mariozechner/pi-coding-agent"
TMUX_PLUGIN_DIR="/root/.tmux/plugins"
TMUX_RESURRECT_DIR="/root/.tmux/resurrect"
TMUX_TPM_REPO="https://github.com/tmux-plugins/tpm"
@ -406,6 +405,8 @@ DEBIAN_FRONTEND=noninteractive apt-get -y install ${APT_PACKAGES_ESCAPED}
curl -fsSL https://mise.run | MISE_INSTALL_PATH="$MISE_INSTALL_PATH" MISE_VERSION="$MISE_VERSION" sh
"$MISE_INSTALL_PATH" use -g "$NODE_TOOL"
"$MISE_INSTALL_PATH" use -g github:anomalyco/opencode
"$MISE_INSTALL_PATH" use -g "$CLAUDE_CODE_TOOL"
"$MISE_INSTALL_PATH" use -g "$PI_TOOL"
"$MISE_INSTALL_PATH" reshim
if [[ ! -e /root/.local/share/mise/shims/node ]]; then
echo 'node shim not found after mise install' >&2
@ -419,19 +420,19 @@ if [[ ! -e /root/.local/share/mise/shims/opencode ]]; then
echo 'opencode shim not found after mise install' >&2
exit 1
fi
mkdir -p "$NPM_GLOBAL_PREFIX"
NPM_CONFIG_PREFIX="$NPM_GLOBAL_PREFIX" /root/.local/share/mise/shims/npm install -g "$CLAUDE_CODE_PACKAGE" "$PI_PACKAGE"
if [[ ! -e "$NPM_GLOBAL_PREFIX/bin/claude" ]]; then
echo 'claude binary not found after npm install' >&2
if [[ ! -e /root/.local/share/mise/shims/claude ]]; then
echo 'claude shim not found after mise install' >&2
exit 1
fi
if [[ ! -e "$NPM_GLOBAL_PREFIX/bin/pi" ]]; then
echo 'pi binary not found after npm install' >&2
if [[ ! -e /root/.local/share/mise/shims/pi ]]; then
echo 'pi shim not found after mise install' >&2
exit 1
fi
ln -snf /root/.local/share/mise/shims/node /usr/local/bin/node
ln -snf /root/.local/share/mise/shims/npm /usr/local/bin/npm
ln -snf /root/.local/share/mise/shims/opencode /usr/local/bin/opencode
ln -snf "$NPM_GLOBAL_PREFIX/bin/claude" /usr/local/bin/claude
ln -snf "$NPM_GLOBAL_PREFIX/bin/pi" /usr/local/bin/pi
ln -snf /root/.local/share/mise/shims/claude /usr/local/bin/claude
ln -snf /root/.local/share/mise/shims/pi /usr/local/bin/pi
mkdir -p /etc/profile.d
cat > /etc/profile.d/mise.sh <<'MISEPROFILE'
if [ -n \"\${BASH_VERSION:-}\" ] && [ -x \"$MISE_INSTALL_PATH\" ]; then

View file

@ -13,7 +13,7 @@ Build an experimental Void Linux rootfs image plus a matching /root work-seed.
Defaults:
--out ./build/manual/rootfs-void.ext4
--size 2G
--size 4G
--mirror https://repo-default.voidlinux.org
--arch x86_64
@ -345,6 +345,8 @@ set -euo pipefail
curl -fsSL https://mise.run | MISE_INSTALL_PATH="$MISE_INSTALL_PATH" MISE_VERSION="$MISE_VERSION" sh
"$MISE_INSTALL_PATH" use -g "$NODE_TOOL"
"$MISE_INSTALL_PATH" use -g "$OPENCODE_TOOL"
"$MISE_INSTALL_PATH" use -g "$CLAUDE_CODE_TOOL"
"$MISE_INSTALL_PATH" use -g "$PI_TOOL"
"$MISE_INSTALL_PATH" reshim
if [[ ! -e /root/.local/share/mise/shims/node ]]; then
echo "node shim not found after mise install" >&2
@ -358,19 +360,19 @@ if [[ ! -e /root/.local/share/mise/shims/opencode ]]; then
echo "opencode shim not found after mise install" >&2
exit 1
fi
mkdir -p "$NPM_GLOBAL_PREFIX"
NPM_CONFIG_PREFIX="$NPM_GLOBAL_PREFIX" /root/.local/share/mise/shims/npm install -g "$CLAUDE_CODE_PACKAGE" "$PI_PACKAGE"
if [[ ! -e "$NPM_GLOBAL_PREFIX/bin/claude" ]]; then
echo "claude binary not found after npm install" >&2
if [[ ! -e /root/.local/share/mise/shims/claude ]]; then
echo "claude shim not found after mise install" >&2
exit 1
fi
if [[ ! -e "$NPM_GLOBAL_PREFIX/bin/pi" ]]; then
echo "pi binary not found after npm install" >&2
if [[ ! -e /root/.local/share/mise/shims/pi ]]; then
echo "pi shim not found after mise install" >&2
exit 1
fi
ln -snf /root/.local/share/mise/shims/node /usr/local/bin/node
ln -snf /root/.local/share/mise/shims/npm /usr/local/bin/npm
ln -snf /root/.local/share/mise/shims/opencode /usr/local/bin/opencode
ln -snf "$NPM_GLOBAL_PREFIX/bin/claude" /usr/local/bin/claude
ln -snf "$NPM_GLOBAL_PREFIX/bin/pi" /usr/local/bin/pi
ln -snf /root/.local/share/mise/shims/claude /usr/local/bin/claude
ln -snf /root/.local/share/mise/shims/pi /usr/local/bin/pi
EOF
cat <<'EOF' | sudo tee "$profile_mise" >/dev/null
@ -400,16 +402,15 @@ MANUAL_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}"
BANGER_BIN="$(resolve_banger_bin)"
SSH_KEY="$("$BANGER_BIN" internal ssh-key-path)"
OUT_ROOTFS="$MANUAL_DIR/rootfs-void.ext4"
SIZE_SPEC="2G"
SIZE_SPEC="4G"
MIRROR="https://repo-default.voidlinux.org"
ARCH="x86_64"
MISE_VERSION="v2025.12.0"
MISE_INSTALL_PATH="/usr/local/bin/mise"
NODE_TOOL="node@22"
OPENCODE_TOOL="github:anomalyco/opencode"
CLAUDE_CODE_PACKAGE="@anthropic-ai/claude-code"
PI_PACKAGE="@mariozechner/pi-coding-agent"
NPM_GLOBAL_PREFIX="/root/.local/share/banger/npm-global"
CLAUDE_CODE_TOOL="npm:@anthropic-ai/claude-code"
PI_TOOL="npm:@mariozechner/pi-coding-agent"
GUESTNET_BOOTSTRAP_SCRIPT="$REPO_ROOT/internal/guestnet/assets/bootstrap.sh"
GUESTNET_VOID_CORE_SERVICE="$REPO_ROOT/internal/guestnet/assets/void-core-service.sh"
MODULES_DIR=""