agent-containers 0.1.0.dev0__py3-none-any.whl
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.
- agent_containers/__init__.py +1 -0
- agent_containers/__main__.py +6 -0
- agent_containers/_assets/agent-images/adal/Dockerfile +103 -0
- agent_containers/_assets/agent-images/adal/examples/egress-allowlist.txt +10 -0
- agent_containers/_assets/agent-images/adal/packages-apt.txt +13 -0
- agent_containers/_assets/agent-images/adal/packages-npm.txt +2 -0
- agent_containers/_assets/agent-images/adal/packages-uv.txt +6 -0
- agent_containers/_assets/agent-images/adal/tools-uv.txt +4 -0
- agent_containers/_assets/agent-images/agent-gateway/Dockerfile +50 -0
- agent_containers/_assets/agent-images/agent-gateway/entrypoint.sh +28 -0
- agent_containers/_assets/agent-images/agent-gateway/sshd_config.agent-gateway +11 -0
- agent_containers/_assets/agent-images/aider/Dockerfile +114 -0
- agent_containers/_assets/agent-images/aider/examples/egress-allowlist.txt +16 -0
- agent_containers/_assets/agent-images/aider/packages-apt.txt +16 -0
- agent_containers/_assets/agent-images/aider/packages-npm.txt +7 -0
- agent_containers/_assets/agent-images/aider/packages-uv.txt +9 -0
- agent_containers/_assets/agent-images/aider/tools-uv.txt +9 -0
- agent_containers/_assets/agent-images/claude-code/Dockerfile +171 -0
- agent_containers/_assets/agent-images/claude-code/entrypoint.sh +22 -0
- agent_containers/_assets/agent-images/claude-code/examples/egress-allowlist.txt +14 -0
- agent_containers/_assets/agent-images/claude-code/examples/settings.local-model.json +7 -0
- agent_containers/_assets/agent-images/claude-code/packages-apt.txt +16 -0
- agent_containers/_assets/agent-images/claude-code/packages-npm.txt +7 -0
- agent_containers/_assets/agent-images/claude-code/packages-uv.txt +9 -0
- agent_containers/_assets/agent-images/claude-code/plugin-marketplaces.txt +23 -0
- agent_containers/_assets/agent-images/claude-code/plugins.txt +13 -0
- agent_containers/_assets/agent-images/claude-code/proxy-ca.d/.keep +1 -0
- agent_containers/_assets/agent-images/claude-code/tools-uv.txt +9 -0
- agent_containers/_assets/agent-images/codex/Dockerfile +147 -0
- agent_containers/_assets/agent-images/codex/codex-wrapper.sh +13 -0
- agent_containers/_assets/agent-images/codex/examples/egress-allowlist.txt +10 -0
- agent_containers/_assets/agent-images/codex/packages-apt.txt +13 -0
- agent_containers/_assets/agent-images/codex/packages-npm.txt +2 -0
- agent_containers/_assets/agent-images/codex/packages-uv.txt +6 -0
- agent_containers/_assets/agent-images/codex/plugin-marketplaces.txt +2 -0
- agent_containers/_assets/agent-images/codex/plugins.txt +2 -0
- agent_containers/_assets/agent-images/codex/proxy-ca.d/.keep +1 -0
- agent_containers/_assets/agent-images/codex/tools-uv.txt +4 -0
- agent_containers/_assets/agent-images/hermes/Dockerfile +122 -0
- agent_containers/_assets/agent-images/hermes/entrypoint.sh +49 -0
- agent_containers/_assets/agent-images/hermes/packages-apt.txt +16 -0
- agent_containers/_assets/agent-images/hermes/packages-npm.txt +7 -0
- agent_containers/_assets/agent-images/hermes/packages-uv.txt +9 -0
- agent_containers/_assets/agent-images/hermes/proxy-ca.d/.keep +1 -0
- agent_containers/_assets/agent-images/hermes/tools-uv.txt +9 -0
- agent_containers/_assets/agent-images/kilo-code/Dockerfile +101 -0
- agent_containers/_assets/agent-images/kilo-code/examples/egress-allowlist.txt +7 -0
- agent_containers/_assets/agent-images/kilo-code/packages-apt.txt +5 -0
- agent_containers/_assets/agent-images/kilo-code/packages-npm.txt +1 -0
- agent_containers/_assets/agent-images/kilo-code/packages-uv.txt +4 -0
- agent_containers/_assets/agent-images/kilo-code/tools-uv.txt +4 -0
- agent_containers/_assets/agent-images/opencode/Dockerfile +126 -0
- agent_containers/_assets/agent-images/opencode/examples/egress-allowlist.txt +6 -0
- agent_containers/_assets/agent-images/opencode/packages-apt.txt +13 -0
- agent_containers/_assets/agent-images/opencode/packages-npm.txt +2 -0
- agent_containers/_assets/agent-images/opencode/packages-uv.txt +6 -0
- agent_containers/_assets/agent-images/opencode/proxy-ca.d/.keep +1 -0
- agent_containers/_assets/agent-images/opencode/tools-uv.txt +4 -0
- agent_containers/_assets/agent-images/qwen-code/Dockerfile +104 -0
- agent_containers/_assets/agent-images/qwen-code/examples/egress-allowlist.txt +10 -0
- agent_containers/_assets/agent-images/qwen-code/packages-apt.txt +13 -0
- agent_containers/_assets/agent-images/qwen-code/packages-npm.txt +2 -0
- agent_containers/_assets/agent-images/qwen-code/packages-uv.txt +6 -0
- agent_containers/_assets/agent-images/qwen-code/tools-uv.txt +4 -0
- agent_containers/_assets/agent-images/shared/egress-allowlist.sh +138 -0
- agent_containers/_assets/agent-images/shared/install-additional-packages.sh +79 -0
- agent_containers/_assets/agent-images/shared/install-workload-base.sh +46 -0
- agent_containers/_assets/agent-images/shared/workload-entrypoint.sh +82 -0
- agent_containers/_assets/agent-images/volume-bridge/Dockerfile +37 -0
- agent_containers/_assets/agent-images/volume-bridge/entrypoint.sh +29 -0
- agent_containers/_assets/agent-images/volume-bridge/serve.sh +66 -0
- agent_containers/assets.py +38 -0
- agent_containers/build_context.py +116 -0
- agent_containers/cli.py +212 -0
- agent_containers/creation.py +177 -0
- agent_containers/docker.py +562 -0
- agent_containers/lifecycle.py +325 -0
- agent_containers/planner.py +99 -0
- agent_containers/profile.py +377 -0
- agent_containers/shortcuts.py +159 -0
- agent_containers/state.py +154 -0
- agent_containers-0.1.0.dev0.dist-info/METADATA +195 -0
- agent_containers-0.1.0.dev0.dist-info/RECORD +86 -0
- agent_containers-0.1.0.dev0.dist-info/WHEEL +4 -0
- agent_containers-0.1.0.dev0.dist-info/entry_points.txt +3 -0
- agent_containers-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Deterministic onboarding and maintenance for hardened agent containers."""
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
FROM python:3.14-slim-trixie AS base
|
|
2
|
+
|
|
3
|
+
ARG TARGETARCH
|
|
4
|
+
|
|
5
|
+
# Shared Node.js, containment, gateway-client, and cloudflared baseline for
|
|
6
|
+
# the first-party Python-and-Node workload images.
|
|
7
|
+
COPY --from=shared install-workload-base.sh /usr/local/lib/agent/install-workload-base.sh
|
|
8
|
+
RUN TARGETARCH="$TARGETARCH" bash /usr/local/lib/agent/install-workload-base.sh
|
|
9
|
+
|
|
10
|
+
COPY --from=shared install-additional-packages.sh /usr/local/lib/agent/install-additional-packages.sh
|
|
11
|
+
COPY packages-apt.txt /tmp/packages-apt.txt
|
|
12
|
+
RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
|
|
13
|
+
&& install_apt_packages /tmp/packages-apt.txt' \
|
|
14
|
+
&& rm -f /tmp/packages-apt.txt
|
|
15
|
+
|
|
16
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
|
|
17
|
+
|
|
18
|
+
# packages-uv.txt — general Python libraries (not CLI tools; see
|
|
19
|
+
# tools-uv.txt below), installed via `uv pip install --system` into this
|
|
20
|
+
# system Python. Runs as root, before the adal user exists, since it writes
|
|
21
|
+
# into the image's system site-packages rather than $HOME.
|
|
22
|
+
COPY packages-uv.txt /tmp/packages-uv.txt
|
|
23
|
+
RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
|
|
24
|
+
&& install_uv_packages /tmp/packages-uv.txt' \
|
|
25
|
+
&& rm -f /tmp/packages-uv.txt
|
|
26
|
+
|
|
27
|
+
# The official npm fallback ships the platform-specific AdaL runtime as an
|
|
28
|
+
# optional dependency. Pin it so rebuilding does not silently change the CLI.
|
|
29
|
+
RUN npm install -g @sylphai/adal-cli@1.7.1
|
|
30
|
+
|
|
31
|
+
# The adal account is created here, at a fixed placeholder UID/GID
|
|
32
|
+
# (1000:1000), purely so the root-only installers below (npm, uv tool) have
|
|
33
|
+
# a real non-root account to run as. `final` remaps this account to the
|
|
34
|
+
# operator's actual host UID/GID instead of recreating it, so a rebuild for
|
|
35
|
+
# a different host user reuses everything installed below unchanged rather
|
|
36
|
+
# than reinstalling it over the network.
|
|
37
|
+
RUN groupadd -g 1000 adal \
|
|
38
|
+
&& useradd -m -u 1000 -g 1000 -s /bin/bash adal \
|
|
39
|
+
&& mkdir -p /home/adal/.adal /workspace \
|
|
40
|
+
&& chown -R adal:adal /home/adal /workspace
|
|
41
|
+
|
|
42
|
+
ENV HOME=/home/adal
|
|
43
|
+
ENV npm_config_prefix=/home/adal/.npm-global
|
|
44
|
+
ENV PATH="/home/adal/.local/bin:/home/adal/.npm-global/bin:${PATH}"
|
|
45
|
+
|
|
46
|
+
# $HOME (uv's default cache location) and /workspace are deliberately
|
|
47
|
+
# separate mounts in this containment model, so uv's hardlink/reflink
|
|
48
|
+
# optimisation can never apply between them — declare the copy fallback
|
|
49
|
+
# explicit rather than have uv warn about it on every install.
|
|
50
|
+
ENV UV_LINK_MODE=copy
|
|
51
|
+
|
|
52
|
+
COPY packages-npm.txt /tmp/packages-npm.txt
|
|
53
|
+
COPY tools-uv.txt /tmp/tools-uv.txt
|
|
54
|
+
RUN gosu adal bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
|
|
55
|
+
&& install_npm_packages /tmp/packages-npm.txt \
|
|
56
|
+
&& install_uv_tools /tmp/tools-uv.txt' \
|
|
57
|
+
&& rm -f /tmp/packages-npm.txt /tmp/tools-uv.txt
|
|
58
|
+
|
|
59
|
+
# Everything above this line is UID/GID-independent and layer-cacheable
|
|
60
|
+
# across hosts: the adal account exists at the fixed placeholder UID/GID
|
|
61
|
+
# only so the installers above have a non-root account to run as. From here
|
|
62
|
+
# down, `final` remaps that account to the operator's actual host UID/GID
|
|
63
|
+
# with --build-arg UID=$(id -u) --build-arg GID=$(id -g) — the base stage
|
|
64
|
+
# above is reused unchanged from cache, and nothing installed above is
|
|
65
|
+
# reinstalled, rather than rebuilding per host user.
|
|
66
|
+
FROM base AS final
|
|
67
|
+
ARG UID=1000
|
|
68
|
+
ARG GID=1000
|
|
69
|
+
|
|
70
|
+
# Only remap when the caller supplied a different UID/GID than the fixed
|
|
71
|
+
# placeholder baked into `base` above — usermod/groupmod plus a chown -R
|
|
72
|
+
# walk over already-installed content is far cheaper than rerunning the
|
|
73
|
+
# npm/uv-tool installs above, but it's skippable entirely for the common
|
|
74
|
+
# case where the caller never overrides UID/GID. Deliberately no -o:
|
|
75
|
+
# without it, usermod/groupmod refuse to reuse an ID already assigned to
|
|
76
|
+
# another account, so a caller can never remap adal onto UID/GID 0 (root)
|
|
77
|
+
# or collide it with another in-image identity — the build fails instead of
|
|
78
|
+
# silently succeeding.
|
|
79
|
+
RUN if [ "$UID" != "1000" ] || [ "$GID" != "1000" ]; then \
|
|
80
|
+
groupmod -g "$GID" adal \
|
|
81
|
+
&& usermod -u "$UID" -g "$GID" adal \
|
|
82
|
+
&& chown -R "$UID":"$GID" /home/adal /workspace; \
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
COPY --from=shared egress-allowlist.sh /usr/local/lib/agent/egress-allowlist.sh
|
|
86
|
+
COPY --from=shared workload-entrypoint.sh /usr/local/lib/agent/workload-entrypoint.sh
|
|
87
|
+
RUN chmod +x /usr/local/lib/agent/workload-entrypoint.sh
|
|
88
|
+
|
|
89
|
+
# Strip setuid/setgid bits from the base image and every package installed
|
|
90
|
+
# above — required infra (install-workload-base.sh), packages-apt.txt,
|
|
91
|
+
# packages-npm.txt, packages-uv.txt, and tools-uv.txt — run last so nothing
|
|
92
|
+
# installed by any of those is missed. NoNewPrivs=1 (set by every documented
|
|
93
|
+
# run invocation) already makes these bits inert on execve, but that's an
|
|
94
|
+
# argument for stripping them, not against: if any single control ever
|
|
95
|
+
# regresses, a setuid-root `mount` becomes a live escalation primitive. None
|
|
96
|
+
# of these binaries are needed at runtime, so none should ship
|
|
97
|
+
# exploitable-shaped.
|
|
98
|
+
RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} + || true
|
|
99
|
+
|
|
100
|
+
WORKDIR /workspace
|
|
101
|
+
|
|
102
|
+
ENTRYPOINT ["/usr/local/lib/agent/workload-entrypoint.sh", "adal", "adal"]
|
|
103
|
+
CMD ["adal"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Hosts/IPs the container is allowed to reach outbound, one per line.
|
|
2
|
+
# Blank lines and lines starting with '#' are ignored. Mount this file at
|
|
3
|
+
# /etc/agent/egress-allowlist.txt; it takes precedence over
|
|
4
|
+
# $AGENT_ALLOWED_EGRESS. If neither is set, outbound traffic is denied.
|
|
5
|
+
#
|
|
6
|
+
# AdaL's browser-based sign-in starts at this documented service host. Add
|
|
7
|
+
# only the further hosts required by the features you intentionally enable:
|
|
8
|
+
# direct BYOAK providers, an MCP server, a plugin marketplace, a source host,
|
|
9
|
+
# or a local-model endpoint. Do not use this file to grant broad egress.
|
|
10
|
+
adal.sylph.ai
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Optional Python libraries installed into the system Python at build time
|
|
2
|
+
# via `uv pip install --system`, one per line. Blank lines and lines
|
|
3
|
+
# starting with '#' are ignored. Unlike tools-uv.txt, these land in the one
|
|
4
|
+
# shared system Python rather than an isolated venv, so they're importable
|
|
5
|
+
# by whatever runs inside the image — a CLI-only package with no importable
|
|
6
|
+
# module of its own belongs in tools-uv.txt instead.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# Optional Python CLI tools installed at build time via `uv tool install`,
|
|
2
|
+
# each into its own isolated venv, one per line. Blank lines and lines
|
|
3
|
+
# starting with '#' are ignored. A plain importable library (no
|
|
4
|
+
# console-script entry point of its own) belongs in packages-uv.txt instead.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
FROM debian:trixie-slim
|
|
2
|
+
|
|
3
|
+
ARG TARGETARCH
|
|
4
|
+
|
|
5
|
+
RUN apt-get update \
|
|
6
|
+
&& apt-get upgrade -y \
|
|
7
|
+
&& apt-get install -y --no-install-recommends \
|
|
8
|
+
ca-certificates \
|
|
9
|
+
iptables \
|
|
10
|
+
ipset \
|
|
11
|
+
dnsmasq \
|
|
12
|
+
dnsutils \
|
|
13
|
+
openssh-server \
|
|
14
|
+
python3 \
|
|
15
|
+
curl \
|
|
16
|
+
&& curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${TARGETARCH}" \
|
|
17
|
+
-o /usr/local/bin/cloudflared \
|
|
18
|
+
&& chmod +x /usr/local/bin/cloudflared \
|
|
19
|
+
&& rm -f /etc/ssh/ssh_host_* \
|
|
20
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
21
|
+
# python3 isn't used by anything in this image — it's what sshuttle execs
|
|
22
|
+
# remotely over the SSH session from the claude-code side. The entrypoint
|
|
23
|
+
# copies the mounted public key to a root-owned runtime path under /run, so
|
|
24
|
+
# it works with a read-only root filesystem without CAP_CHOWN. cloudflared
|
|
25
|
+
# is always installed but only ever invoked (see entrypoint.sh) when
|
|
26
|
+
# CLOUDFLARE_TUNNEL_TOKEN is set — the direct-TCP path (Task 4) needs no
|
|
27
|
+
# Cloudflare account and stays completely unaffected by its presence.
|
|
28
|
+
# TARGETARCH is supplied automatically by Buildx and matches cloudflared's
|
|
29
|
+
# own release-asset naming (amd64/arm64/386/...).
|
|
30
|
+
|
|
31
|
+
RUN useradd -m -s /bin/bash -d /home/tunnel tunnel \
|
|
32
|
+
&& passwd -l tunnel
|
|
33
|
+
|
|
34
|
+
COPY --from=shared egress-allowlist.sh /usr/local/lib/agent/egress-allowlist.sh
|
|
35
|
+
COPY sshd_config.agent-gateway /etc/ssh/sshd_config.d/agent-gateway.conf
|
|
36
|
+
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
37
|
+
RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
38
|
+
|
|
39
|
+
# Strip setuid/setgid bits from the base image and packages above (mount,
|
|
40
|
+
# umount, passwd, chfn, gpasswd, newgrp, ...) — none of them are this image's
|
|
41
|
+
# own privilege mechanism. sshd runs as real root throughout (not via a
|
|
42
|
+
# setuid binary) and dnsmasq drops root->its own user via the CAP_SETUID/
|
|
43
|
+
# CAP_SETGID this container is granted directly, not through any setuid-bit
|
|
44
|
+
# binary, so both keep working unaffected. The build's own `passwd -l
|
|
45
|
+
# tunnel` step above already ran as root before this point, so it isn't
|
|
46
|
+
# affected either.
|
|
47
|
+
RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} + || true
|
|
48
|
+
|
|
49
|
+
EXPOSE 2222
|
|
50
|
+
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
AUTHORIZED_KEYS_DIR=/run/agent-gateway
|
|
5
|
+
mkdir -p "$AUTHORIZED_KEYS_DIR"
|
|
6
|
+
chmod 755 "$AUTHORIZED_KEYS_DIR"
|
|
7
|
+
# This is a public key, so root ownership and mode 0644 are safe. Keeping it
|
|
8
|
+
# under /run makes the authorization state ephemeral while preserving a
|
|
9
|
+
# read-only image root filesystem.
|
|
10
|
+
install -m 644 /etc/agent/gateway-key.pub "$AUTHORIZED_KEYS_DIR/authorized_keys"
|
|
11
|
+
|
|
12
|
+
HOST_KEY_DIR=/etc/ssh/keys
|
|
13
|
+
mkdir -p "$HOST_KEY_DIR"
|
|
14
|
+
if [ ! -f "$HOST_KEY_DIR/ssh_host_ed25519_key" ]; then
|
|
15
|
+
echo "[entrypoint] Generating gateway SSH host key ..." >&2
|
|
16
|
+
ssh-keygen -q -t ed25519 -f "$HOST_KEY_DIR/ssh_host_ed25519_key" -N ""
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
source /usr/local/lib/agent/egress-allowlist.sh
|
|
20
|
+
configure_egress_allowlist
|
|
21
|
+
|
|
22
|
+
if [ -n "${CLOUDFLARE_TUNNEL_TOKEN:-}" ]; then
|
|
23
|
+
echo "[entrypoint] Starting cloudflared tunnel (no inbound port needed)." >&2
|
|
24
|
+
cloudflared tunnel run --token "$CLOUDFLARE_TUNNEL_TOKEN" &
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
mkdir -p /run/sshd
|
|
28
|
+
exec /usr/sbin/sshd -D -e
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Port 2222
|
|
2
|
+
HostKey /etc/ssh/keys/ssh_host_ed25519_key
|
|
3
|
+
PermitRootLogin no
|
|
4
|
+
PasswordAuthentication no
|
|
5
|
+
KbdInteractiveAuthentication no
|
|
6
|
+
AllowUsers tunnel
|
|
7
|
+
AuthorizedKeysFile /run/agent-gateway/authorized_keys
|
|
8
|
+
AllowTcpForwarding yes
|
|
9
|
+
X11Forwarding no
|
|
10
|
+
AllowAgentForwarding no
|
|
11
|
+
PrintMotd no
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
FROM python:3.12-slim-trixie AS base
|
|
2
|
+
|
|
3
|
+
ARG TARGETARCH
|
|
4
|
+
|
|
5
|
+
# Reuse the hardened workload baseline: egress enforcement, gateway-client
|
|
6
|
+
# support, and the non-root handoff. Node.js and uv remain available for
|
|
7
|
+
# project tooling and optional user-selected packages.
|
|
8
|
+
COPY --from=shared install-workload-base.sh /usr/local/lib/agent/install-workload-base.sh
|
|
9
|
+
RUN TARGETARCH="$TARGETARCH" bash /usr/local/lib/agent/install-workload-base.sh
|
|
10
|
+
|
|
11
|
+
COPY --from=shared install-additional-packages.sh /usr/local/lib/agent/install-additional-packages.sh
|
|
12
|
+
COPY packages-apt.txt /tmp/packages-apt.txt
|
|
13
|
+
RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
|
|
14
|
+
&& install_apt_packages /tmp/packages-apt.txt' \
|
|
15
|
+
&& rm -f /tmp/packages-apt.txt
|
|
16
|
+
|
|
17
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
|
|
18
|
+
|
|
19
|
+
# packages-uv.txt — general Python libraries (not CLI tools; see
|
|
20
|
+
# tools-uv.txt below), installed via `uv pip install --system` into this
|
|
21
|
+
# system Python. Runs as root, before the aider user exists, since it
|
|
22
|
+
# writes into the image's system site-packages rather than $HOME.
|
|
23
|
+
COPY packages-uv.txt /tmp/packages-uv.txt
|
|
24
|
+
RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
|
|
25
|
+
&& install_uv_packages /tmp/packages-uv.txt' \
|
|
26
|
+
&& rm -f /tmp/packages-uv.txt
|
|
27
|
+
|
|
28
|
+
# Aider 0.86.2 supports Python 3.10-3.12. Pin the CLI so a rebuild does not
|
|
29
|
+
# silently change the agent or its LLM-provider dependency graph. The default
|
|
30
|
+
# package deliberately excludes browser/Playwright extras: installing browser
|
|
31
|
+
# binaries at runtime is incompatible with the read-only-rootfs profile.
|
|
32
|
+
RUN python -m pip install --no-cache-dir 'aider-chat==0.86.2'
|
|
33
|
+
|
|
34
|
+
# The aider account is created here, at a fixed placeholder UID/GID
|
|
35
|
+
# (1000:1000), purely so the root-only installers below (npm, uv tool) have
|
|
36
|
+
# a real non-root account to run as. `final` remaps this account to the
|
|
37
|
+
# operator's actual host UID/GID instead of recreating it, so a rebuild for
|
|
38
|
+
# a different host user reuses everything installed below unchanged rather
|
|
39
|
+
# than reinstalling it over the network.
|
|
40
|
+
RUN groupadd -g 1000 aider \
|
|
41
|
+
&& useradd -m -u 1000 -g 1000 -s /bin/bash aider \
|
|
42
|
+
&& mkdir -p /workspace \
|
|
43
|
+
&& chown -R aider:aider /home/aider /workspace
|
|
44
|
+
|
|
45
|
+
ENV HOME=/home/aider
|
|
46
|
+
ENV npm_config_prefix=/home/aider/.npm-global
|
|
47
|
+
ENV PATH="/home/aider/.local/bin:/home/aider/.npm-global/bin:${PATH}"
|
|
48
|
+
|
|
49
|
+
# $HOME (uv's default cache location) and /workspace are deliberately
|
|
50
|
+
# separate mounts in this containment model, so uv's hardlink/reflink
|
|
51
|
+
# optimisation can never apply between them — declare the copy fallback
|
|
52
|
+
# explicit rather than have uv warn about it on every install.
|
|
53
|
+
ENV UV_LINK_MODE=copy
|
|
54
|
+
|
|
55
|
+
# Aider would otherwise check for updates at startup and may offer to install
|
|
56
|
+
# Playwright dynamically. Both expand egress/write requirements without being
|
|
57
|
+
# necessary for the core terminal workflow. Analytics is disabled explicitly,
|
|
58
|
+
# rather than relying on Aider's prompt-driven opt-in behavior.
|
|
59
|
+
ENV AIDER_CHECK_UPDATE=false
|
|
60
|
+
ENV AIDER_ANALYTICS=false
|
|
61
|
+
ENV AIDER_DISABLE_PLAYWRIGHT=true
|
|
62
|
+
|
|
63
|
+
COPY packages-npm.txt /tmp/packages-npm.txt
|
|
64
|
+
COPY tools-uv.txt /tmp/tools-uv.txt
|
|
65
|
+
RUN gosu aider bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
|
|
66
|
+
&& install_npm_packages /tmp/packages-npm.txt \
|
|
67
|
+
&& install_uv_tools /tmp/tools-uv.txt' \
|
|
68
|
+
&& rm -f /tmp/packages-npm.txt /tmp/tools-uv.txt
|
|
69
|
+
|
|
70
|
+
# Everything above this line is UID/GID-independent and layer-cacheable
|
|
71
|
+
# across hosts: the aider account exists at the fixed placeholder UID/GID
|
|
72
|
+
# only so the installers above have a non-root account to run as. From here
|
|
73
|
+
# down, `final` remaps that account to the operator's actual host UID/GID
|
|
74
|
+
# with --build-arg UID=$(id -u) --build-arg GID=$(id -g) — the base stage
|
|
75
|
+
# above is reused unchanged from cache, and nothing installed above is
|
|
76
|
+
# reinstalled, rather than rebuilding per host user.
|
|
77
|
+
FROM base AS final
|
|
78
|
+
ARG UID=1000
|
|
79
|
+
ARG GID=1000
|
|
80
|
+
|
|
81
|
+
# Only remap when the caller supplied a different UID/GID than the fixed
|
|
82
|
+
# placeholder baked into `base` above — usermod/groupmod plus a chown -R
|
|
83
|
+
# walk over already-installed content is far cheaper than rerunning the
|
|
84
|
+
# npm/uv-tool installs above, but it's skippable entirely for the common
|
|
85
|
+
# case where the caller never overrides UID/GID. Deliberately no -o:
|
|
86
|
+
# without it, usermod/groupmod refuse to reuse an ID already assigned to
|
|
87
|
+
# another account, so a caller can never remap aider onto UID/GID 0 (root)
|
|
88
|
+
# or collide it with another in-image identity — the build fails instead of
|
|
89
|
+
# silently succeeding.
|
|
90
|
+
RUN if [ "$UID" != "1000" ] || [ "$GID" != "1000" ]; then \
|
|
91
|
+
groupmod -g "$GID" aider \
|
|
92
|
+
&& usermod -u "$UID" -g "$GID" aider \
|
|
93
|
+
&& chown -R "$UID":"$GID" /home/aider /workspace; \
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
COPY --from=shared egress-allowlist.sh /usr/local/lib/agent/egress-allowlist.sh
|
|
97
|
+
COPY --from=shared workload-entrypoint.sh /usr/local/lib/agent/workload-entrypoint.sh
|
|
98
|
+
RUN chmod +x /usr/local/lib/agent/workload-entrypoint.sh
|
|
99
|
+
|
|
100
|
+
# Strip setuid/setgid bits from the base image and every package installed
|
|
101
|
+
# above — required infra (install-workload-base.sh), packages-apt.txt,
|
|
102
|
+
# packages-npm.txt, packages-uv.txt, and tools-uv.txt — run last so nothing
|
|
103
|
+
# installed by any of those is missed. NoNewPrivs=1 (set by every documented
|
|
104
|
+
# run invocation) already makes these bits inert on execve, but that's an
|
|
105
|
+
# argument for stripping them, not against: if any single control ever
|
|
106
|
+
# regresses, a setuid-root `mount` becomes a live escalation primitive. None
|
|
107
|
+
# of these binaries are needed at runtime, so none should ship
|
|
108
|
+
# exploitable-shaped.
|
|
109
|
+
RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} + || true
|
|
110
|
+
|
|
111
|
+
WORKDIR /workspace
|
|
112
|
+
|
|
113
|
+
ENTRYPOINT ["/usr/local/lib/agent/workload-entrypoint.sh", "aider", "aider"]
|
|
114
|
+
CMD ["aider"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Hosts/IPs the container is allowed to reach outbound, one per line.
|
|
2
|
+
# Blank lines and lines starting with '#' are ignored.
|
|
3
|
+
#
|
|
4
|
+
# Accepted forms of each entry:
|
|
5
|
+
# example.com Hostname (resolved via dnsmasq, both A/AAAA)
|
|
6
|
+
# 203.0.113.10 IPv4 address or CIDR (e.g. 203.0.113.0/24)
|
|
7
|
+
# 2001:db8::1 IPv6 address or CIDR
|
|
8
|
+
#
|
|
9
|
+
# Mount this file to /etc/agent/egress-allowlist.txt in the container.
|
|
10
|
+
# A file mount here takes precedence over $AGENT_ALLOWED_EGRESS.
|
|
11
|
+
# If neither is set, all outbound traffic is denied by default.
|
|
12
|
+
#
|
|
13
|
+
# This is only an example. Retain just the API provider(s) you selected and
|
|
14
|
+
# add a source-control, package-registry, or MCP host only when you need it.
|
|
15
|
+
api.anthropic.com
|
|
16
|
+
api.openai.com
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Default/optional apt packages installed at build time, one per line.
|
|
2
|
+
# Blank lines and lines starting with '#' are ignored. Edit freely and
|
|
3
|
+
# rebuild the image — this is separate from the Dockerfile's own
|
|
4
|
+
# required-infra apt list (git, iptables, gosu, etc.), which this file
|
|
5
|
+
# cannot remove packages from.
|
|
6
|
+
gh
|
|
7
|
+
jq
|
|
8
|
+
ripgrep
|
|
9
|
+
fd-find
|
|
10
|
+
tree
|
|
11
|
+
unzip
|
|
12
|
+
less
|
|
13
|
+
file
|
|
14
|
+
lsof
|
|
15
|
+
yq
|
|
16
|
+
miller
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Optional global npm packages installed at build time (via `npm install -g`),
|
|
2
|
+
# one per line. Blank lines and lines starting with '#' are ignored. Empty by
|
|
3
|
+
# default — there's no "everyone wants this" global npm CLI the way there is
|
|
4
|
+
# for apt utilities.
|
|
5
|
+
#
|
|
6
|
+
# Example:
|
|
7
|
+
# @some-scope/some-cli
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Optional Python libraries installed into the system Python at build time
|
|
2
|
+
# (via `uv pip install --system`), one per line. Blank lines and lines
|
|
3
|
+
# starting with '#' are ignored. Empty by default. Unlike tools-uv.txt, these
|
|
4
|
+
# land in the one shared system Python rather than an isolated venv, so
|
|
5
|
+
# they're importable by whatever runs inside the image — a CLI-only package
|
|
6
|
+
# with no importable module of its own belongs in tools-uv.txt instead.
|
|
7
|
+
#
|
|
8
|
+
# Example:
|
|
9
|
+
# langfuse
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Optional Python CLI tools installed at build time (via `uv tool install`),
|
|
2
|
+
# one per line, each into its own isolated venv. Blank lines and lines
|
|
3
|
+
# starting with '#' are ignored. Empty by default — there's no "everyone
|
|
4
|
+
# wants this" uv-installed CLI the way there is for apt utilities. A plain
|
|
5
|
+
# importable library (no console-script entry point of its own) belongs in
|
|
6
|
+
# packages-uv.txt instead.
|
|
7
|
+
#
|
|
8
|
+
# Example:
|
|
9
|
+
# ruff
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
FROM python:3.14-slim-trixie AS base
|
|
2
|
+
|
|
3
|
+
ARG TARGETARCH
|
|
4
|
+
|
|
5
|
+
# Shared Node.js, containment, gateway-client, and cloudflared baseline for
|
|
6
|
+
# the first-party Python-and-Node workload images.
|
|
7
|
+
COPY --from=shared install-workload-base.sh /usr/local/lib/agent/install-workload-base.sh
|
|
8
|
+
RUN TARGETARCH="$TARGETARCH" bash /usr/local/lib/agent/install-workload-base.sh
|
|
9
|
+
|
|
10
|
+
# Optional profile-managed organisational CAs. The canonical context carries
|
|
11
|
+
# only a marker; onboarding replaces this directory with the selected file or
|
|
12
|
+
# certificate directory before building. The merged system bundle is then
|
|
13
|
+
# available to curl/git/OpenSSL clients on the read-only runtime rootfs.
|
|
14
|
+
COPY proxy-ca.d/ /tmp/agent-containers-proxy-ca/
|
|
15
|
+
RUN set -eu; \
|
|
16
|
+
mkdir -p /usr/local/share/ca-certificates/custom; \
|
|
17
|
+
found=0; \
|
|
18
|
+
for cert in /tmp/agent-containers-proxy-ca/*; do \
|
|
19
|
+
[ -f "$cert" ] || continue; \
|
|
20
|
+
name="$(basename "$cert")"; \
|
|
21
|
+
[ "$name" = ".keep" ] && continue; \
|
|
22
|
+
case "$name" in *.crt) ;; *) name="${name%.*}.crt" ;; esac; \
|
|
23
|
+
cp "$cert" "/usr/local/share/ca-certificates/custom/$name"; \
|
|
24
|
+
found=1; \
|
|
25
|
+
done; \
|
|
26
|
+
if [ "$found" -eq 1 ]; then update-ca-certificates; fi; \
|
|
27
|
+
rm -rf /tmp/agent-containers-proxy-ca
|
|
28
|
+
|
|
29
|
+
# User-configurable default software (gh, jq, ripgrep, ...) — see
|
|
30
|
+
# packages-apt.txt. Kept as a separate RUN from the baseline block above so
|
|
31
|
+
# the required-infra list stays untouched by editing that file.
|
|
32
|
+
COPY --from=shared install-additional-packages.sh /usr/local/lib/agent/install-additional-packages.sh
|
|
33
|
+
COPY packages-apt.txt /tmp/packages-apt.txt
|
|
34
|
+
RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
|
|
35
|
+
&& install_apt_packages /tmp/packages-apt.txt' \
|
|
36
|
+
&& rm -f /tmp/packages-apt.txt
|
|
37
|
+
|
|
38
|
+
# uv/uvx (Python package & venv manager), copied in rather than switching
|
|
39
|
+
# the base image — uv manages venvs against this image's existing system
|
|
40
|
+
# Python just as well as against a uv-provisioned one, without disturbing
|
|
41
|
+
# the apt/Node.js setup above.
|
|
42
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
|
|
43
|
+
|
|
44
|
+
# packages-uv.txt — general Python libraries (not CLI tools; see
|
|
45
|
+
# tools-uv.txt below), installed via `uv pip install --system` into this
|
|
46
|
+
# system Python. Runs as root, before the claude user exists, since it
|
|
47
|
+
# writes into the image's system site-packages rather than $HOME.
|
|
48
|
+
COPY packages-uv.txt /tmp/packages-uv.txt
|
|
49
|
+
RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
|
|
50
|
+
&& install_uv_packages /tmp/packages-uv.txt' \
|
|
51
|
+
&& rm -f /tmp/packages-uv.txt
|
|
52
|
+
|
|
53
|
+
RUN npm install -g @anthropic-ai/claude-code
|
|
54
|
+
|
|
55
|
+
# The claude account is created here, at a fixed placeholder UID/GID
|
|
56
|
+
# (1000:1000), purely so the root-only installers below (npm, uv tool,
|
|
57
|
+
# claude plugin) have a real non-root account to run as. `final` remaps
|
|
58
|
+
# this account to the operator's actual host UID/GID instead of recreating
|
|
59
|
+
# it, so a rebuild for a different host user reuses everything installed
|
|
60
|
+
# below unchanged rather than reinstalling it over the network.
|
|
61
|
+
RUN groupadd -g 1000 claude \
|
|
62
|
+
&& useradd -m -u 1000 -g 1000 -s /bin/bash claude \
|
|
63
|
+
&& mkdir -p /home/claude/.claude /workspace \
|
|
64
|
+
&& chown -R claude:claude /home/claude /workspace
|
|
65
|
+
|
|
66
|
+
# gosu switches UID/GID but, unlike a login shell, does not reset $HOME —
|
|
67
|
+
# without this, plugin install (below) and the runtime `claude` process would
|
|
68
|
+
# both try to use root's $HOME instead of /home/claude.
|
|
69
|
+
ENV HOME=/home/claude
|
|
70
|
+
|
|
71
|
+
# npm's default global-install prefix (/usr/local) sits on the read-only
|
|
72
|
+
# rootfs at runtime; repoint it to a location under the claude user's home
|
|
73
|
+
# so `npm install -g` works there instead. This doesn't affect the
|
|
74
|
+
# `npm install -g @anthropic-ai/claude-code` above, which already ran as
|
|
75
|
+
# root before this user/prefix existed. Set via ENV (not `npm config set`,
|
|
76
|
+
# which would only write ~/.npmrc into the image — that file wouldn't reach
|
|
77
|
+
# a volume that already has content the first time it's mounted here, since
|
|
78
|
+
# Docker's volume copy-up only seeds genuinely empty volumes) so the prefix
|
|
79
|
+
# applies regardless of what's already in the mounted $HOME volume.
|
|
80
|
+
ENV npm_config_prefix=/home/claude/.npm-global
|
|
81
|
+
# Home-managed installations intentionally precede profile-managed tools. The
|
|
82
|
+
# latter are image-owned under /opt so a populated home volume cannot hide a
|
|
83
|
+
# rebuilt profile image's requested versions.
|
|
84
|
+
ENV PATH="/home/claude/.local/bin:/home/claude/.npm-global/bin:/opt/agent-tools/bin:/opt/agent-tools/npm/bin:${PATH}"
|
|
85
|
+
|
|
86
|
+
# $HOME (uv's default cache location) and /workspace are deliberately
|
|
87
|
+
# separate mounts in this containment model, so uv's hardlink/reflink
|
|
88
|
+
# optimisation can never apply between them — declare the copy fallback
|
|
89
|
+
# explicit rather than have uv warn about it on every install.
|
|
90
|
+
ENV UV_LINK_MODE=copy
|
|
91
|
+
|
|
92
|
+
# packages-npm.txt / tools-uv.txt — see packages-apt.txt above. Profile-managed
|
|
93
|
+
# tools live outside the persistent home, so changing an image changes the
|
|
94
|
+
# visible managed version even when a populated home volume is mounted.
|
|
95
|
+
COPY packages-npm.txt /tmp/packages-npm.txt
|
|
96
|
+
COPY tools-uv.txt /tmp/tools-uv.txt
|
|
97
|
+
RUN mkdir -p /opt/agent-tools/npm /opt/agent-tools/uv-tools /opt/agent-tools/bin \
|
|
98
|
+
&& chown -R claude:claude /opt/agent-tools \
|
|
99
|
+
&& gosu claude env \
|
|
100
|
+
npm_config_prefix=/opt/agent-tools/npm \
|
|
101
|
+
UV_TOOL_DIR=/opt/agent-tools/uv-tools \
|
|
102
|
+
UV_TOOL_BIN_DIR=/opt/agent-tools/bin \
|
|
103
|
+
bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
|
|
104
|
+
&& install_npm_packages /tmp/packages-npm.txt \
|
|
105
|
+
&& install_uv_tools /tmp/tools-uv.txt' \
|
|
106
|
+
&& rm -f /tmp/packages-npm.txt /tmp/tools-uv.txt
|
|
107
|
+
|
|
108
|
+
COPY plugin-marketplaces.txt /tmp/plugin-marketplaces.txt
|
|
109
|
+
COPY plugins.txt /tmp/plugins.txt
|
|
110
|
+
|
|
111
|
+
# The official marketplace is always available; plugin-marketplaces.txt lists
|
|
112
|
+
# any additional (non-official) marketplaces to add before installing plugins,
|
|
113
|
+
# since plugins.txt entries may reference any of them via "<plugin>@<marketplace>".
|
|
114
|
+
RUN gosu claude claude plugin marketplace add anthropics/claude-plugins-official \
|
|
115
|
+
&& while IFS= read -r marketplace; do \
|
|
116
|
+
case "$marketplace" in ''|'#'*) continue ;; esac; \
|
|
117
|
+
gosu claude claude plugin marketplace add "${marketplace}"; \
|
|
118
|
+
done < /tmp/plugin-marketplaces.txt \
|
|
119
|
+
&& while IFS= read -r plugin; do \
|
|
120
|
+
case "$plugin" in ''|'#'*) continue ;; esac; \
|
|
121
|
+
gosu claude claude plugin install "${plugin}" --scope user; \
|
|
122
|
+
done < /tmp/plugins.txt \
|
|
123
|
+
&& rm /tmp/plugin-marketplaces.txt /tmp/plugins.txt
|
|
124
|
+
|
|
125
|
+
# Everything above this line is UID/GID-independent and layer-cacheable
|
|
126
|
+
# across hosts: the claude account exists at the fixed placeholder UID/GID
|
|
127
|
+
# only so the installers above have a non-root account to run as. From here
|
|
128
|
+
# down, `final` remaps that account to the operator's actual host UID/GID
|
|
129
|
+
# with --build-arg UID=$(id -u) --build-arg GID=$(id -g) — the base stage
|
|
130
|
+
# above is reused unchanged from cache, and nothing installed above is
|
|
131
|
+
# reinstalled, rather than rebuilding per host user.
|
|
132
|
+
FROM base AS final
|
|
133
|
+
ARG UID=1000
|
|
134
|
+
ARG GID=1000
|
|
135
|
+
|
|
136
|
+
# Only remap when the caller supplied a different UID/GID than the fixed
|
|
137
|
+
# placeholder baked into `base` above — usermod/groupmod plus a chown -R
|
|
138
|
+
# walk over already-installed content is far cheaper than rerunning the
|
|
139
|
+
# npm/uv-tool/plugin installs above, but it's skippable entirely for the
|
|
140
|
+
# common case where the caller never overrides UID/GID. Deliberately no -o:
|
|
141
|
+
# without it, usermod/groupmod refuse to reuse an ID already assigned to
|
|
142
|
+
# another account, so a caller can never remap claude onto UID/GID 0 (root)
|
|
143
|
+
# or collide it with another in-image identity — the build fails instead of
|
|
144
|
+
# silently succeeding.
|
|
145
|
+
RUN if [ "$UID" != "1000" ] || [ "$GID" != "1000" ]; then \
|
|
146
|
+
groupmod -g "$GID" claude \
|
|
147
|
+
&& usermod -u "$UID" -g "$GID" claude \
|
|
148
|
+
&& chown -R "$UID":"$GID" /home/claude /workspace /opt/agent-tools; \
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
COPY --from=shared egress-allowlist.sh /usr/local/lib/agent/egress-allowlist.sh
|
|
152
|
+
COPY --from=shared workload-entrypoint.sh /usr/local/lib/agent/workload-entrypoint.sh
|
|
153
|
+
|
|
154
|
+
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
155
|
+
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/lib/agent/workload-entrypoint.sh
|
|
156
|
+
|
|
157
|
+
# Strip setuid/setgid bits from the base image and every package installed
|
|
158
|
+
# above — required infra (install-workload-base.sh), packages-apt.txt,
|
|
159
|
+
# packages-npm.txt, packages-uv.txt, tools-uv.txt, and installed plugins —
|
|
160
|
+
# run last so nothing installed by any of those is missed. NoNewPrivs=1 (set
|
|
161
|
+
# by every documented run invocation) already makes these bits inert on
|
|
162
|
+
# execve, but that's an argument for stripping them, not against: if any
|
|
163
|
+
# single control ever regresses, a setuid-root `mount` becomes a live
|
|
164
|
+
# escalation primitive. None of these binaries are needed at runtime, so
|
|
165
|
+
# none should ship exploitable-shaped.
|
|
166
|
+
RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} + || true
|
|
167
|
+
|
|
168
|
+
WORKDIR /workspace
|
|
169
|
+
|
|
170
|
+
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
171
|
+
CMD ["claude"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Older images mounted the persistent volume directly at /home/claude/.claude;
|
|
5
|
+
# it's now mounted at /home/claude so uv/npm state under $HOME persists too.
|
|
6
|
+
# A volume from before this change has its old .claude contents sitting at
|
|
7
|
+
# its own root, so /home/claude/.claude won't exist yet — nest them one
|
|
8
|
+
# level down to match where Claude Code still expects them. Both the check
|
|
9
|
+
# and the action run as the claude user (via gosu): /home/claude is
|
|
10
|
+
# 0700-owned by claude, and root lacks CAP_DAC_OVERRIDE under --cap-drop=ALL,
|
|
11
|
+
# so root can't even stat into it — checking as root would misreport
|
|
12
|
+
# "missing" on every single start (not just genuinely legacy volumes) and
|
|
13
|
+
# re-run the sweep repeatedly, corrupting a volume that's already migrated.
|
|
14
|
+
if ! gosu claude test -d /home/claude/.claude; then
|
|
15
|
+
echo "[entrypoint] Migrating legacy claude-home volume layout into ~/.claude ..." >&2
|
|
16
|
+
gosu claude mkdir -p /home/claude/.claude
|
|
17
|
+
gosu claude find /home/claude -mindepth 1 -maxdepth 1 ! -name .claude -exec mv -t /home/claude/.claude -- {} +
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Claude-specific state migration stays here; all containment setup and the
|
|
21
|
+
# privilege drop are shared with the other first-party Node.js workload images.
|
|
22
|
+
exec /usr/local/lib/agent/workload-entrypoint.sh claude claude "$@"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Hosts/IPs the container is allowed to reach outbound, one per line.
|
|
2
|
+
# Blank lines and lines starting with '#' are ignored.
|
|
3
|
+
#
|
|
4
|
+
# Accepted forms of each entry:
|
|
5
|
+
# example.com Hostname (resolved via dnsmasq, both A/AAAA)
|
|
6
|
+
# 203.0.113.10 IPv4 address or CIDR (e.g. 203.0.113.0/24)
|
|
7
|
+
# 2001:db8::1 IPv6 address or CIDR
|
|
8
|
+
#
|
|
9
|
+
# Mount this file to /etc/agent/egress-allowlist.txt in the container.
|
|
10
|
+
# A file mount here takes precedence over $AGENT_ALLOWED_EGRESS.
|
|
11
|
+
# If neither is set, all outbound traffic is denied by default.
|
|
12
|
+
#
|
|
13
|
+
# Example:
|
|
14
|
+
api.anthropic.com
|