#!/usr/bin/env bash # slaude/ghost.sh — disposable, RAM-only Claude Code REPL. # # Usage: # bash -c "$(curl -fsSL https:///ghost.sh)" # # Any env vars you set before invocation pass through to claude. Auth is # claude's problem, not this script's. # # Spec: see ~/slaude/spec.md set -euo pipefail SHM="" for cand in /dev/shm "/run/user/$(id -u)" /tmp; do if [ -d "$cand" ] && [ -w "$cand" ]; then SHM="$cand"; break; fi done [ -n "$SHM" ] || { echo "ghost.sh: no writable tmpfs candidate found" >&2; exit 3; } # Warn if /tmp turned out to be the only option — likely not tmpfs on this box. if [ "$SHM" = "/tmp" ]; then case "$(stat -f -c %T /tmp 2>/dev/null || stat -f -t /tmp 2>/dev/null)" in *tmpfs*|*ramfs*) ;; *) echo "ghost.sh: warning — /tmp may not be tmpfs on this host; RAM-only guarantee weakened" >&2 ;; esac fi # ── B. tmpfs workspace ────────────────────────────────────────────────── WORK="$(mktemp -d "$SHM/ghost.XXXXXX")" chmod 700 "$WORK" export HOME="$WORK" export XDG_CONFIG_HOME="$WORK/.config" export XDG_CACHE_HOME="$WORK/.cache" export XDG_DATA_HOME="$WORK/.local/share" export XDG_STATE_HOME="$WORK/.local/state" export PATH="$WORK/.local/bin:$PATH" mkdir -p "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" "$WORK/.local/bin" # ── D. Cleanup guards ─────────────────────────────────────────────────── cleanup() { rm -rf -- "$WORK" 2>/dev/null || true; } trap cleanup EXIT INT TERM HUP # Watchdog: detached from session, polls parent, cleans on parent death # (covers SIGKILL of parent — trap won't fire, watchdog still does). PARENT_PID=$$ if command -v setsid >/dev/null 2>&1; then setsid -f bash -c ' trap "" HUP while kill -0 '"$PARENT_PID"' 2>/dev/null; do sleep 1; done rm -rf -- "'"$WORK"'" 2>/dev/null || true ' /dev/null 2>&1 || true else ( trap "" HUP while kill -0 "$PARENT_PID" 2>/dev/null; do sleep 1; done rm -rf -- "$WORK" 2>/dev/null ) /dev/null 2>&1 & disown 2>/dev/null || true fi # ── E. Install Claude Code into tmpfs ────────────────────────────────── # Override with CLAUDE_INSTALLER_URL if upstream URL changes. INSTALLER_URL="${CLAUDE_INSTALLER_URL:-https://claude.ai/install.sh}" install_via_native() { curl -fsSL "$INSTALLER_URL" | bash >&2 } install_via_npm() { command -v npm >/dev/null 2>&1 || return 1 # npm respects npm_config_prefix → installs into $WORK/.local npm_config_prefix="$WORK/.local" \ npm install --silent --no-fund --no-audit --no-update-notifier \ -g @anthropic-ai/claude-code >&2 } if ! install_via_native; then echo "ghost.sh: native installer failed, trying npm fallback" >&2 install_via_npm || { echo "ghost.sh: claude install failed (native + npm)" >&2 exit 4 } fi if ! command -v claude >/dev/null 2>&1; then # Some installers drop into $HOME/.claude/local/claude for guess in \ "$WORK/.local/bin/claude" \ "$WORK/.claude/local/claude" \ "$WORK/bin/claude"; do if [ -x "$guess" ]; then export PATH="$(dirname "$guess"):$PATH" break fi done fi command -v claude >/dev/null 2>&1 || { echo "ghost.sh: claude binary not found after install" >&2 exit 5 } # ── F. Privacy envs ───────────────────────────────────────────────────── export DISABLE_TELEMETRY=1 export DISABLE_ERROR_REPORTING=1 export DISABLE_AUTOUPDATER=1 export DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 export HISTFILE=/dev/null # ── G. Pre-accept the bypass-permissions warning ──────────────────────── # Without this marker, --dangerously-skip-permissions shows an interactive # confirm dialog on first launch — and on tmpfs HOME every launch is "first." mkdir -p "$WORK/.claude" cat > "$WORK/.claude/settings.json" <<'JSON' { "skipDangerousModePermissionPrompt": true } JSON # ── H. Hand off ───────────────────────────────────────────────────────── cd "$WORK" exec claude --dangerously-skip-permissions "$@"