mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
feat(dev-sandbox): support fake installer / fake main / git clones
allow you to simulate the whole official curl | bash installer, and subsequent hermes updates. Run development commands in a bubblewrap filesystem and network sandbox with a local HTTPS MITM fixture server and a fake github git-upload-pack transport. Package the sandbox command and expose it from the nix devShell. Stage the local installer at its canonical fake HTTPS URL and add a persistent installation/update test path. Route root installs through sandbox-owned filesystem locations and snapshot dirty source worktrees into temporary fake commits so update tests can fast-forward without changing the real checkout. Add an explicit --from-main installer mode that fetches the official upstream main outside the sealed sandbox, installs from that snapshot, and then promotes the fake remote to the current worktree so update flows can be exercised with a fast-forward.
This commit is contained in:
parent
c7dd9e5670
commit
db87ddca9f
5 changed files with 710 additions and 148 deletions
|
|
@ -6446,10 +6446,14 @@ def _desktop_linux_needs_no_sandbox() -> bool:
|
|||
unprivileged desktop user on an AppArmor-restricted host. The root case
|
||||
should remain an explicit user choice.
|
||||
"""
|
||||
if os.environ.get("ELECTRON_DISABLE_SANDBOX", 0) == "1":
|
||||
return True
|
||||
|
||||
if sys.platform != "linux":
|
||||
return False
|
||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
||||
return False
|
||||
|
||||
try:
|
||||
with open("/proc/sys/kernel/apparmor_restrict_unprivileged_userns", encoding="utf-8") as f:
|
||||
return f.read().strip() == "1"
|
||||
|
|
|
|||
|
|
@ -30,10 +30,7 @@
|
|||
mkdir -p $out/bin
|
||||
install -Dm755 ${../hermes} $out/bin/hermes
|
||||
'')
|
||||
(pkgs.runCommand "dev-sandbox" { } ''
|
||||
mkdir -p $out/bin
|
||||
install -Dm755 ${../scripts/dev-sandbox.sh} $out/bin/sandbox
|
||||
'')
|
||||
self'.packages.sandbox
|
||||
uv
|
||||
# Headless Wayland compositor for E2E tests (test:e2e:visual).
|
||||
# cage renders a single client with no window management, so
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
...
|
||||
}:
|
||||
let
|
||||
|
||||
sandbox = pkgs.callPackage ./sandbox.nix { };
|
||||
|
||||
minimal = pkgs.callPackage ./hermes-agent.nix {
|
||||
inherit (inputs) uv2nix pyproject-nix pyproject-build-systems;
|
||||
npm-lockfile-fix = inputs'.npm-lockfile-fix.packages.default;
|
||||
|
|
@ -46,6 +49,8 @@
|
|||
packages = {
|
||||
default = full;
|
||||
|
||||
inherit sandbox;
|
||||
|
||||
inherit minimal;
|
||||
|
||||
# Ships discord.py + python-telegram-bot + slack-sdk so a plain
|
||||
|
|
|
|||
120
nix/sandbox.nix
Normal file
120
nix/sandbox.nix
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
# electron deps
|
||||
alsa-lib,
|
||||
at-spi2-atk,
|
||||
atk,
|
||||
cairo,
|
||||
cups,
|
||||
dbus,
|
||||
expat,
|
||||
fontconfig,
|
||||
freetype,
|
||||
glib,
|
||||
gtk3,
|
||||
libdrm,
|
||||
libgbm,
|
||||
libxkbcommon,
|
||||
mesa,
|
||||
nspr,
|
||||
nss,
|
||||
pango,
|
||||
systemd,
|
||||
libX11,
|
||||
libXcomposite,
|
||||
libXdamage,
|
||||
libXext,
|
||||
libXfixes,
|
||||
libXrandr,
|
||||
libXrender,
|
||||
libXtst,
|
||||
libxcb,
|
||||
|
||||
# sandbox deps
|
||||
bash,
|
||||
bubblewrap,
|
||||
cacert,
|
||||
coreutils,
|
||||
curl,
|
||||
gawk,
|
||||
git,
|
||||
glibc,
|
||||
gnumake,
|
||||
gnugrep,
|
||||
gnused,
|
||||
gzip,
|
||||
netcat-gnu,
|
||||
nodejs_22,
|
||||
openssl,
|
||||
python3,
|
||||
slirp4netns,
|
||||
stdenv,
|
||||
gnutar,
|
||||
|
||||
# etc
|
||||
writeShellApplication,
|
||||
lib,
|
||||
}:
|
||||
let
|
||||
electronRuntime = [
|
||||
alsa-lib
|
||||
at-spi2-atk
|
||||
atk
|
||||
cairo
|
||||
cups
|
||||
dbus
|
||||
expat
|
||||
fontconfig
|
||||
freetype
|
||||
glib
|
||||
gtk3
|
||||
libdrm
|
||||
libgbm
|
||||
libxkbcommon
|
||||
mesa
|
||||
nspr
|
||||
nss
|
||||
pango
|
||||
systemd
|
||||
libX11
|
||||
libXcomposite
|
||||
libXdamage
|
||||
libXext
|
||||
libXfixes
|
||||
libXrandr
|
||||
libXrender
|
||||
libXtst
|
||||
libxcb
|
||||
];
|
||||
in
|
||||
writeShellApplication {
|
||||
name = "sandbox";
|
||||
runtimeInputs = [
|
||||
bash
|
||||
bubblewrap
|
||||
cacert
|
||||
coreutils
|
||||
curl
|
||||
gawk
|
||||
git
|
||||
glibc.bin
|
||||
gnumake
|
||||
gnugrep
|
||||
gnused
|
||||
gzip
|
||||
netcat-gnu
|
||||
nodejs_22
|
||||
openssl
|
||||
python3
|
||||
slirp4netns
|
||||
stdenv.cc
|
||||
gnutar
|
||||
]
|
||||
++ electronRuntime;
|
||||
text = ''
|
||||
export DEV_SANDBOX_REAL_CA_CERT=${cacert}/etc/ssl/certs/ca-bundle.crt
|
||||
export DEV_SANDBOX_DYNAMIC_LINKER=${stdenv.cc.bintools.dynamicLinker}
|
||||
export DEV_SANDBOX_NODE_DIR=${nodejs_22}
|
||||
export DEV_SANDBOX_ELECTRON_LD_LIBRARY_PATH=${lib.makeLibraryPath electronRuntime}
|
||||
exec ${../scripts/dev-sandbox.sh} "$@"
|
||||
'';
|
||||
}
|
||||
|
|
@ -1,198 +1,634 @@
|
|||
#!/usr/bin/env bash
|
||||
# Run a Hermes instance in an isolated sandbox — separate HERMES_HOME,
|
||||
# separate Electron userData, and a distinct Desktop app name so it doesn't compete
|
||||
# with your main desktop instance's single-instance lock.
|
||||
# Run a command in a disposable, network-isolated fake Internet.
|
||||
#
|
||||
# By default the sandbox is throwaway: a temp dir is created and removed on
|
||||
# exit. Use --persistent to keep the sandbox across restarts (stored under
|
||||
# .hermes-sandbox/ in the worktree git root).
|
||||
#
|
||||
# Usage:
|
||||
# scripts/dev-sandbox.sh python -m hermes_cli.main
|
||||
# scripts/dev-sandbox.sh hermes desktop
|
||||
# scripts/dev-sandbox.sh electron .
|
||||
# scripts/dev-sandbox.sh -- npm run dev # from apps/desktop/
|
||||
# scripts/dev-sandbox.sh --persistent hermes desktop
|
||||
# scripts/dev-sandbox.sh --persistent -- npm run dev
|
||||
#
|
||||
# Seed the sandbox HERMES_HOME from an existing directory (e.g. your main
|
||||
# ~/.hermes) so config, sessions, skills, etc. are pre-populated:
|
||||
# scripts/dev-sandbox.sh --from ~/.hermes hermes desktop
|
||||
#
|
||||
# Override the app name (default: HermesSandbox):
|
||||
# HERMES_DEV_SANDBOX_NAME=Staging scripts/dev-sandbox.sh hermes desktop
|
||||
#
|
||||
# Override the persistent sandbox dir name (default: .hermes-sandbox):
|
||||
# HERMES_DEV_SANDBOX_DIR=.staging-sandbox scripts/dev-sandbox.sh --persistent hermes desktop
|
||||
# The command runs in bubblewrap's private user, mount, PID, and network
|
||||
# namespaces. Its only writable filesystem is SANDBOX_ROOT. HTTP(S) goes to
|
||||
# a local static MITM proxy. github.com SSH uses a sandbox-local git-upload-pack
|
||||
# shim; neither transport can reach the host network.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${1:-}" = "--internal-run" ]; then
|
||||
shift
|
||||
: "${DEV_SANDBOX_ROOT:?missing DEV_SANDBOX_ROOT}"
|
||||
: "${DEV_SANDBOX_BASH:?missing DEV_SANDBOX_BASH}"
|
||||
: "${DEV_SANDBOX_SLIRP4NETNS:?missing DEV_SANDBOX_SLIRP4NETNS}"
|
||||
: "${DEV_SANDBOX_INTERACTIVE:?missing DEV_SANDBOX_INTERACTIVE}"
|
||||
|
||||
node_env=()
|
||||
if [ -n "${DEV_SANDBOX_NODE_DIR:-}" ]; then
|
||||
node_env+=(--setenv npm_config_nodedir "$DEV_SANDBOX_NODE_DIR")
|
||||
fi
|
||||
electron_env=()
|
||||
if [ -n "${DEV_SANDBOX_ELECTRON_LD_LIBRARY_PATH:-}" ]; then
|
||||
electron_env+=(
|
||||
--setenv LD_LIBRARY_PATH "$DEV_SANDBOX_ELECTRON_LD_LIBRARY_PATH"
|
||||
--setenv HERMES_DESKTOP_DISABLE_GPU 1
|
||||
)
|
||||
fi
|
||||
gui_mounts=()
|
||||
if [ -n "${DEV_SANDBOX_WAYLAND_SOCKET:-}" ]; then
|
||||
runtime_dir="${DEV_SANDBOX_XDG_RUNTIME_DIR:?missing DEV_SANDBOX_XDG_RUNTIME_DIR}"
|
||||
runtime_parent="$(dirname "$runtime_dir")"
|
||||
runtime_grandparent="$(dirname "$runtime_parent")"
|
||||
gui_mounts+=(
|
||||
--dir "$runtime_grandparent"
|
||||
--dir "$runtime_parent"
|
||||
--dir "$runtime_dir"
|
||||
--bind "$DEV_SANDBOX_WAYLAND_SOCKET" "$DEV_SANDBOX_WAYLAND_SOCKET"
|
||||
--setenv XDG_RUNTIME_DIR "$runtime_dir"
|
||||
--setenv WAYLAND_DISPLAY "${DEV_SANDBOX_WAYLAND_DISPLAY:?missing DEV_SANDBOX_WAYLAND_DISPLAY}"
|
||||
)
|
||||
fi
|
||||
|
||||
runtime_mounts=()
|
||||
if [ -d /nix ] && [[ "$(readlink -f "$DEV_SANDBOX_BASH")" == /nix/* ]]; then
|
||||
runtime_mounts+=(--ro-bind /nix /nix)
|
||||
else
|
||||
# Non-Nix Linux distributions keep dynamic executables and their loaders
|
||||
# below these system paths. They are read-only in the sandbox.
|
||||
for path in /usr /bin /sbin /lib /lib64; do
|
||||
[ -e "$path" ] && runtime_mounts+=(--ro-bind "$path" "$path")
|
||||
done
|
||||
fi
|
||||
|
||||
sandbox_info="$DEV_SANDBOX_ROOT/root/logs/bwrap-info.json"
|
||||
: > "$sandbox_info"
|
||||
bwrap \
|
||||
--unshare-user --uid 0 --gid 0 --unshare-pid --unshare-net \
|
||||
--die-with-parent --proc /proc --dev /dev --tmpfs /tmp \
|
||||
"${gui_mounts[@]}" \
|
||||
"${runtime_mounts[@]}" \
|
||||
--dir /usr \
|
||||
--dir /bin \
|
||||
--dir /lib64 \
|
||||
--bind "$DEV_SANDBOX_ROOT/root" /work \
|
||||
--bind "$DEV_SANDBOX_ROOT/root/bin" /bin \
|
||||
--bind "$DEV_SANDBOX_ROOT/root/lib64" /lib64 \
|
||||
--bind "$DEV_SANDBOX_ROOT/root/usr/bin" /usr/bin \
|
||||
--bind "$DEV_SANDBOX_ROOT/root/usr/local" /usr/local \
|
||||
--bind "$DEV_SANDBOX_ROOT/home" /root \
|
||||
--bind "$DEV_SANDBOX_ROOT/etc" /etc \
|
||||
--chdir /work/repo \
|
||||
--clearenv \
|
||||
--setenv PATH "/usr/local/bin:/usr/bin:$PATH" \
|
||||
--setenv HOME /root \
|
||||
--setenv USER root \
|
||||
--setenv LOGNAME root \
|
||||
--setenv CURL_CA_BUNDLE /work/certs/ca.pem \
|
||||
--setenv SSL_CERT_FILE /work/certs/ca.pem \
|
||||
--setenv GIT_SSL_CAINFO /work/certs/ca.pem \
|
||||
--setenv NODE_EXTRA_CA_CERTS /work/certs/real-ca.pem \
|
||||
--setenv HTTP_PROXY http://127.0.0.1:8080 \
|
||||
--setenv HTTPS_PROXY http://127.0.0.1:8080 \
|
||||
--setenv ALL_PROXY http://127.0.0.1:8080 \
|
||||
--setenv NO_PROXY '' \
|
||||
--setenv DEV_SANDBOX_INTERACTIVE "$DEV_SANDBOX_INTERACTIVE" \
|
||||
--setenv ELECTRON_DISABLE_SANDBOX 1 \
|
||||
"${node_env[@]}" \
|
||||
"${electron_env[@]}" \
|
||||
--info-fd 3 \
|
||||
-- "$DEV_SANDBOX_BASH" -ceu '
|
||||
python3 /work/proxy.py /work/http /work/certs /work/certs/real-ca.pem >/work/logs/proxy.log 2>&1 &
|
||||
proxy_pid=$!
|
||||
cleanup() {
|
||||
kill "$proxy_pid" 2>/dev/null || true
|
||||
wait "$proxy_pid" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
for _ in $(seq 1 50); do
|
||||
nc -z 127.0.0.1 8080 && break
|
||||
sleep 0.05
|
||||
done
|
||||
if ! nc -z 127.0.0.1 8080; then
|
||||
cat /work/logs/proxy.log >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
"$@"
|
||||
' sandbox-command "$@" 3>"$sandbox_info" &
|
||||
bwrap_pid=$!
|
||||
for _ in $(seq 1 100); do
|
||||
[ -s "$sandbox_info" ] && break
|
||||
if ! kill -0 "$bwrap_pid" 2>/dev/null; then
|
||||
wait "$bwrap_pid"
|
||||
exit $?
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
sandbox_pid="$(awk -F: '/"child-pid"/ {gsub(/[^0-9]/, "", $2); print $2}' "$sandbox_info")"
|
||||
if [ -z "$sandbox_pid" ]; then
|
||||
echo 'error: Bubblewrap did not report a sandbox child PID' >&2
|
||||
exit 1
|
||||
fi
|
||||
slirp_ready="$DEV_SANDBOX_ROOT/root/logs/slirp.ready"
|
||||
: > "$slirp_ready"
|
||||
"$DEV_SANDBOX_SLIRP4NETNS" --configure --disable-host-loopback --ready-fd=3 \
|
||||
--userns-path="/proc/$sandbox_pid/ns/user" "$sandbox_pid" tap0 \
|
||||
3>"$slirp_ready" >"$DEV_SANDBOX_ROOT/root/logs/slirp.log" 2>&1 &
|
||||
slirp_pid=$!
|
||||
cleanup() {
|
||||
kill "$slirp_pid" 2>/dev/null || true
|
||||
wait "$slirp_pid" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
for _ in $(seq 1 100); do
|
||||
[ -s "$slirp_ready" ] && break
|
||||
if ! kill -0 "$slirp_pid" 2>/dev/null; then
|
||||
cat "$DEV_SANDBOX_ROOT/root/logs/slirp.log" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
if [ ! -s "$slirp_ready" ]; then
|
||||
echo 'error: timed out waiting for sandbox network setup' >&2
|
||||
exit 1
|
||||
fi
|
||||
wait "$bwrap_pid"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
print_help() {
|
||||
cat <<'EOF'
|
||||
Usage: dev-sandbox.sh [--persistent] [--from DIR] [--] <command...>
|
||||
Usage: dev-sandbox.sh [options] [--] <command...>
|
||||
dev-sandbox.sh install [options] [--] [installer arguments...]
|
||||
|
||||
Run a Hermes instance in an isolated sandbox.
|
||||
Run COMMAND in a throwaway chroot-like bubblewrap sandbox. The sandbox has no
|
||||
writable host mounts: only its own root, mounted at /work, is writable.
|
||||
|
||||
Options:
|
||||
--persistent Keep the sandbox dir across restarts (under the worktree
|
||||
git root, in .hermes-sandbox/). Without this flag the
|
||||
sandbox is a temp dir that is removed on exit.
|
||||
--from DIR Copy DIR into the sandbox HERMES_HOME as the starting
|
||||
point (config, sessions, skills, etc.).
|
||||
Ignored if the sandbox HERMES_HOME already has content
|
||||
(e.g. reusing a --persistent sandbox) to avoid clobbering.
|
||||
--delete Delete the existing persistent sandbox in .hermes-sandbox.
|
||||
-h, --help Show this help message.
|
||||
--persistent Keep the whole sandbox under .hermes-sandbox/.
|
||||
--delete Delete the persistent sandbox (asks first).
|
||||
--from DIR One-time copy of DIR into the sandbox's $HOME.
|
||||
Existing persistent sandboxes are never overwritten.
|
||||
--http-root DIR Copy DIR into the fake web server root for this run.
|
||||
Requests map to DIR/<host>/<path>; no URL is forwarded.
|
||||
--installer PATH With `install`, serve PATH at the canonical install.sh
|
||||
URL. Default: scripts/install.sh in this worktree.
|
||||
--from-main With `install`, fetch the real upstream main installer
|
||||
and repository, then advance fake main to this folder
|
||||
after a successful install for update testing.
|
||||
-h, --help Show this help.
|
||||
|
||||
Environment:
|
||||
HERMES_DEV_SANDBOX_NAME Override the app name (default: HermesSandbox)
|
||||
HERMES_DEV_SANDBOX_DIR Override the persistent dir name (default: .hermes-sandbox)
|
||||
The fake web server signs certificates with a CA trusted only inside this
|
||||
sandbox. HTTP_PROXY/HTTPS_PROXY send fixture URLs there first; other HTTP(S)
|
||||
requests pass through the sandbox's rootless outbound network. SSH to github.com
|
||||
runs a sandbox-local upload-pack shim, never your SSH config, agent,
|
||||
known-hosts file, or authorized keys.
|
||||
|
||||
Fake github main always comes from this folder. If it has staged, unstaged, or
|
||||
non-ignored untracked changes, the sandbox warns and creates a temporary local
|
||||
commit containing them; it never stages or commits the real worktree.
|
||||
|
||||
Examples:
|
||||
dev-sandbox.sh hermes desktop
|
||||
dev-sandbox.sh --persistent hermes desktop
|
||||
dev-sandbox.sh --from ~/.hermes hermes desktop
|
||||
dev-sandbox.sh -- npm run dev
|
||||
# create a sandbox, install this branch as `main`, and then drop to a shell,
|
||||
# skipping `hermes setup` & the browser tools for speed.
|
||||
scripts/dev-sandbox.sh install --persistent --skip-setup --skip-browser
|
||||
|
||||
# Install the official upstream main. You're dropped into a shell where
|
||||
# you can run `hermes update`.
|
||||
scripts/dev-sandbox.sh install --persistent --from-main
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
PERSISTENT=false
|
||||
DELETE=false
|
||||
SEED_DIR=""
|
||||
HTTP_ROOT=""
|
||||
INSTALL_SHORTCUT=false
|
||||
INSTALLER_PATH=""
|
||||
INSTALL_FROM_MAIN=false
|
||||
|
||||
if [ "${1:-}" = install ]; then
|
||||
INSTALL_SHORTCUT=true
|
||||
shift
|
||||
fi
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--persistent)
|
||||
PERSISTENT=true
|
||||
shift
|
||||
;;
|
||||
--persistent) PERSISTENT=true; shift ;;
|
||||
--delete) DELETE=true; shift ;;
|
||||
--from)
|
||||
if [ "$#" -lt 2 ] || [[ "$2" == -* ]]; then
|
||||
echo "error: --from requires a directory argument" >&2
|
||||
exit 1
|
||||
fi
|
||||
SEED_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--from=*)
|
||||
SEED_DIR="${1#--from=}"
|
||||
if [ -z "$SEED_DIR" ]; then
|
||||
echo "error: --from requires a directory argument" >&2
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
--delete)
|
||||
DELETE=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
print_help
|
||||
exit 0
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
[ "$#" -ge 2 ] || { echo 'error: --from needs a directory' >&2; exit 1; }
|
||||
SEED_DIR="$2"; shift 2 ;;
|
||||
--http-root)
|
||||
[ "$#" -ge 2 ] || { echo 'error: --http-root needs a directory' >&2; exit 1; }
|
||||
HTTP_ROOT="$2"; shift 2 ;;
|
||||
--installer)
|
||||
[ "$#" -ge 2 ] || { echo 'error: --installer needs a file' >&2; exit 1; }
|
||||
INSTALLER_PATH="$2"; shift 2 ;;
|
||||
--from-main) INSTALL_FROM_MAIN=true; shift ;;
|
||||
--from=*|--http-root=*|--installer=*)
|
||||
key="${1%%=*}"; value="${1#*=}"
|
||||
[ -n "$value" ] || { echo "error: $key needs a value" >&2; exit 1; }
|
||||
case "$key" in
|
||||
--from) SEED_DIR="$value" ;;
|
||||
--http-root) HTTP_ROOT="$value" ;;
|
||||
--installer) INSTALLER_PATH="$value" ;;
|
||||
esac
|
||||
shift ;;
|
||||
-h|--help) print_help; exit 0 ;;
|
||||
--) shift; break ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n "$SEED_DIR" ]; then
|
||||
if [ ! -d "$SEED_DIR" ]; then
|
||||
echo "error: --from dir '$SEED_DIR' does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Resolve to absolute path so it's valid after we cd later.
|
||||
SEED_DIR="$(cd "$SEED_DIR" && pwd)"
|
||||
fi
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
if [ "$INSTALL_SHORTCUT" = false ] && [ "$#" -eq 0 ]; then
|
||||
print_help >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$INSTALLER_PATH" ] && [ "$INSTALL_SHORTCUT" = false ]; then
|
||||
echo 'error: --installer is only valid with the install shortcut' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$INSTALL_FROM_MAIN" = true ] && [ "$INSTALL_SHORTCUT" = false ]; then
|
||||
echo 'error: --from-main is only valid with the install shortcut' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$INSTALL_FROM_MAIN" = true ] && [ -n "$INSTALLER_PATH" ]; then
|
||||
echo 'error: --from-main and --installer cannot be combined' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SANDBOX_DIR_NAME="${HERMES_DEV_SANDBOX_DIR:-.hermes-sandbox}"
|
||||
GIT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR/..")"
|
||||
for dir in "$SEED_DIR" "$HTTP_ROOT"; do
|
||||
[ -z "$dir" ] || [ -d "$dir" ] || { echo "error: directory '$dir' does not exist" >&2; exit 1; }
|
||||
done
|
||||
|
||||
GIT_ROOT="${HERMES_SANDBOX_SOURCE_ROOT:-$(git rev-parse --show-toplevel)}"
|
||||
GIT_ROOT="$(cd "$GIT_ROOT" && pwd)"
|
||||
PERSISTENT_SANDBOX_ROOT="$GIT_ROOT/$SANDBOX_DIR_NAME"
|
||||
if [ "$INSTALL_SHORTCUT" = true ] && [ "$INSTALL_FROM_MAIN" = false ] && [ -z "$INSTALLER_PATH" ]; then
|
||||
INSTALLER_PATH="$GIT_ROOT/scripts/install.sh"
|
||||
fi
|
||||
if [ -n "$INSTALLER_PATH" ] && [ ! -f "$INSTALLER_PATH" ]; then
|
||||
echo "error: installer '$INSTALLER_PATH' does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
COMMIT="$(git -C "$GIT_ROOT" rev-parse --verify 'HEAD^{commit}')" || {
|
||||
echo "error: current folder has no HEAD commit" >&2
|
||||
exit 1
|
||||
}
|
||||
SANDBOX_DIR_NAME="${HERMES_DEV_SANDBOX_DIR:-.hermes-sandbox}"
|
||||
PERSISTENT_ROOT="$GIT_ROOT/$SANDBOX_DIR_NAME"
|
||||
|
||||
if [ "$DELETE" = true ]; then
|
||||
if [ -d "$PERSISTENT_SANDBOX_ROOT" ]; then
|
||||
read -r -p "[sandbox] delete $PERSISTENT_SANDBOX_ROOT? [y/N] " REPLY
|
||||
case "$REPLY" in
|
||||
[yY]|[yY][eE][sS])
|
||||
echo "[sandbox] deleting $PERSISTENT_SANDBOX_ROOT" >&2
|
||||
rm -rf -- "$PERSISTENT_SANDBOX_ROOT"
|
||||
;;
|
||||
*)
|
||||
echo "[sandbox] aborted" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "[sandbox] nothing to delete at $PERSISTENT_SANDBOX_ROOT" >&2
|
||||
if [ ! -d "$PERSISTENT_ROOT" ]; then
|
||||
echo "[sandbox] nothing to delete at $PERSISTENT_ROOT" >&2
|
||||
exit 0
|
||||
fi
|
||||
read -r -p "[sandbox] delete $PERSISTENT_ROOT? [y/N] " reply
|
||||
case "$reply" in
|
||||
y|Y|yes|YES) rm -rf -- "$PERSISTENT_ROOT" ;;
|
||||
*) echo '[sandbox] aborted' >&2; exit 1 ;;
|
||||
esac
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Derive a per-worktree app name so multiple checkouts don't collide.
|
||||
# Each worktree has its own toplevel path even though they share one repo,
|
||||
# so we hash that path into a short, stable suffix.
|
||||
WORKTREE_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR/..")"
|
||||
WORKTREE_ROOT="$(cd "$WORKTREE_ROOT" && pwd)"
|
||||
WORKTREE_HASH="$(printf '%s' "$WORKTREE_ROOT" | cksum | cut -d' ' -f1)"
|
||||
WORKTREE_NAME="$(basename "$WORKTREE_ROOT")"
|
||||
DEFAULT_SANDBOX_NAME="HermesSandbox-${WORKTREE_NAME}-${WORKTREE_HASH}"
|
||||
|
||||
SANDBOX_NAME="${HERMES_DEV_SANDBOX_NAME:-$DEFAULT_SANDBOX_NAME}"
|
||||
|
||||
if [ "$PERSISTENT" = true ]; then
|
||||
SANDBOX_ROOT="$PERSISTENT_SANDBOX_ROOT"
|
||||
SANDBOX_ROOT="$PERSISTENT_ROOT"
|
||||
else
|
||||
SANDBOX_ROOT="$(mktemp -d -t hermes-sandbox.XXXXXX)"
|
||||
cleanup() { chmod -R u+w "$SANDBOX_ROOT"; rm -rf -- "$SANDBOX_ROOT"; }
|
||||
trap cleanup EXIT INT TERM
|
||||
fi
|
||||
|
||||
export HERMES_HOME="$SANDBOX_ROOT/hermes-home"
|
||||
export HERMES_DESKTOP_USER_DATA_DIR="$SANDBOX_ROOT/user-data"
|
||||
export HERMES_DESKTOP_APP_NAME="$SANDBOX_NAME"
|
||||
|
||||
mkdir -p "$HERMES_HOME" "$HERMES_DESKTOP_USER_DATA_DIR"
|
||||
|
||||
if [ -n "$SEED_DIR" ]; then
|
||||
# Only seed when the sandbox HERMES_HOME is empty — avoids clobbering an
|
||||
# existing persistent sandbox on re-run.
|
||||
if [ -z "$(ls -A "$HERMES_HOME" 2>/dev/null)" ]; then
|
||||
echo "[sandbox] seeding HERMES_HOME from $SEED_DIR" >&2
|
||||
cp -a "$SEED_DIR/." "$HERMES_HOME/"
|
||||
else
|
||||
echo "[sandbox] --from ignored: $HERMES_HOME already has content" >&2
|
||||
mkdir -p "$SANDBOX_ROOT"/{root,home,etc}
|
||||
UPSTREAM_REPO=""
|
||||
UPSTREAM_COMMIT=""
|
||||
if [ "$INSTALL_FROM_MAIN" = true ]; then
|
||||
echo '[sandbox] fetching real upstream main for installer/update test' >&2
|
||||
UPSTREAM_REPO="$(mktemp -d -t hermes-sandbox-upstream.XXXXXX)"
|
||||
git -C "$UPSTREAM_REPO" init -q
|
||||
if ! git -C "$UPSTREAM_REPO" fetch -q https://github.com/NousResearch/hermes-agent.git refs/heads/main; then
|
||||
rm -rf -- "$UPSTREAM_REPO"
|
||||
echo 'error: failed to fetch real upstream main' >&2
|
||||
exit 1
|
||||
fi
|
||||
UPSTREAM_COMMIT="$(git -C "$UPSTREAM_REPO" rev-parse FETCH_HEAD)"
|
||||
fi
|
||||
if [ ! -e "$SANDBOX_ROOT/root/repo/.sandbox-source" ]; then
|
||||
mkdir -p "$SANDBOX_ROOT/root/repo"
|
||||
# Persistent roots live under the worktree, so copying with cp would recurse
|
||||
# into the sandbox itself. tar also lets us exclude a worktree's .git file,
|
||||
# which can point at the host's shared worktree metadata.
|
||||
tar -C "$GIT_ROOT" --exclude='./.git' --exclude="./$SANDBOX_DIR_NAME" -cf - . \
|
||||
| tar -C "$SANDBOX_ROOT/root/repo" -xf -
|
||||
: > "$SANDBOX_ROOT/root/repo/.sandbox-source"
|
||||
fi
|
||||
|
||||
echo "[sandbox] HERMES_HOME=$HERMES_HOME" >&2
|
||||
echo "[sandbox] userData=$HERMES_DESKTOP_USER_DATA_DIR" >&2
|
||||
echo "[sandbox] appName=$HERMES_DESKTOP_APP_NAME" >&2
|
||||
if [ "$PERSISTENT" = true ]; then
|
||||
echo "[sandbox] persistent: $SANDBOX_ROOT" >&2
|
||||
if [ -n "$SEED_DIR" ] && [ ! -e "$SANDBOX_ROOT/.seeded" ]; then
|
||||
echo "[sandbox] seeding home from $SEED_DIR" >&2
|
||||
cp -a "$SEED_DIR/." "$SANDBOX_ROOT/home/"
|
||||
: > "$SANDBOX_ROOT/.seeded"
|
||||
fi
|
||||
|
||||
rm -rf "$SANDBOX_ROOT/root/http"
|
||||
mkdir -p "$SANDBOX_ROOT/root/http"
|
||||
if [ -n "$HTTP_ROOT" ]; then
|
||||
cp -a "$HTTP_ROOT/." "$SANDBOX_ROOT/root/http/"
|
||||
fi
|
||||
if [ "$INSTALL_SHORTCUT" = true ]; then
|
||||
mkdir -p "$SANDBOX_ROOT/root/http/hermes-agent.nousresearch.com"
|
||||
if [ "$INSTALL_FROM_MAIN" = true ]; then
|
||||
git -C "$UPSTREAM_REPO" show "$UPSTREAM_COMMIT:scripts/install.sh" \
|
||||
> "$SANDBOX_ROOT/root/http/hermes-agent.nousresearch.com/install.sh"
|
||||
else
|
||||
cp -a "$INSTALLER_PATH" "$SANDBOX_ROOT/root/http/hermes-agent.nousresearch.com/install.sh"
|
||||
fi
|
||||
set -- bash -c '
|
||||
set +e
|
||||
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- "$@"
|
||||
install_status=$?
|
||||
if [ "$install_status" -eq 0 ] && [ -f /work/promote-main ]; then
|
||||
next_main=$(cat /work/promote-main)
|
||||
if git --git-dir=/work/repos/hermes-agent.git update-ref refs/heads/main "$next_main"; then
|
||||
rm -f /work/promote-main
|
||||
printf "[sandbox] fake main advanced to this folder for update testing\n" >&2
|
||||
else
|
||||
printf "[sandbox] failed to advance fake main after install\n" >&2
|
||||
install_status=1
|
||||
fi
|
||||
fi
|
||||
if [ "$DEV_SANDBOX_INTERACTIVE" = true ]; then
|
||||
printf "\n[sandbox] installer exited %s; entering sandbox shell\n" "$install_status" >&2
|
||||
exec </dev/tty >/dev/tty 2>&1
|
||||
exec bash -i
|
||||
fi
|
||||
exit "$install_status"
|
||||
' sandbox-installer "$@"
|
||||
fi
|
||||
|
||||
mkdir -p "$SANDBOX_ROOT/root"/{bin,certs,lib64,logs,repos,ssh,usr/bin,usr/local}
|
||||
REAL_CA_CERT="${DEV_SANDBOX_REAL_CA_CERT:-}"
|
||||
if [ -z "$REAL_CA_CERT" ]; then
|
||||
for candidate in /etc/ssl/certs/ca-certificates.crt /etc/ssl/cert.pem; do
|
||||
if [ -f "$candidate" ]; then
|
||||
REAL_CA_CERT="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ ! -f "$REAL_CA_CERT" ]; then
|
||||
echo 'error: no system CA bundle found for outbound sandbox HTTPS' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$SANDBOX_ROOT/root/certs/real-ca.pem" ]; then
|
||||
cp "$REAL_CA_CERT" "$SANDBOX_ROOT/root/certs/real-ca.pem"
|
||||
fi
|
||||
printf 'nameserver 10.0.2.3\n' > "$SANDBOX_ROOT/etc/resolv.conf"
|
||||
SANDBOX_SHELL="$(command -v bash)"
|
||||
DYNAMIC_LINKER="${DEV_SANDBOX_DYNAMIC_LINKER:-}"
|
||||
if [ -z "$DYNAMIC_LINKER" ]; then
|
||||
for candidate in /nix/store/*-glibc-*/lib/ld-linux-*.so.*; do
|
||||
if [ -f "$candidate" ]; then
|
||||
DYNAMIC_LINKER="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ ! -f "$DYNAMIC_LINKER" ]; then
|
||||
echo 'error: no glibc dynamic linker found for sandboxed release binaries' >&2
|
||||
exit 1
|
||||
fi
|
||||
ln -sf "$SANDBOX_SHELL" "$SANDBOX_ROOT/root/bin/sh"
|
||||
ln -sf "$(command -v ls)" "$SANDBOX_ROOT/root/bin/ls"
|
||||
ln -sf "$(command -v env)" "$SANDBOX_ROOT/root/usr/bin/env"
|
||||
ln -sf "$DYNAMIC_LINKER" "$SANDBOX_ROOT/root/lib64/$(basename "$DYNAMIC_LINKER")"
|
||||
printf 'root:x:0:0:Sandbox Root:/root:%s\n' "$SANDBOX_SHELL" > "$SANDBOX_ROOT/etc/passwd"
|
||||
printf 'root:x:0:\n' > "$SANDBOX_ROOT/etc/group"
|
||||
printf 'hosts: files dns\n' > "$SANDBOX_ROOT/etc/nsswitch.conf"
|
||||
printf '127.0.0.1 localhost\n' > "$SANDBOX_ROOT/etc/hosts"
|
||||
|
||||
SOURCE_REPO="$GIT_ROOT"
|
||||
SOURCE_REF="$COMMIT"
|
||||
SNAPSHOT_REPO=""
|
||||
FAKE_REPO="$SANDBOX_ROOT/root/repos/hermes-agent.git"
|
||||
git -C "$SANDBOX_ROOT/root/repos" init --bare -q hermes-agent.git
|
||||
if [ "$INSTALL_FROM_MAIN" = true ]; then
|
||||
git --git-dir="$FAKE_REPO" fetch -q --force "$UPSTREAM_REPO" \
|
||||
"$UPSTREAM_COMMIT:refs/heads/main"
|
||||
fi
|
||||
if [ -n "$(git -C "$GIT_ROOT" status --porcelain)" ]; then
|
||||
echo '[sandbox] warning: current folder is dirty; creating a temporary fake commit for main' >&2
|
||||
SNAPSHOT_REPO="$(mktemp -d -t hermes-sandbox-snapshot.XXXXXX)"
|
||||
git -C "$SNAPSHOT_REPO" init -q
|
||||
git -C "$SNAPSHOT_REPO" fetch -q "$GIT_ROOT" "$COMMIT"
|
||||
git -C "$SNAPSHOT_REPO" config user.name 'Hermes sandbox'
|
||||
git -C "$SNAPSHOT_REPO" config user.email 'sandbox@invalid'
|
||||
GIT_DIR="$SNAPSHOT_REPO/.git" GIT_WORK_TREE="$GIT_ROOT" git read-tree "$COMMIT"
|
||||
GIT_DIR="$SNAPSHOT_REPO/.git" GIT_WORK_TREE="$GIT_ROOT" \
|
||||
git add -A -- .
|
||||
SNAPSHOT_TREE="$(GIT_DIR="$SNAPSHOT_REPO/.git" git write-tree)"
|
||||
SNAPSHOT_PARENT="$COMMIT"
|
||||
if EXISTING_MAIN="$(git --git-dir="$FAKE_REPO" rev-parse --verify refs/heads/main 2>/dev/null)"; then
|
||||
git -C "$SNAPSHOT_REPO" fetch -q "$FAKE_REPO" "$EXISTING_MAIN"
|
||||
SNAPSHOT_PARENT="$EXISTING_MAIN"
|
||||
fi
|
||||
SOURCE_REF="$(GIT_DIR="$SNAPSHOT_REPO/.git" git commit-tree "$SNAPSHOT_TREE" -p "$SNAPSHOT_PARENT" \
|
||||
-m 'sandbox snapshot of dirty worktree')"
|
||||
SOURCE_REPO="$SNAPSHOT_REPO"
|
||||
fi
|
||||
|
||||
if [ "$INSTALL_FROM_MAIN" = true ]; then
|
||||
git --git-dir="$FAKE_REPO" fetch -q --force "$SOURCE_REPO" \
|
||||
"$SOURCE_REF:refs/hermes-sandbox/next"
|
||||
printf '%s\n' "$SOURCE_REF" > "$SANDBOX_ROOT/root/promote-main"
|
||||
else
|
||||
echo "[sandbox] ephemeral (will be cleaned up on exit)" >&2
|
||||
git --git-dir="$FAKE_REPO" fetch -q --force "$SOURCE_REPO" \
|
||||
"$SOURCE_REF:refs/heads/main"
|
||||
fi
|
||||
git --git-dir="$FAKE_REPO" symbolic-ref HEAD refs/heads/main
|
||||
if [ -n "$SNAPSHOT_REPO" ]; then
|
||||
rm -rf -- "$SNAPSHOT_REPO"
|
||||
fi
|
||||
if [ -n "$UPSTREAM_REPO" ]; then
|
||||
rm -rf -- "$UPSTREAM_REPO"
|
||||
fi
|
||||
|
||||
if [ "$PERSISTENT" = false ]; then
|
||||
cleanup() {
|
||||
chmod -R u+w "$SANDBOX_ROOT"
|
||||
rm -rf -- "$SANDBOX_ROOT"
|
||||
if [ ! -f "$SANDBOX_ROOT/root/certs/ca.pem" ]; then
|
||||
openssl req -x509 -newkey rsa:2048 -nodes -days 2 \
|
||||
-subj '/CN=Hermes dev sandbox CA' \
|
||||
-keyout "$SANDBOX_ROOT/root/certs/ca.key" \
|
||||
-out "$SANDBOX_ROOT/root/certs/ca.pem" >/dev/null 2>&1
|
||||
fi
|
||||
GIT_UPLOAD_PACK="$(command -v git-upload-pack)"
|
||||
printf '#!%s\nexec %q /work/repos/hermes-agent.git\n' "$SANDBOX_SHELL" "$GIT_UPLOAD_PACK" \
|
||||
> "$SANDBOX_ROOT/root/usr/bin/ssh"
|
||||
chmod 700 "$SANDBOX_ROOT/root/usr/bin/ssh"
|
||||
|
||||
cat > "$SANDBOX_ROOT/root/proxy.py" <<'PY'
|
||||
import pathlib, socket, ssl, subprocess, sys, threading
|
||||
from urllib.parse import unquote, urlsplit
|
||||
|
||||
ROOT, CERTS, REAL_CA = map(pathlib.Path, sys.argv[1:])
|
||||
|
||||
def read_request(conn):
|
||||
data = b""
|
||||
while b"\r\n\r\n" not in data and len(data) < 65536:
|
||||
part = conn.recv(4096)
|
||||
if not part:
|
||||
return b""
|
||||
data += part
|
||||
return data
|
||||
|
||||
def cert_for(host):
|
||||
safe = ''.join(char if char.isalnum() or char in '.-' else '_' for char in host)
|
||||
cert, key = CERTS / f'{safe}.pem', CERTS / f'{safe}.key'
|
||||
if not cert.exists():
|
||||
csr = CERTS / f'{safe}.csr'
|
||||
subprocess.run(['openssl', 'req', '-newkey', 'rsa:2048', '-nodes',
|
||||
'-subj', f'/CN={host}', '-addext', f'subjectAltName=DNS:{host}',
|
||||
'-keyout', str(key), '-out', str(csr)], check=True,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
subprocess.run(['openssl', 'x509', '-req', '-days', '2', '-in', str(csr),
|
||||
'-CA', str(CERTS / 'ca.pem'), '-CAkey', str(CERTS / 'ca.key'),
|
||||
'-CAcreateserial', '-copy_extensions', 'copy', '-out', str(cert)],
|
||||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
return cert, key
|
||||
|
||||
def file_for(host, target):
|
||||
path = urlsplit(target).path or '/'
|
||||
parts = pathlib.PurePosixPath(unquote(path)).parts
|
||||
if '..' in parts:
|
||||
return None
|
||||
candidate = ROOT / host / pathlib.PurePosixPath(*[part for part in parts if part != '/'])
|
||||
if candidate.is_dir():
|
||||
candidate /= 'index.html'
|
||||
return candidate if candidate.is_file() else None
|
||||
|
||||
def respond_fixture(conn, found):
|
||||
body = found.read_bytes()
|
||||
header = b'HTTP/1.1 200 OK\r\n'
|
||||
conn.sendall(header + f'Content-Length: {len(body)}\r\nConnection: close\r\n\r\n'.encode() + body)
|
||||
|
||||
def close_request(request, target=None):
|
||||
headers, separator, body = request.partition(b'\r\n\r\n')
|
||||
lines = headers.split(b'\r\n')
|
||||
if target is not None:
|
||||
method, _, version = lines[0].split(b' ', 2)
|
||||
lines[0] = b' '.join((method, target.encode(), version))
|
||||
lines = [line for line in lines if not line.lower().startswith(b'proxy-connection:')]
|
||||
lines.append(b'Connection: close')
|
||||
return b'\r\n'.join(lines) + separator + body
|
||||
|
||||
def relay(source, destination):
|
||||
while True:
|
||||
chunk = source.recv(65536)
|
||||
if not chunk:
|
||||
return
|
||||
destination.sendall(chunk)
|
||||
|
||||
def forward_https(conn, host, port, request):
|
||||
context = ssl.create_default_context(cafile=str(REAL_CA))
|
||||
with socket.create_connection((host, port), timeout=30) as raw:
|
||||
with context.wrap_socket(raw, server_hostname=host) as upstream:
|
||||
upstream.sendall(close_request(request))
|
||||
relay(upstream, conn)
|
||||
|
||||
def forward_http(conn, host, port, request, target):
|
||||
parsed = urlsplit(target)
|
||||
path = parsed.path or '/'
|
||||
if parsed.query:
|
||||
path += f'?{parsed.query}'
|
||||
with socket.create_connection((host, port), timeout=30) as upstream:
|
||||
upstream.sendall(close_request(request, path))
|
||||
relay(upstream, conn)
|
||||
|
||||
def handle_request(conn):
|
||||
with conn:
|
||||
request = read_request(conn)
|
||||
if not request:
|
||||
return
|
||||
line = request.split(b'\r\n', 1)[0].decode('iso-8859-1')
|
||||
method, target, _ = line.split(' ', 2)
|
||||
if method.upper() == 'CONNECT':
|
||||
host, _, port_text = target.rpartition(':')
|
||||
port = int(port_text or '443')
|
||||
conn.sendall(b'HTTP/1.1 200 Connection Established\r\n\r\n')
|
||||
cert, key = cert_for(host)
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(cert, key)
|
||||
with context.wrap_socket(conn, server_side=True) as tls:
|
||||
nested = read_request(tls)
|
||||
if nested:
|
||||
nested_target = nested.split(b'\r\n', 1)[0].decode('iso-8859-1').split(' ', 2)[1]
|
||||
found = file_for(host, nested_target)
|
||||
if found is not None:
|
||||
respond_fixture(tls, found)
|
||||
else:
|
||||
forward_https(tls, host, port, nested)
|
||||
return
|
||||
parsed = urlsplit(target)
|
||||
host = parsed.hostname
|
||||
if not host:
|
||||
for header in request.split(b'\r\n')[1:]:
|
||||
if header.lower().startswith(b'host:'):
|
||||
host = header.split(b':', 1)[1].strip().decode().split(':', 1)[0]
|
||||
break
|
||||
host = host or 'unknown'
|
||||
found = file_for(host, target)
|
||||
if found is not None:
|
||||
respond_fixture(conn, found)
|
||||
else:
|
||||
forward_http(conn, host, parsed.port or 80, request, target)
|
||||
|
||||
def handle(conn):
|
||||
try:
|
||||
handle_request(conn)
|
||||
except Exception as error:
|
||||
print(f'proxy request failed: {error!r}', file=sys.stderr, flush=True)
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind(('127.0.0.1', 8080))
|
||||
server.listen()
|
||||
while True:
|
||||
conn, _ = server.accept()
|
||||
threading.Thread(target=handle, args=(conn,), daemon=True).start()
|
||||
PY
|
||||
|
||||
if [ "$INSTALL_FROM_MAIN" = true ]; then
|
||||
echo "[sandbox] fake main: real upstream main ($UPSTREAM_COMMIT)" >&2
|
||||
echo "[sandbox] prepared update: current folder ($SOURCE_REF)" >&2
|
||||
else
|
||||
echo "[sandbox] fake main: current folder ($SOURCE_REF)" >&2
|
||||
fi
|
||||
echo "[sandbox] root: $SANDBOX_ROOT" >&2
|
||||
echo "[sandbox] http root: $SANDBOX_ROOT/root/http" >&2
|
||||
[ "$PERSISTENT" = true ] && echo '[sandbox] persistent' >&2 || echo '[sandbox] ephemeral' >&2
|
||||
|
||||
for command in awk bash bwrap curl git nc openssl python3 slirp4netns tar; do
|
||||
command -v "$command" >/dev/null || {
|
||||
echo "error: missing required command: $command" >&2
|
||||
exit 1
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'cleanup; exit 130' INT TERM
|
||||
done
|
||||
|
||||
INTERACTIVE=false
|
||||
if [ -t 0 ] && [ -t 1 ]; then
|
||||
INTERACTIVE=true
|
||||
fi
|
||||
NODE_DIR="${DEV_SANDBOX_NODE_DIR:-}"
|
||||
if [ -z "$NODE_DIR" ] && command -v node >/dev/null; then
|
||||
NODE_DIR="$(dirname "$(dirname "$(command -v node)")")"
|
||||
fi
|
||||
WAYLAND_SOCKET=""
|
||||
if [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -n "${WAYLAND_DISPLAY:-}" ] \
|
||||
&& [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then
|
||||
WAYLAND_SOCKET="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"
|
||||
fi
|
||||
|
||||
"$@"
|
||||
rc=$?
|
||||
exit $rc
|
||||
exec env \
|
||||
DEV_SANDBOX_ROOT="$SANDBOX_ROOT" \
|
||||
DEV_SANDBOX_BASH="$(command -v bash)" \
|
||||
DEV_SANDBOX_SLIRP4NETNS="$(command -v slirp4netns)" \
|
||||
DEV_SANDBOX_REAL_CA_CERT="$REAL_CA_CERT" \
|
||||
DEV_SANDBOX_INTERACTIVE="$INTERACTIVE" \
|
||||
DEV_SANDBOX_NODE_DIR="$NODE_DIR" \
|
||||
DEV_SANDBOX_ELECTRON_LD_LIBRARY_PATH="${DEV_SANDBOX_ELECTRON_LD_LIBRARY_PATH:-}" \
|
||||
DEV_SANDBOX_XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-}" \
|
||||
DEV_SANDBOX_WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-}" \
|
||||
DEV_SANDBOX_WAYLAND_SOCKET="$WAYLAND_SOCKET" \
|
||||
"$BASH_SOURCE" --internal-run "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue