Repository navigation
Expand file tree
/
Copy pathmounts.env
More file actions
142 lines (132 loc) · 6.78 KB
/
Copy pathmounts.env
File metadata and controls
142 lines (132 loc) · 6.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# Single source of truth for sandbox mount + run flags.
# Sourced by bin/sandbox.sh; mirrored (with a lint check) in
# .devcontainer/devcontainer.json.
#
# Identity & layout are AUTO-DETECTED so this repo works for any fork:
# - SANDBOX_LOGIN ← gh api user .login (fallback: git config user.name,
# then $USER, then "user")
# - SANDBOX_WORKSPACE ← parent dir of this repo (so cloning into
# ~/work/sandbox makes ~/work the workspace bind mount;
# override via SANDBOX_WORKSPACE env if you want
# something else)
#
# Do NOT put secrets here — this file is committed.
# --- Auto-detect login (used in volume names, image tag, container name) ---
SANDBOX_LOGIN="${SANDBOX_LOGIN:-$(
gh api user --jq .login 2>/dev/null \
|| git config --global user.name 2>/dev/null \
|| echo "${USER:-user}"
)}"
# --- Workspace path (host dir bind-mounted into the container) ---
# Default: the directory CONTAINING this sandbox repo. So a checkout at
# ~/oss/sandbox/ makes ~/oss/ the workspace; checkout at ~/work/sandbox/
# makes ~/work/ the workspace. Override via SANDBOX_WORKSPACE.
SANDBOX_REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SANDBOX_WORKSPACE="${SANDBOX_WORKSPACE:-$(dirname "$SANDBOX_REPO_DIR")}"
# Sibling work tree (work-internal repos). Mounted read/write for verify
# dogfood only — sandbox identity stays personal-OSS; commit from host with
# projects/.envrc identity.
# `dirname` of a top-level workspace is "/", which would derive "//projects" —
# a path that cannot exist and fails the bind mount. Fall back to a child of the
# workspace in that case; every other layout keeps the sibling semantics.
_sandbox_projects_parent="$(dirname "$SANDBOX_WORKSPACE")"
[ "$_sandbox_projects_parent" = "/" ] && _sandbox_projects_parent="$SANDBOX_WORKSPACE"
SANDBOX_PROJECTS_DIR="${SANDBOX_PROJECTS_DIR:-$_sandbox_projects_parent/projects}"
unset _sandbox_projects_parent
SANDBOX_HOME_DIR="$SANDBOX_WORKSPACE/.sandbox-home"
SANDBOX_INBOX_DIR="$SANDBOX_WORKSPACE/learnings-inbox"
# --- Named volumes (survive `docker rm`) ---
VOL_TOOLCHAINS="${SANDBOX_LOGIN}-toolchains"
VOL_GH="${SANDBOX_LOGIN}-gh"
VOL_CLAUDE="${SANDBOX_LOGIN}-claude"
VOL_CODEX="${SANDBOX_LOGIN}-codex"
VOL_HERMES="${SANDBOX_LOGIN}-hermes"
# --- Image + container identity ---
IMAGE_NAME="${SANDBOX_LOGIN}/sandbox"
IMAGE_TAG="v1"
CONTAINER_NAME="${SANDBOX_LOGIN}-sandbox"
# Node major baked into the image. Default stays conservative; override for
# repos that pin newer engines, e.g. SANDBOX_NODE_MAJOR=24 bin/sandbox.sh rebuild.
SANDBOX_NODE_MAJOR="${SANDBOX_NODE_MAJOR:-20}"
# --- Mount declarations (mirrored in devcontainer.json) ---
SANDBOX_MOUNTS=(
--mount "type=bind,source=$SANDBOX_WORKSPACE,target=/workspace/oss"
--mount "type=bind,source=$SANDBOX_PROJECTS_DIR,target=/workspace/projects"
--mount "type=bind,source=$SANDBOX_HOME_DIR,target=/workspace/home"
--mount "type=bind,source=$SANDBOX_INBOX_DIR,target=/workspace/inbox"
--mount "type=volume,source=$VOL_TOOLCHAINS,target=/workspace/home/.cache/toolchains"
--mount "type=volume,source=$VOL_GH,target=/workspace/home/.config/gh"
--mount "type=volume,source=$VOL_CLAUDE,target=/workspace/home/.claude"
--mount "type=volume,source=$VOL_CODEX,target=/workspace/home/.codex"
--mount "type=volume,source=$VOL_HERMES,target=/workspace/home/.hermes"
--mount "type=tmpfs,target=/run/secrets,tmpfs-size=65536"
)
# --- Runtime flags ---
SANDBOX_RUNFLAGS=(
--hostname "$CONTAINER_NAME"
--stop-timeout "60"
--network "bridge"
# srt (bubblewrap) must create namespaces inside the container; Docker's
# default seccomp profile blocks unprivileged userns creation (measured:
# `unshare -U` rc 1 default). seccomp-bwrap.json is the default moby
# profile plus one allow rule for bwrap's userns/mountns syscalls —
# regenerate with tools/gen-seccomp-profile.sh. Everything else keeps
# Docker's default filtering.
--security-opt "seccomp=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/seccomp-bwrap.json"
--user "dev"
--workdir "/workspace/oss"
# NOTE: --cap-drop=ALL intentionally omitted in v1.
# NOTE: --security-opt=no-new-privileges removed after dogfood:
# it blocked `sudo apt install` (the standard install-as-needed flow
# the user explicitly chose over baking toolchains). Single-user
# container + identity-isolation = no real escalation surface this
# flag was defending against. Re-add only with a concrete threat
# model that justifies the workflow cost.
)
# --- Env-var allowlist passed at runtime ---
# Only the two that tests actually exercise; expand when a real caller needs more.
# GH_TOKEN is piped via tmpfs, not env, so it's NOT in this list.
SANDBOX_ENV_ALLOWLIST=(
GIT_AUTHOR_NAME
GIT_AUTHOR_EMAIL
)
# --- Container runtime shim (cross-platform) --------------------------------
# SANDBOX_RUNTIME=auto|docker|podman. docker and podman share one CLI
# surface, so a single shadowing function covers every call site in the
# scripts that source this file. Apple `container` (macOS 26) has a
# different verb surface; the resolver names it when found so the user
# knows the option exists, but it is not yet a supported backend.
SANDBOX_RUNTIME="${SANDBOX_RUNTIME:-auto}"
_resolve_sandbox_runtime() {
case "$SANDBOX_RUNTIME" in
docker|podman)
command -v "$SANDBOX_RUNTIME" >/dev/null 2>&1 \
|| { echo "sandbox: SANDBOX_RUNTIME=$SANDBOX_RUNTIME but it is not on PATH" >&2; return 69; }
printf '%s' "$SANDBOX_RUNTIME" ;;
auto)
if command -v docker >/dev/null 2>&1; then printf 'docker'
elif command -v podman >/dev/null 2>&1; then printf 'podman'
else
echo "sandbox: no supported container runtime found (docker, podman)" >&2
if command -v container >/dev/null 2>&1; then
echo "sandbox: apple/container is installed, but its CLI is not docker-compatible and is not yet a supported backend" >&2
fi
return 69
fi ;;
*)
echo "sandbox: unknown SANDBOX_RUNTIME='$SANDBOX_RUNTIME' (use auto, docker, or podman)" >&2
return 64 ;;
esac
}
SANDBOX_RT="$(_resolve_sandbox_runtime)" || return 69 2>/dev/null || exit 69
# Shadow `docker` for every sourcing script: `command` skips the function,
# so SANDBOX_RT=docker does not recurse.
docker() { command "$SANDBOX_RT" "$@"; }
# On apparmor-enforcing daemons (most Linux hosts; not Docker Desktop, whose
# VM ships no apparmor) the docker-default profile blocks bwrap's mount
# propagation even with seccomp-bwrap.json above — srt dies "Failed to make
# / slave: Permission denied" (observed on GitHub ubuntu runners). Appended
# here, after the runtime shim, so `docker info` resolves through it.
if docker info --format '{{join .SecurityOptions ","}}' 2>/dev/null | grep -q apparmor; then
SANDBOX_RUNFLAGS+=(--security-opt "apparmor=unconfined")
fi