@madarco/agentbox 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_cloud-attach-DMVH6GWO.js +12 -0
- package/dist/chunk-7KOEFGN2.js +1162 -0
- package/dist/chunk-7KOEFGN2.js.map +1 -0
- package/dist/chunk-I24B6AXR.js +600 -0
- package/dist/chunk-I24B6AXR.js.map +1 -0
- package/dist/chunk-NAVL4R34.js +7546 -0
- package/dist/chunk-NAVL4R34.js.map +1 -0
- package/dist/chunk-NW5NYTQM.js +1366 -0
- package/dist/chunk-NW5NYTQM.js.map +1 -0
- package/dist/chunk-UK72UQ5U.js +237 -0
- package/dist/chunk-UK72UQ5U.js.map +1 -0
- package/dist/chunk-V5KZGB5V.js +722 -0
- package/dist/chunk-V5KZGB5V.js.map +1 -0
- package/dist/cloud-poller-ZIWSADJB-JXFRJUEM.js +10 -0
- package/dist/dist-ETCFRVPA.js +423 -0
- package/dist/dist-QZGJIBT5.js +1339 -0
- package/dist/dist-QZGJIBT5.js.map +1 -0
- package/dist/dist-R67WMLCF.js +183 -0
- package/dist/dist-R67WMLCF.js.map +1 -0
- package/dist/index.js +4088 -1451
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
- package/runtime/docker/Dockerfile.box +115 -19
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +34 -19
- package/runtime/docker/packages/ctl/dist/bin.cjs +10246 -758
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +13 -3
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +37 -0
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-dockerd-start +87 -7
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +28 -0
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +4 -9
- package/runtime/hetzner/agentbox-checkpoint-cleanup +52 -0
- package/runtime/hetzner/agentbox-codex-hooks.json +37 -0
- package/runtime/hetzner/agentbox-dockerd-start +132 -0
- package/runtime/hetzner/agentbox-open +28 -0
- package/runtime/hetzner/agentbox-setup-skill.md +196 -0
- package/runtime/hetzner/agentbox-vnc-start +77 -0
- package/runtime/hetzner/claude-managed-settings.json +54 -0
- package/runtime/hetzner/ctl.cjs +22350 -0
- package/runtime/hetzner/custom-system-CLAUDE.md +27 -0
- package/runtime/hetzner/scripts/install-box.sh +365 -0
- package/runtime/relay/bin.cjs +9182 -754
- package/share/agentbox-setup/SKILL.md +34 -19
- package/dist/chunk-6VTAPD4H.js +0 -507
- package/dist/chunk-6VTAPD4H.js.map +0 -1
- package/dist/chunk-7J5AJLWG.js +0 -238
- package/dist/chunk-7J5AJLWG.js.map +0 -1
- package/dist/chunk-FJNIFTWK.js +0 -523
- package/dist/chunk-FJNIFTWK.js.map +0 -1
- package/dist/chunk-HPZMD5DE.js +0 -106
- package/dist/chunk-HPZMD5DE.js.map +0 -1
- package/dist/chunk-PXUBE5KS.js +0 -2346
- package/dist/chunk-PXUBE5KS.js.map +0 -1
- package/dist/chunk-RFC5F5HR.js +0 -1709
- package/dist/chunk-RFC5F5HR.js.map +0 -1
- package/dist/create-AHZ3GVEZ-TGEDL7UX.js +0 -15
- package/dist/lifecycle-LFOL6YFM-TCHDX3J5.js +0 -38
- package/dist/state-KD7M46ZP-KHFTHFUS.js +0 -26
- package/dist/stats-Z4BVJODD-HEC4TMUZ.js +0 -19
- package/dist/stats-Z4BVJODD-HEC4TMUZ.js.map +0 -1
- /package/dist/{create-AHZ3GVEZ-TGEDL7UX.js.map → _cloud-attach-DMVH6GWO.js.map} +0 -0
- /package/dist/{lifecycle-LFOL6YFM-TCHDX3J5.js.map → cloud-poller-ZIWSADJB-JXFRJUEM.js.map} +0 -0
- /package/dist/{state-KD7M46ZP-KHFTHFUS.js.map → dist-ETCFRVPA.js.map} +0 -0
|
@@ -22,8 +22,14 @@ set +e
|
|
|
22
22
|
apt-get clean 2>/dev/null
|
|
23
23
|
rm -rf /var/lib/apt/lists/* 2>/dev/null
|
|
24
24
|
|
|
25
|
-
# Throwaway scratch dirs.
|
|
26
|
-
|
|
25
|
+
# Throwaway scratch dirs. Preserve /tmp/claude-* — that is the live in-box
|
|
26
|
+
# Claude Code session's working tree (its per-task stdout/stderr files). The
|
|
27
|
+
# agent that triggered this checkpoint *is* that session; deleting its task
|
|
28
|
+
# output mid-run makes its harness see ENOENT, treat the command as failed,
|
|
29
|
+
# and retry the checkpoint (observed: 5 duplicate auto-named checkpoints).
|
|
30
|
+
# Stale claude-* dirs baked into the image are tiny and Claude Code prunes
|
|
31
|
+
# them itself on the next session start.
|
|
32
|
+
find /tmp /var/tmp -mindepth 1 -maxdepth 1 ! -name 'claude-*' -exec rm -rf {} + 2>/dev/null
|
|
27
33
|
|
|
28
34
|
# Logs: truncate (don't delete) so the original file modes / ownerships stay
|
|
29
35
|
# intact for the next run. Targets common rotated archives too.
|
|
@@ -31,9 +37,13 @@ find /var/log -type f \( -name '*.log' -o -name '*.gz' -o -name '*.1' \) \
|
|
|
31
37
|
-exec truncate -s0 {} + 2>/dev/null
|
|
32
38
|
find /var/log/agentbox -type f -exec truncate -s0 {} + 2>/dev/null
|
|
33
39
|
|
|
34
|
-
# Bash history (root + vscode).
|
|
40
|
+
# Bash history (root + vscode). Re-assert vscode ownership: `: >` run as root
|
|
41
|
+
# (re)creates the file root-owned 0644 when it didn't exist, which the uid-1000
|
|
42
|
+
# vscode user cannot append to, silently dropping all shell history.
|
|
35
43
|
: > /root/.bash_history 2>/dev/null
|
|
36
44
|
: > /home/vscode/.bash_history 2>/dev/null
|
|
45
|
+
chown vscode:vscode /home/vscode/.bash_history 2>/dev/null
|
|
46
|
+
chmod 600 /home/vscode/.bash_history 2>/dev/null
|
|
37
47
|
|
|
38
48
|
# Anthropic's installer writes a transient marker; redundant once the binary
|
|
39
49
|
# is in place. Safe to wipe.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"SessionStart": [
|
|
3
|
+
{
|
|
4
|
+
"hooks": [
|
|
5
|
+
{ "type": "command", "command": "agentbox-ctl codex-state idle >/dev/null 2>&1 &", "timeout": 3 }
|
|
6
|
+
]
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"UserPromptSubmit": [
|
|
10
|
+
{
|
|
11
|
+
"hooks": [
|
|
12
|
+
{ "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"PreToolUse": [
|
|
17
|
+
{
|
|
18
|
+
"hooks": [
|
|
19
|
+
{ "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"PermissionRequest": [
|
|
24
|
+
{
|
|
25
|
+
"hooks": [
|
|
26
|
+
{ "type": "command", "command": "agentbox-ctl codex-state waiting >/dev/null 2>&1 &", "timeout": 3 }
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"Stop": [
|
|
31
|
+
{
|
|
32
|
+
"hooks": [
|
|
33
|
+
{ "type": "command", "command": "agentbox-ctl codex-state idle >/dev/null 2>&1 &", "timeout": 3 }
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Start the in-box dockerd. Launched by the host via
|
|
3
|
-
# `docker exec -d --user root
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# `docker exec -d --user root`. Idempotent — safe to call again on
|
|
4
|
+
# `agentbox start`. The storage driver is selected at runtime (see
|
|
5
|
+
# select_storage_driver below): the kernel-native `overlay2` when a probe
|
|
6
|
+
# proves it works on the data-root filesystem, otherwise `fuse-overlayfs`.
|
|
7
|
+
# The chosen driver is written to /etc/docker/daemon.json before launch.
|
|
7
8
|
|
|
8
9
|
set -euo pipefail
|
|
9
10
|
|
|
@@ -33,14 +34,93 @@ rm -f /var/run/docker.pid /var/run/docker.sock
|
|
|
33
34
|
mount -o remount,rw /sys/fs/cgroup 2>/dev/null || true
|
|
34
35
|
mount -o remount,rw /proc/sys 2>/dev/null || true
|
|
35
36
|
|
|
37
|
+
# --- Storage-driver selection -------------------------------------------------
|
|
38
|
+
# The inner dockerd's data root (/var/lib/docker, a Docker named volume) used
|
|
39
|
+
# to be pinned to fuse-overlayfs. fuse-overlayfs is broken on recent kernels
|
|
40
|
+
# (e.g. Docker Desktop's 6.x linuxkit kernel): inner `docker run` fails at
|
|
41
|
+
# execve() with "exec ...: invalid argument". The kernel-native overlay2
|
|
42
|
+
# driver works when the data-root filesystem can carry an overlay mount, which
|
|
43
|
+
# the ext4 named volume can. We pick overlay2 when a probe proves it works,
|
|
44
|
+
# else fall back to fuse-overlayfs.
|
|
45
|
+
#
|
|
46
|
+
# dockerd refuses to switch drivers once its data root is populated, so if the
|
|
47
|
+
# data root is already initialized under one driver we reuse that driver and
|
|
48
|
+
# skip the probe — a box created under one driver never switches.
|
|
49
|
+
DOCKER_DATA_ROOT=/var/lib/docker
|
|
50
|
+
DAEMON_JSON=/etc/docker/daemon.json
|
|
51
|
+
|
|
52
|
+
probe_overlay2() {
|
|
53
|
+
# The kernel overlay filesystem has to exist at all.
|
|
54
|
+
grep -qw overlay /proc/filesystems 2>/dev/null || return 1
|
|
55
|
+
|
|
56
|
+
local probe lower upper work merged ok=1
|
|
57
|
+
# The probe dir MUST live inside the data root so the test overlay is mounted
|
|
58
|
+
# on the SAME filesystem the real graph will use. A probe under /tmp would
|
|
59
|
+
# test the container's overlayfs writable layer — the wrong filesystem.
|
|
60
|
+
probe="$(mktemp -d "$DOCKER_DATA_ROOT/.overlay2-probe.XXXXXX" 2>/dev/null)" || return 1
|
|
61
|
+
lower="$probe/lower"; upper="$probe/upper"; work="$probe/work"; merged="$probe/merged"
|
|
62
|
+
mkdir -p "$lower" "$upper" "$work" "$merged" || { rm -rf "$probe"; return 1; }
|
|
63
|
+
|
|
64
|
+
# Stage a known-good executable so the merged view exposes it.
|
|
65
|
+
cp /bin/true "$lower/probe-bin" 2>/dev/null || { rm -rf "$probe"; return 1; }
|
|
66
|
+
chmod 0755 "$lower/probe-bin" 2>/dev/null || true
|
|
67
|
+
|
|
68
|
+
if mount -t overlay overlay \
|
|
69
|
+
-o "lowerdir=$lower,upperdir=$upper,workdir=$work" "$merged" 2>/dev/null; then
|
|
70
|
+
# The actual fuse-overlayfs failure mode: execve from the merged dir. A
|
|
71
|
+
# successful mount is not enough — fuse-overlayfs mounts fine and only
|
|
72
|
+
# fails here.
|
|
73
|
+
"$merged/probe-bin" >/dev/null 2>&1 || ok=0
|
|
74
|
+
umount "$merged" 2>/dev/null || umount -l "$merged" 2>/dev/null || true
|
|
75
|
+
else
|
|
76
|
+
ok=0
|
|
77
|
+
fi
|
|
78
|
+
rm -rf "$probe"
|
|
79
|
+
[ "$ok" = 1 ]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
select_storage_driver() {
|
|
83
|
+
# 1. Reuse an already-initialized data root's driver — dockerd cannot switch
|
|
84
|
+
# a populated data root, and this script reruns on every `agentbox start`.
|
|
85
|
+
local has_overlay2=0 has_fuse=0
|
|
86
|
+
[ -d "$DOCKER_DATA_ROOT/overlay2" ] \
|
|
87
|
+
&& [ -n "$(ls -A "$DOCKER_DATA_ROOT/overlay2" 2>/dev/null)" ] && has_overlay2=1
|
|
88
|
+
[ -d "$DOCKER_DATA_ROOT/fuse-overlayfs" ] \
|
|
89
|
+
&& [ -n "$(ls -A "$DOCKER_DATA_ROOT/fuse-overlayfs" 2>/dev/null)" ] && has_fuse=1
|
|
90
|
+
if [ "$has_overlay2" = 1 ]; then echo "overlay2"; return 0; fi
|
|
91
|
+
if [ "$has_fuse" = 1 ]; then echo "fuse-overlayfs"; return 0; fi
|
|
92
|
+
|
|
93
|
+
# 2. Fresh data root: probe overlay2 against the data-root filesystem.
|
|
94
|
+
if probe_overlay2; then echo "overlay2"; return 0; fi
|
|
95
|
+
echo "fuse-overlayfs"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Sweep any leaked probe dir from a hard-killed previous run (cosmetic; the
|
|
99
|
+
# driver subdir checks above ignore it, and dockerd ignores non-driver dirs).
|
|
100
|
+
rm -rf "$DOCKER_DATA_ROOT"/.overlay2-probe.* 2>/dev/null || true
|
|
101
|
+
|
|
102
|
+
STORAGE_DRIVER="$(select_storage_driver)"
|
|
103
|
+
|
|
104
|
+
# Write daemon.json with the resolved driver. `iptables: true` stays for inner
|
|
105
|
+
# bridge networking. Rewritten every start, but the driver is stable (step 1
|
|
106
|
+
# above), so this never causes a mid-life driver switch.
|
|
107
|
+
mkdir -p /etc/docker
|
|
108
|
+
printf '%s\n' \
|
|
109
|
+
"{ \"storage-driver\": \"$STORAGE_DRIVER\", \"iptables\": true }" \
|
|
110
|
+
> "$DAEMON_JSON"
|
|
111
|
+
# Truncate dockerd.log fresh for this start, marker line first; dockerd appends.
|
|
112
|
+
echo "agentbox-dockerd-start: storage-driver=$STORAGE_DRIVER" \
|
|
113
|
+
> /var/log/agentbox/dockerd.log
|
|
114
|
+
# --- end storage-driver selection --------------------------------------------
|
|
115
|
+
|
|
36
116
|
# nohup + & + disown lets us survive the `docker exec -d` returning. dockerd
|
|
37
117
|
# reads /etc/docker/daemon.json on its own; no flags here keeps the start path
|
|
38
118
|
# debuggable from inside the container (just edit the file and restart).
|
|
39
|
-
nohup dockerd
|
|
119
|
+
nohup dockerd >>/var/log/agentbox/dockerd.log 2>&1 &
|
|
40
120
|
|
|
41
121
|
# Wait for the socket to become accept()-able. Bound by ~30s — first start has
|
|
42
|
-
# to initialize iptables chains and the fuse-overlayfs
|
|
43
|
-
# noticeably slower than overlay2.
|
|
122
|
+
# to initialize iptables chains and the storage graphdriver (fuse-overlayfs is
|
|
123
|
+
# noticeably slower to initialize than overlay2).
|
|
44
124
|
for _ in $(seq 1 300); do
|
|
45
125
|
if [ -S /var/run/docker.sock ] \
|
|
46
126
|
&& docker -H unix:///var/run/docker.sock info >/dev/null 2>&1; then
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Routes in-box URL opens to `agentbox-ctl open`, which opens the link in the
|
|
3
|
+
# box's own Chromium (agent-browser, visible via `agentbox screen`) and asks
|
|
4
|
+
# the host user — in the footer/dashboard — whether to also open it on the
|
|
5
|
+
# host. This script is installed at /usr/local/bin (earlier in PATH than
|
|
6
|
+
# xdg-utils' /usr/bin/xdg-open, which it is also symlinked over) and is the
|
|
7
|
+
# box's $BROWSER, so `xdg-open`, Claude Code's OAuth flow, `gh`,
|
|
8
|
+
# `git web--browse`, python's webbrowser, etc. all land here.
|
|
9
|
+
#
|
|
10
|
+
# Only http(s) URLs are routed. Anything else (a file path, another scheme)
|
|
11
|
+
# falls through to the real xdg-open, which resolves it locally in the box.
|
|
12
|
+
|
|
13
|
+
set -uo pipefail
|
|
14
|
+
|
|
15
|
+
target="${1:-}"
|
|
16
|
+
|
|
17
|
+
case "$target" in
|
|
18
|
+
http://* | https://*)
|
|
19
|
+
exec agentbox-ctl open "$target"
|
|
20
|
+
;;
|
|
21
|
+
*)
|
|
22
|
+
if [[ -x /usr/bin/xdg-open ]]; then
|
|
23
|
+
exec /usr/bin/xdg-open "$@"
|
|
24
|
+
fi
|
|
25
|
+
echo "agentbox-open: not an http(s) URL: $target" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
;;
|
|
28
|
+
esac
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
You are running inside an AgentBox sandbox: a Linux Docker container with
|
|
4
4
|
docker-in-docker (run `docker` directly, no sudo).
|
|
5
|
-
Your user is `vscode` and you can use passwordless sudo to run commands as root.
|
|
5
|
+
Your user is `vscode` and you can use passwordless **sudo** to run commands as root.
|
|
6
6
|
`/workspace` is your own per-box git worktree on branch `agentbox/<box-name>`:
|
|
7
7
|
writes there stay in the container's writable layer and don't touch the host's working
|
|
8
8
|
tree.
|
|
9
9
|
|
|
10
10
|
You can save the current filesystem state to be reused by future boxes by
|
|
11
|
-
running `agentbox-ctl checkpoint --set-default`.
|
|
11
|
+
running `agentbox-ctl checkpoint --set-default`. This doesn't need to be confirmed by the user.
|
|
12
12
|
|
|
13
13
|
The main `.git/` is bind-mounted at the same absolute path as on
|
|
14
14
|
the host, so local commits show up in the host's `git log` immediately.
|
|
@@ -18,15 +18,10 @@ No SSH creds, no host gitconfig identity. For ops that need the user
|
|
|
18
18
|
|
|
19
19
|
For ad-hoc file transfers between this box and the host, use
|
|
20
20
|
`agentbox-ctl cp toHost <boxPath> <hostPath>` and
|
|
21
|
-
`agentbox-ctl cp fromHost <hostPath> <boxPath
|
|
21
|
+
`agentbox-ctl cp fromHost <hostPath> <boxPath>` or `agentbox-ctl download claude` / `download env` /
|
|
22
|
+
`download config`. They RPC to the host and
|
|
22
23
|
ask the user for confirmation on the wrapper that runs `agentbox claude`;
|
|
23
24
|
deny returns exit 10 (`denied by user`).
|
|
24
25
|
Don't put any timeout on the command, it will run forever and the user will be notified through multiple channels.
|
|
25
26
|
|
|
26
|
-
If you install a skill/plugin, change `~/.claude`, or write
|
|
27
|
-
`.env`/`.envrc`/secrets/`agentbox.yaml`, you can pull those onto the host
|
|
28
|
-
yourself with `agentbox-ctl download claude` / `download env` /
|
|
29
|
-
`download config` (also user-confirmed; additive; never overwrites host
|
|
30
|
-
files, don't put any timeout on the command).
|
|
31
|
-
|
|
32
27
|
Box identity: /etc/agentbox/box.env and the AGENTBOX_* env vars.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-`docker commit` cleanup: strip ephemeral / disposable state so the
|
|
3
|
+
# captured checkpoint image is closer to "warm project state, nothing else".
|
|
4
|
+
#
|
|
5
|
+
# Invoked by the host via `docker exec --user root <container>
|
|
6
|
+
# /usr/local/bin/agentbox-checkpoint-cleanup` right before
|
|
7
|
+
# `docker commit`. Best-effort: every step is allowed to fail (a checkpoint
|
|
8
|
+
# capture should never block on cleanup hiccups).
|
|
9
|
+
#
|
|
10
|
+
# What we DELIBERATELY keep:
|
|
11
|
+
# - /workspace the actual point of the checkpoint
|
|
12
|
+
# - /home/vscode/.npm warm npm cache (next install is fast)
|
|
13
|
+
# - /home/vscode/.cache pnpm/yarn/Cargo/etc. caches
|
|
14
|
+
# - /var/lib/docker in-box dockerd's data root
|
|
15
|
+
# - /home/vscode/.claude the named volume is bind-mounted; image
|
|
16
|
+
# layer never sees it anyway
|
|
17
|
+
set +e
|
|
18
|
+
|
|
19
|
+
# apt: drop downloaded .deb cache and the package index. The index is ~50MB
|
|
20
|
+
# and gets refreshed on the next `apt-get update`; the .deb cache is reusable
|
|
21
|
+
# only if we don't change versions, which we usually do.
|
|
22
|
+
apt-get clean 2>/dev/null
|
|
23
|
+
rm -rf /var/lib/apt/lists/* 2>/dev/null
|
|
24
|
+
|
|
25
|
+
# Throwaway scratch dirs. Preserve /tmp/claude-* — that is the live in-box
|
|
26
|
+
# Claude Code session's working tree (its per-task stdout/stderr files). The
|
|
27
|
+
# agent that triggered this checkpoint *is* that session; deleting its task
|
|
28
|
+
# output mid-run makes its harness see ENOENT, treat the command as failed,
|
|
29
|
+
# and retry the checkpoint (observed: 5 duplicate auto-named checkpoints).
|
|
30
|
+
# Stale claude-* dirs baked into the image are tiny and Claude Code prunes
|
|
31
|
+
# them itself on the next session start.
|
|
32
|
+
find /tmp /var/tmp -mindepth 1 -maxdepth 1 ! -name 'claude-*' -exec rm -rf {} + 2>/dev/null
|
|
33
|
+
|
|
34
|
+
# Logs: truncate (don't delete) so the original file modes / ownerships stay
|
|
35
|
+
# intact for the next run. Targets common rotated archives too.
|
|
36
|
+
find /var/log -type f \( -name '*.log' -o -name '*.gz' -o -name '*.1' \) \
|
|
37
|
+
-exec truncate -s0 {} + 2>/dev/null
|
|
38
|
+
find /var/log/agentbox -type f -exec truncate -s0 {} + 2>/dev/null
|
|
39
|
+
|
|
40
|
+
# Bash history (root + vscode). Re-assert vscode ownership: `: >` run as root
|
|
41
|
+
# (re)creates the file root-owned 0644 when it didn't exist, which the uid-1000
|
|
42
|
+
# vscode user cannot append to, silently dropping all shell history.
|
|
43
|
+
: > /root/.bash_history 2>/dev/null
|
|
44
|
+
: > /home/vscode/.bash_history 2>/dev/null
|
|
45
|
+
chown vscode:vscode /home/vscode/.bash_history 2>/dev/null
|
|
46
|
+
chmod 600 /home/vscode/.bash_history 2>/dev/null
|
|
47
|
+
|
|
48
|
+
# Anthropic's installer writes a transient marker; redundant once the binary
|
|
49
|
+
# is in place. Safe to wipe.
|
|
50
|
+
rm -rf /home/vscode/.claude-installer 2>/dev/null
|
|
51
|
+
|
|
52
|
+
exit 0
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"SessionStart": [
|
|
3
|
+
{
|
|
4
|
+
"hooks": [
|
|
5
|
+
{ "type": "command", "command": "agentbox-ctl codex-state idle >/dev/null 2>&1 &", "timeout": 3 }
|
|
6
|
+
]
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"UserPromptSubmit": [
|
|
10
|
+
{
|
|
11
|
+
"hooks": [
|
|
12
|
+
{ "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"PreToolUse": [
|
|
17
|
+
{
|
|
18
|
+
"hooks": [
|
|
19
|
+
{ "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"PermissionRequest": [
|
|
24
|
+
{
|
|
25
|
+
"hooks": [
|
|
26
|
+
{ "type": "command", "command": "agentbox-ctl codex-state waiting >/dev/null 2>&1 &", "timeout": 3 }
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"Stop": [
|
|
31
|
+
{
|
|
32
|
+
"hooks": [
|
|
33
|
+
{ "type": "command", "command": "agentbox-ctl codex-state idle >/dev/null 2>&1 &", "timeout": 3 }
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Start the in-box dockerd. Launched by the host via
|
|
3
|
+
# `docker exec -d --user root`. Idempotent — safe to call again on
|
|
4
|
+
# `agentbox start`. The storage driver is selected at runtime (see
|
|
5
|
+
# select_storage_driver below): the kernel-native `overlay2` when a probe
|
|
6
|
+
# proves it works on the data-root filesystem, otherwise `fuse-overlayfs`.
|
|
7
|
+
# The chosen driver is written to /etc/docker/daemon.json before launch.
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
if pgrep -x dockerd >/dev/null; then
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
mkdir -p /var/lib/docker /var/run /var/log/agentbox
|
|
16
|
+
|
|
17
|
+
# /var/run lives in the container's writable layer (not a volume), so files
|
|
18
|
+
# written by the previous dockerd run survive `docker stop`/`start`. dockerd
|
|
19
|
+
# refuses to start if `/var/run/docker.pid` exists with a PID in /proc — and
|
|
20
|
+
# after restart, that PID number has been reassigned to (probably) the new
|
|
21
|
+
# `sleep infinity`. Wipe both the pidfile and the stale socket here; pgrep
|
|
22
|
+
# above already confirmed no real dockerd is running.
|
|
23
|
+
rm -f /var/run/docker.pid /var/run/docker.sock
|
|
24
|
+
|
|
25
|
+
# Cgroup v2 + unprivileged DinD on OrbStack/Docker Desktop: the outer
|
|
26
|
+
# container has /sys/fs/cgroup and /proc/sys bind-mounted RO from the host
|
|
27
|
+
# (Docker's standard hardening). We need both writable for dockerd to:
|
|
28
|
+
# * mkdir /sys/fs/cgroup/docker (its own cgroup slice for child containers)
|
|
29
|
+
# * write /proc/sys/net/ipv6/conf/<veth>/disable_ipv6 (default bridge setup)
|
|
30
|
+
# Without these, `docker run` fails with EROFS or "failed to disable IPv6".
|
|
31
|
+
# SYS_ADMIN + the private cgroup namespace let us remount these RW; the
|
|
32
|
+
# writes only affect the box's own namespaces (not the host). Failure is
|
|
33
|
+
# tolerable — some hosts already mount these RW.
|
|
34
|
+
mount -o remount,rw /sys/fs/cgroup 2>/dev/null || true
|
|
35
|
+
mount -o remount,rw /proc/sys 2>/dev/null || true
|
|
36
|
+
|
|
37
|
+
# --- Storage-driver selection -------------------------------------------------
|
|
38
|
+
# The inner dockerd's data root (/var/lib/docker, a Docker named volume) used
|
|
39
|
+
# to be pinned to fuse-overlayfs. fuse-overlayfs is broken on recent kernels
|
|
40
|
+
# (e.g. Docker Desktop's 6.x linuxkit kernel): inner `docker run` fails at
|
|
41
|
+
# execve() with "exec ...: invalid argument". The kernel-native overlay2
|
|
42
|
+
# driver works when the data-root filesystem can carry an overlay mount, which
|
|
43
|
+
# the ext4 named volume can. We pick overlay2 when a probe proves it works,
|
|
44
|
+
# else fall back to fuse-overlayfs.
|
|
45
|
+
#
|
|
46
|
+
# dockerd refuses to switch drivers once its data root is populated, so if the
|
|
47
|
+
# data root is already initialized under one driver we reuse that driver and
|
|
48
|
+
# skip the probe — a box created under one driver never switches.
|
|
49
|
+
DOCKER_DATA_ROOT=/var/lib/docker
|
|
50
|
+
DAEMON_JSON=/etc/docker/daemon.json
|
|
51
|
+
|
|
52
|
+
probe_overlay2() {
|
|
53
|
+
# The kernel overlay filesystem has to exist at all.
|
|
54
|
+
grep -qw overlay /proc/filesystems 2>/dev/null || return 1
|
|
55
|
+
|
|
56
|
+
local probe lower upper work merged ok=1
|
|
57
|
+
# The probe dir MUST live inside the data root so the test overlay is mounted
|
|
58
|
+
# on the SAME filesystem the real graph will use. A probe under /tmp would
|
|
59
|
+
# test the container's overlayfs writable layer — the wrong filesystem.
|
|
60
|
+
probe="$(mktemp -d "$DOCKER_DATA_ROOT/.overlay2-probe.XXXXXX" 2>/dev/null)" || return 1
|
|
61
|
+
lower="$probe/lower"; upper="$probe/upper"; work="$probe/work"; merged="$probe/merged"
|
|
62
|
+
mkdir -p "$lower" "$upper" "$work" "$merged" || { rm -rf "$probe"; return 1; }
|
|
63
|
+
|
|
64
|
+
# Stage a known-good executable so the merged view exposes it.
|
|
65
|
+
cp /bin/true "$lower/probe-bin" 2>/dev/null || { rm -rf "$probe"; return 1; }
|
|
66
|
+
chmod 0755 "$lower/probe-bin" 2>/dev/null || true
|
|
67
|
+
|
|
68
|
+
if mount -t overlay overlay \
|
|
69
|
+
-o "lowerdir=$lower,upperdir=$upper,workdir=$work" "$merged" 2>/dev/null; then
|
|
70
|
+
# The actual fuse-overlayfs failure mode: execve from the merged dir. A
|
|
71
|
+
# successful mount is not enough — fuse-overlayfs mounts fine and only
|
|
72
|
+
# fails here.
|
|
73
|
+
"$merged/probe-bin" >/dev/null 2>&1 || ok=0
|
|
74
|
+
umount "$merged" 2>/dev/null || umount -l "$merged" 2>/dev/null || true
|
|
75
|
+
else
|
|
76
|
+
ok=0
|
|
77
|
+
fi
|
|
78
|
+
rm -rf "$probe"
|
|
79
|
+
[ "$ok" = 1 ]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
select_storage_driver() {
|
|
83
|
+
# 1. Reuse an already-initialized data root's driver — dockerd cannot switch
|
|
84
|
+
# a populated data root, and this script reruns on every `agentbox start`.
|
|
85
|
+
local has_overlay2=0 has_fuse=0
|
|
86
|
+
[ -d "$DOCKER_DATA_ROOT/overlay2" ] \
|
|
87
|
+
&& [ -n "$(ls -A "$DOCKER_DATA_ROOT/overlay2" 2>/dev/null)" ] && has_overlay2=1
|
|
88
|
+
[ -d "$DOCKER_DATA_ROOT/fuse-overlayfs" ] \
|
|
89
|
+
&& [ -n "$(ls -A "$DOCKER_DATA_ROOT/fuse-overlayfs" 2>/dev/null)" ] && has_fuse=1
|
|
90
|
+
if [ "$has_overlay2" = 1 ]; then echo "overlay2"; return 0; fi
|
|
91
|
+
if [ "$has_fuse" = 1 ]; then echo "fuse-overlayfs"; return 0; fi
|
|
92
|
+
|
|
93
|
+
# 2. Fresh data root: probe overlay2 against the data-root filesystem.
|
|
94
|
+
if probe_overlay2; then echo "overlay2"; return 0; fi
|
|
95
|
+
echo "fuse-overlayfs"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Sweep any leaked probe dir from a hard-killed previous run (cosmetic; the
|
|
99
|
+
# driver subdir checks above ignore it, and dockerd ignores non-driver dirs).
|
|
100
|
+
rm -rf "$DOCKER_DATA_ROOT"/.overlay2-probe.* 2>/dev/null || true
|
|
101
|
+
|
|
102
|
+
STORAGE_DRIVER="$(select_storage_driver)"
|
|
103
|
+
|
|
104
|
+
# Write daemon.json with the resolved driver. `iptables: true` stays for inner
|
|
105
|
+
# bridge networking. Rewritten every start, but the driver is stable (step 1
|
|
106
|
+
# above), so this never causes a mid-life driver switch.
|
|
107
|
+
mkdir -p /etc/docker
|
|
108
|
+
printf '%s\n' \
|
|
109
|
+
"{ \"storage-driver\": \"$STORAGE_DRIVER\", \"iptables\": true }" \
|
|
110
|
+
> "$DAEMON_JSON"
|
|
111
|
+
# Truncate dockerd.log fresh for this start, marker line first; dockerd appends.
|
|
112
|
+
echo "agentbox-dockerd-start: storage-driver=$STORAGE_DRIVER" \
|
|
113
|
+
> /var/log/agentbox/dockerd.log
|
|
114
|
+
# --- end storage-driver selection --------------------------------------------
|
|
115
|
+
|
|
116
|
+
# nohup + & + disown lets us survive the `docker exec -d` returning. dockerd
|
|
117
|
+
# reads /etc/docker/daemon.json on its own; no flags here keeps the start path
|
|
118
|
+
# debuggable from inside the container (just edit the file and restart).
|
|
119
|
+
nohup dockerd >>/var/log/agentbox/dockerd.log 2>&1 &
|
|
120
|
+
|
|
121
|
+
# Wait for the socket to become accept()-able. Bound by ~30s — first start has
|
|
122
|
+
# to initialize iptables chains and the storage graphdriver (fuse-overlayfs is
|
|
123
|
+
# noticeably slower to initialize than overlay2).
|
|
124
|
+
for _ in $(seq 1 300); do
|
|
125
|
+
if [ -S /var/run/docker.sock ] \
|
|
126
|
+
&& docker -H unix:///var/run/docker.sock info >/dev/null 2>&1; then
|
|
127
|
+
break
|
|
128
|
+
fi
|
|
129
|
+
sleep 0.1
|
|
130
|
+
done
|
|
131
|
+
|
|
132
|
+
disown -a
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Routes in-box URL opens to `agentbox-ctl open`, which opens the link in the
|
|
3
|
+
# box's own Chromium (agent-browser, visible via `agentbox screen`) and asks
|
|
4
|
+
# the host user — in the footer/dashboard — whether to also open it on the
|
|
5
|
+
# host. This script is installed at /usr/local/bin (earlier in PATH than
|
|
6
|
+
# xdg-utils' /usr/bin/xdg-open, which it is also symlinked over) and is the
|
|
7
|
+
# box's $BROWSER, so `xdg-open`, Claude Code's OAuth flow, `gh`,
|
|
8
|
+
# `git web--browse`, python's webbrowser, etc. all land here.
|
|
9
|
+
#
|
|
10
|
+
# Only http(s) URLs are routed. Anything else (a file path, another scheme)
|
|
11
|
+
# falls through to the real xdg-open, which resolves it locally in the box.
|
|
12
|
+
|
|
13
|
+
set -uo pipefail
|
|
14
|
+
|
|
15
|
+
target="${1:-}"
|
|
16
|
+
|
|
17
|
+
case "$target" in
|
|
18
|
+
http://* | https://*)
|
|
19
|
+
exec agentbox-ctl open "$target"
|
|
20
|
+
;;
|
|
21
|
+
*)
|
|
22
|
+
if [[ -x /usr/bin/xdg-open ]]; then
|
|
23
|
+
exec /usr/bin/xdg-open "$@"
|
|
24
|
+
fi
|
|
25
|
+
echo "agentbox-open: not an http(s) URL: $target" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
;;
|
|
28
|
+
esac
|