#!/usr/bin/env bash
#
# bwrap-claude - Run Claude Code inside a bubblewrap sandbox for ow development.
#
# Drop-in wrapper for the `claude` CLI. Provides filesystem isolation by
# running Claude inside a bubblewrap (bwrap) container. Only explicitly
# allowed directories are visible to Claude inside the sandbox.
#
# This script is for developing on the ow project itself. For Odoo workspaces,
# use the bwrap-claude script generated in the workspace directory.
#
# REQUIREMENTS
#   - bubblewrap (bwrap) installed and available on PATH
#   - Claude Code binary at ~/.local/bin/claude
#
#   Optional (mounts use bind-try and are silently skipped if absent):
#   - Google Chrome or Chromium (/opt/google/chrome or /snap/chromium)
#   - PostgreSQL (Unix socket at /var/run/postgresql)
#   - Odoo filestore (~/.local/share/Odoo)
#
# USAGE
#   bwrap-claude [--add-dir <path> ...] [claude args...]
#
# OPTIONS
#   --add-dir <path>
#       Bind-mount <path> read-write into the sandbox in addition to the
#       pre-configured ow directory. Also passed to Claude as --add-dir
#       so its internal tool access matches the sandbox. All other arguments
#       are forwarded to Claude.
#
# SANDBOX LAYOUT
#   Read-write mounts:
#     ~/.cache/claude*          Claude cache directories
#     ~/.claude, ~/.claude.json Claude config and session data
#     ~/.local/bin/claude       Claude binary symlink (allows Claude auto-updates)
#     ~/.local/share/claude     Claude binary versions cache
#     $OW_ROOT                  ow project directory
#     <--add-dir paths>         Extra directories passed by the caller
#
#   Optional read-write mounts (bind-try, skipped if absent):
#     ~/.local/share/Odoo       Odoo filestore
#     /var/run/postgresql       PostgreSQL Unix socket (local DB access)
#
#   Read-only mounts:
#     /usr (+ /bin, /sbin, /lib, /lib64 symlinks)
#                                System binaries and libraries. /usr is mounted
#                                whole so VSCode can access /usr/share/code when
#                                Claude is launched from the IDE.
#     /etc/alternatives         Symlink targets (Chrome binary chain)
#     /etc/hosts                Hostname resolution
#     /etc/passwd               User database (required for PostgreSQL auth)
#     /etc/ssl                  TLS certificates
#     /run/systemd/resolve      Systemd-resolved DNS
#
#   Optional read-only mounts (bind-try, skipped if absent):
#     /opt/google/chrome        Chrome installation
#     /snap/bin, /snap/chromium Snap Chromium installation
#     /etc/fonts                Font configuration
#     ~/.fonts, ~/.local/share/fonts  User fonts
#
#   Ephemeral:
#     /dev                      Device nodes (via --dev)
#     /proc                     Process info (via --proc)
#     /tmp                      Temporary files (tmpfs, cleared on exit)
#
# NAMESPACE ISOLATION
#   Unshared: IPC, UTS, cgroup
#   Shared:
#     - Network: Claude requires outbound internet access.
#     - PID: Unsharing PID breaks VSCode's ability to attach to the Claude
#             process for the /ide integration.
#
# WORKING DIRECTORY
#   The sandbox starts in $OW_ROOT (the ow project directory).
#
# IDE INTEGRATION
#   VSCode Unix domain sockets (vscode-*.sock) found in $XDG_RUNTIME_DIR are
#   bind-mounted into the sandbox so `claude /ide` can connect to the editor.
#
set -e

HOME="${HOME:-/home/$(whoami)}"
CLAUDE_BIN="${CLAUDE_BIN:-$HOME/.local/bin/claude}"

if [[ ! -x "$CLAUDE_BIN" ]]; then
  echo "Claude Code not found at $CLAUDE_BIN" >&2
  exit 1
fi

CLAUDE_DIRS=(
  "$HOME/.cache/claude"
  "$HOME/.cache/claude-cli-nodejs"
  "$HOME/.claude"
  "$HOME/.local/state/claude"
)
CLAUDE_FILES=(
  "$HOME/.claude.json"
)
for d in "${CLAUDE_DIRS[@]}"; do
  mkdir -p "$d"
done
for f in "${CLAUDE_FILES[@]}"; do
  [[ -e "$f" ]] || touch "$f"
done

OW_ROOT="$(cd "$(dirname "$0")" && pwd)"

ALLOW_DIRS=("$OW_ROOT")
CLAUDE_ARGS=()
while [[ $# -gt 0 ]]; do
  if [[ "$1" == "--add-dir" ]]; then
    shift
    ALLOW_DIRS+=("$(cd "$1" && pwd)")
    shift
  else
    CLAUDE_ARGS+=("$1")
    shift
  fi
done

BWRAP=(
  --bind "$HOME/.local/bin/claude" "$HOME/.local/bin/claude"
  --bind "$HOME/.local/share/claude" "$HOME/.local/share/claude"
  --ro-bind-try "$HOME/.vscode" "$HOME/.vscode"
  --ro-bind /run/systemd/resolve /run/systemd/resolve
  --ro-bind /etc/hosts /etc/hosts
  --ro-bind /etc/ssl /etc/ssl
  --ro-bind-try /etc/ca-certificates /etc/ca-certificates
  --ro-bind-try /usr/share/ca-certificates /usr/share/ca-certificates
  --symlink ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
  --ro-bind /usr /usr
  --symlink usr/bin   /bin
  --symlink usr/sbin  /sbin
  --symlink usr/lib   /lib
  --symlink usr/lib64 /lib64
  --dev /dev
  --proc /proc
  --tmpfs /tmp
  --chdir "$OW_ROOT"
  --unshare-ipc
  --unshare-uts
  --unshare-cgroup
  --die-with-parent
  --new-session
  --bind-try "$HOME/.local/share/Odoo" "$HOME/.local/share/Odoo"
  --bind-try /var/run/postgresql /var/run/postgresql
  --ro-bind /etc/passwd /etc/passwd
  --ro-bind-try /etc/alternatives /etc/alternatives
  --ro-bind-try /opt/google/chrome /opt/google/chrome
  --ro-bind-try /snap/bin /snap/bin
  --ro-bind-try /snap/chromium /snap/chromium
  --ro-bind-try /etc/fonts /etc/fonts
  --ro-bind-try "$HOME/.fonts" "$HOME/.fonts"
  --ro-bind-try "$HOME/.local/share/fonts" "$HOME/.local/share/fonts"
)

for path in "${CLAUDE_DIRS[@]}" "${CLAUDE_FILES[@]}"; do
  BWRAP+=(--bind "$path" "$path")
done

for path in "${ALLOW_DIRS[@]}"; do
  BWRAP+=(--bind "$path" "$path")
done

for sock in "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"/vscode-*.sock; do
  [[ -S "$sock" ]] && BWRAP+=(--bind "$sock" "$sock")
done

CLAUDE_CMD=()
for d in "${ALLOW_DIRS[@]}"; do
  CLAUDE_CMD+=(--add-dir "$d")
done
CLAUDE_CMD+=( "${CLAUDE_ARGS[@]}" )

echo "Running: bwrap ${BWRAP[@]} -- $CLAUDE_BIN ${CLAUDE_CMD[@]}" >&2
exec bwrap "${BWRAP[@]}" -- "$CLAUDE_BIN" "${CLAUDE_CMD[@]}"
