#!/usr/bin/env bash # netvm-chrome.sh [--headless] [--cdp-port N|--no-cdp] [--contained] [url] # Launch a chrome-box profile inside its dedicated NetVM netns. # 1:1: profile = node = warp identity = egress IP. # CDP is on by default (deterministic port) so agents can automate the # session via Playwright/Puppeteer; --headless runs without a display. # --contained adds a bwrap filesystem jail INSIDE the netns (no net unshare): # the browser sees only its own profile home (+ vault read-only). Chromium's # own sandbox stays on (we never pass --no-sandbox to it). # Run as your normal user (uses sudo -n only for allowlisted netns ops). set -euo pipefail NETVM_BIN="$(cd "$(dirname "$0")" && pwd)" . "$NETVM_BIN/netvm-names.sh" WAYPIPE=0; HEADLESS=0; CDP_OVERRIDE=""; NO_CDP=0; PROFILE=""; URL=""; CONTAINED=0 usage() { echo "usage: netvm-chrome.sh [--waypipe] [--headless] [--cdp-port N|--no-cdp] [--contained] [url]"; } while [ $# -gt 0 ]; do case "$1" in --waypipe) WAYPIPE=1; shift;; --headless) HEADLESS=1; shift;; --cdp-port) CDP_OVERRIDE="$2"; shift 2;; --cdp-port=*) CDP_OVERRIDE="${1#*=}"; shift;; --no-cdp) NO_CDP=1; shift;; --contained) CONTAINED=1; shift;; -h|--help) usage; exit 0;; *) if [ -z "$PROFILE" ]; then PROFILE="$1"; elif [ -z "$URL" ]; then URL="$1"; else echo "unexpected: $1"; usage; exit 1; fi; shift;; esac done [ -n "$PROFILE" ] || { usage; exit 1; } # Visible launch needs a display. Auto-detect Wayland when the shell # doesn't have one (e.g. ssh/bare terminal on a Wayland desktop). if [ -z "${WAYLAND_DISPLAY:-}" ] && [ -z "${DISPLAY:-}" ]; then for _s in "/run/user/$(id -u)"/wayland-*; do case "$_s" in *.lock|*'\*') continue;; esac [ -S "$_s" ] || continue export WAYLAND_DISPLAY="$(basename "$_s")" [ -z "${XDG_RUNTIME_DIR:-}" ] && export XDG_RUNTIME_DIR="/run/user/$(id -u)" echo "display: auto-detected WAYLAND_DISPLAY=$WAYLAND_DISPLAY" >&2 break done fi NODE="$PROFILE" netvm_names "$NODE" CHROME_BOX="${CHROME_BOX_BIN:-$HOME/Projects/chrome-box/chrome-box}" [ -x "$CHROME_BOX" ] || { echo "chrome-box not found at $CHROME_BOX (set CHROME_BOX_BIN)"; exit 1; } [ -f "/etc/netvm/${NODE}.conf" ] || { echo "no warp identity for '$NODE' — human: netvm-new-identity.sh $NODE"; exit 1; } if [ "$NO_CDP" = 1 ]; then CDP=""; elif [ -n "$CDP_OVERRIDE" ]; then CDP="$CDP_OVERRIDE"; else CDP="$CDP_PORT"; fi sudo -n "$NETVM_BIN/netvm-node-up.sh" "$NODE" | tail -1 LAUNCH_ARGS=(launch "$PROFILE" --no-sandbox) [ "$HEADLESS" = 1 ] && LAUNCH_ARGS+=(--headless) [ -n "$CDP" ] && LAUNCH_ARGS+=(--cdp-port "$CDP") [ -n "$URL" ] && LAUNCH_ARGS+=("$URL") [ -n "$CDP" ] && echo "cdp: http://$PEER_IP:$CDP/json/list (local) | ssh -L $CDP:$PEER_IP:$CDP @ (remote)" CMD=("$CHROME_BOX" "${LAUNCH_ARGS[@]}") if [ "$CONTAINED" = 1 ]; then # Contained mode execs Chromium directly (same flags chrome-box uses for a # native launch) because chrome-box re-resolves its profile dir from $HOME, # which the jail replaces. Direct Warp egress: no proxy flags by design. command -v bwrap >/dev/null 2>&1 || { echo "bwrap not found" >&2; exit 1; } P_HOME="$HOME/.local/share/chrome-box/profiles/$PROFILE" mkdir -p "$P_HOME/.config/chromium" KEYRING="$(python3 -c "import json,sys;print(json.load(open('$P_HOME/config.json')).get('keyring','basic'))" 2>/dev/null || echo basic)" SB_DIR="$(dirname "$CHROME_BOX")" BWRAP=(bwrap --unshare-uts --unshare-ipc --die-with-parent --ro-bind / / --dev /dev --proc /proc --tmpfs /tmp --tmpfs /dev/shm --bind "$P_HOME" "$HOME" --setenv HOME "$HOME" --setenv PATH /usr/bin:/bin) [ -d "$HOME/notes" ] && BWRAP+=(--ro-bind "$HOME/notes" /tmp/vault) [ -f "$SB_DIR/hosts-sandbox.conf" ] && BWRAP+=(--ro-bind "$SB_DIR/hosts-sandbox.conf" /etc/hosts) [ -f "$SB_DIR/nsswitch-sandbox.conf" ] && BWRAP+=(--ro-bind "$SB_DIR/nsswitch-sandbox.conf" /etc/nsswitch.conf) CHROMIUM=(/usr/lib/chromium/chromium "--user-data-dir=$HOME/.config/chromium" "--password-store=$KEYRING" --ozone-platform=x11 --disable-gpu --disable-quic --disable-features=DnsOverHttpsUpgrade,AsyncDns --built-in-dns-client-enabled=false) if [ "$HEADLESS" = 1 ]; then BWRAP+=(--unsetenv DISPLAY --unsetenv WAYLAND_DISPLAY --unsetenv XDG_RUNTIME_DIR) CHROMIUM+=(--headless=new) else [ -d /tmp/.X11-unix ] && BWRAP+=(--ro-bind /tmp/.X11-unix /tmp/.X11-unix) [ -n "${WAYLAND_DISPLAY:-}" ] && [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ] && \ BWRAP+=(--ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY") [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -d "$XDG_RUNTIME_DIR/pulse" ] && BWRAP+=(--bind "$XDG_RUNTIME_DIR/pulse" /run/pulse) [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -S "$XDG_RUNTIME_DIR/bus" ] && BWRAP+=(--ro-bind "$XDG_RUNTIME_DIR/bus" /run/dbus/system_bus_socket) fi [ -n "$CDP" ] && CHROMIUM+=(--remote-debugging-port="$CDP" --remote-allow-origins='*') [ -n "$URL" ] && CHROMIUM+=("$URL") CMD=("${BWRAP[@]}" -- "${CHROMIUM[@]}") fi if [ "$WAYPIPE" = 1 ]; then exec waypipe --compress=lz4 run -- sudo -n "$NETVM_BIN/netvm-enter.sh" "$NODE" "$(id -u)" "$(id -g)" "$HOME" -- "${CMD[@]}" else exec sudo -n "$NETVM_BIN/netvm-enter.sh" "$NODE" "$(id -u)" "$(id -g)" "$HOME" -- "${CMD[@]}" fi