user-local installs for claude and pi, fix sandbox mounts
Move claude and pi from sudo global installs to ~/.local. Claude binary is copied to /usr/local/bin for sandbox visibility, pi runs via node from ~/.local/lib (ro-mounted). Fix bwrap intermediate dir traversal by setting 0755 perms on auto-created HOME dirs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
-31
@@ -695,35 +695,27 @@ else
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─── 14. npm global packages (system-wide) ──────────────────────────────────
|
# ─── 14. npm global packages (user-local) ───────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "── npm global packages (system-wide) ──"
|
echo "── npm global packages (user-local) ──"
|
||||||
|
|
||||||
|
# Ensure ~/.local/bin is in PATH for this session
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
if ! has npm; then
|
if ! has npm; then
|
||||||
warn "npm not found — skipping global package installs"
|
warn "npm not found — skipping global package installs"
|
||||||
else
|
else
|
||||||
# Use explicit path to system npm if available, to avoid nvm conflicts
|
# Set npm prefix to user-local so no sudo is needed for installs/updates
|
||||||
SYSTEM_NPM="/usr/bin/npm"
|
echo " Configuring npm global prefix to ~/.local..."
|
||||||
if [ ! -f "$SYSTEM_NPM" ]; then
|
npm config set prefix "$HOME/.local"
|
||||||
SYSTEM_NPM="$(command -v npm)"
|
ok "npm prefix set to $HOME/.local"
|
||||||
fi
|
|
||||||
|
|
||||||
# Set npm prefix to system location so all users can access packages
|
|
||||||
echo " Configuring npm global prefix to /usr/local..."
|
|
||||||
sudo "$SYSTEM_NPM" config set prefix /usr/local
|
|
||||||
ok "npm prefix set to /usr/local"
|
|
||||||
|
|
||||||
# Update npm itself to latest
|
|
||||||
echo " Updating npm to latest..."
|
|
||||||
sudo "$SYSTEM_NPM" install -g npm@latest
|
|
||||||
ok "npm updated to v$(npm --version 2>/dev/null)"
|
|
||||||
|
|
||||||
# Pi (coding agent)
|
# Pi (coding agent)
|
||||||
if has pi; then
|
if has pi; then
|
||||||
skip "pi (@mariozechner/pi-coding-agent)"
|
skip "pi (@mariozechner/pi-coding-agent)"
|
||||||
else
|
else
|
||||||
echo " Installing pi..."
|
echo " Installing pi..."
|
||||||
sudo "$SYSTEM_NPM" install -g @mariozechner/pi-coding-agent
|
npm install -g @mariozechner/pi-coding-agent
|
||||||
if has pi; then ok "pi installed"; else warn "pi install failed"; fi
|
if has pi; then ok "pi installed"; else warn "pi install failed"; fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -736,20 +728,12 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fix ~/.pi ownership if needed (in case of mixed user/sudo installs)
|
# Claude Code (uses Anthropic's own installer for auto-update support)
|
||||||
if [ -d "$HOME/.pi" ]; then
|
|
||||||
if find "$HOME/.pi" -not -user "$USER" -print -quit 2>/dev/null | grep -q .; then
|
|
||||||
sudo chown -R "$USER:$(id -gn)" "$HOME/.pi"
|
|
||||||
ok "Fixed ~/.pi ownership to $USER"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Claude Code
|
|
||||||
if has claude; then
|
if has claude; then
|
||||||
skip "claude (@anthropic-ai/claude-code)"
|
skip "claude (claude-code)"
|
||||||
else
|
else
|
||||||
echo " Installing claude-code..."
|
echo " Installing claude-code via Anthropic installer..."
|
||||||
sudo "$SYSTEM_NPM" install -g @anthropic-ai/claude-code
|
curl -fsSL https://claude.ai/install.sh | sh
|
||||||
if has claude; then ok "claude-code installed"; else warn "claude-code install failed"; fi
|
if has claude; then ok "claude-code installed"; else warn "claude-code install failed"; fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -758,7 +742,7 @@ else
|
|||||||
skip "pm2"
|
skip "pm2"
|
||||||
else
|
else
|
||||||
echo " Installing pm2..."
|
echo " Installing pm2..."
|
||||||
sudo "$SYSTEM_NPM" install -g pm2
|
npm install -g pm2
|
||||||
if has pm2; then ok "pm2 installed"; else warn "pm2 install failed"; fi
|
if has pm2; then ok "pm2 installed"; else warn "pm2 install failed"; fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ import { parseStream } from './stream-parser';
|
|||||||
|
|
||||||
const SEND_TIMEOUT_MS = 5 * 60 * 1000;
|
const SEND_TIMEOUT_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
// Resolve absolute path to claude binary
|
// Use /usr/local/bin/claude so it's visible inside bwrap sandbox (which ro-binds /usr).
|
||||||
const CLAUDE_BIN = (() => {
|
// The actual binary lives at ~/.local/bin/claude, symlinked from /usr/local/bin/claude.
|
||||||
const result = Bun.spawnSync({ cmd: ['which', 'claude'], stdout: 'pipe', stderr: 'ignore' });
|
const CLAUDE_BIN = '/usr/local/bin/claude';
|
||||||
return result.stdout.toString().trim() || 'claude';
|
|
||||||
})();
|
|
||||||
|
|
||||||
const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ function isPidAlive(pid: number): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve pi as [node, cli.js]
|
// Resolve pi as [node, cli.js] — the real .js path lives under ~/.local/lib/node_modules
|
||||||
|
// which is ro-mounted in the sandbox. Node is at /usr/bin/node (under /usr ro-bind).
|
||||||
const PI_CMD = (() => {
|
const PI_CMD = (() => {
|
||||||
const whichResult = Bun.spawnSync({ cmd: ['which', 'pi'], stdout: 'pipe', stderr: 'ignore' });
|
const whichResult = Bun.spawnSync({ cmd: ['which', 'pi'], stdout: 'pipe', stderr: 'ignore' });
|
||||||
const piBin = whichResult.stdout.toString().trim() || 'pi';
|
const piBin = whichResult.stdout.toString().trim() || 'pi';
|
||||||
|
|||||||
@@ -65,9 +65,21 @@ export function buildSandboxPrefix(email: string): string[] {
|
|||||||
if (existsSync('/lib64')) args.push('--ro-bind', '/lib64', '/lib64');
|
if (existsSync('/lib64')) args.push('--ro-bind', '/lib64', '/lib64');
|
||||||
if (existsSync('/sbin')) args.push('--ro-bind', '/sbin', '/sbin');
|
if (existsSync('/sbin')) args.push('--ro-bind', '/sbin', '/sbin');
|
||||||
|
|
||||||
|
// Ensure intermediate dirs under HOME are traversable after runuser drops privileges
|
||||||
|
// (bwrap auto-creates them as root-owned drwx------)
|
||||||
|
const homeDir = process.env.HOME!;
|
||||||
|
args.push('--perms', '0755', '--dir', homeDir);
|
||||||
|
|
||||||
// Bun runtime (e.g. /home/pastilhas/.bun)
|
// Bun runtime (e.g. /home/pastilhas/.bun)
|
||||||
args.push('--ro-bind', BUN_DIR, BUN_DIR);
|
args.push('--ro-bind', BUN_DIR, BUN_DIR);
|
||||||
|
|
||||||
|
// npm global packages (~/.local/lib) — pi and its dependencies
|
||||||
|
const localLib = join(homeDir, '.local', 'lib');
|
||||||
|
if (existsSync(localLib)) {
|
||||||
|
args.push('--perms', '0755', '--dir', join(homeDir, '.local'));
|
||||||
|
args.push('--ro-bind', localLib, localLib);
|
||||||
|
}
|
||||||
|
|
||||||
// Project source (for MCP server)
|
// Project source (for MCP server)
|
||||||
args.push('--ro-bind', PROJECT_ROOT, PROJECT_ROOT);
|
args.push('--ro-bind', PROJECT_ROOT, PROJECT_ROOT);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user