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.
Files changed (86) hide show
  1. agent_containers/__init__.py +1 -0
  2. agent_containers/__main__.py +6 -0
  3. agent_containers/_assets/agent-images/adal/Dockerfile +103 -0
  4. agent_containers/_assets/agent-images/adal/examples/egress-allowlist.txt +10 -0
  5. agent_containers/_assets/agent-images/adal/packages-apt.txt +13 -0
  6. agent_containers/_assets/agent-images/adal/packages-npm.txt +2 -0
  7. agent_containers/_assets/agent-images/adal/packages-uv.txt +6 -0
  8. agent_containers/_assets/agent-images/adal/tools-uv.txt +4 -0
  9. agent_containers/_assets/agent-images/agent-gateway/Dockerfile +50 -0
  10. agent_containers/_assets/agent-images/agent-gateway/entrypoint.sh +28 -0
  11. agent_containers/_assets/agent-images/agent-gateway/sshd_config.agent-gateway +11 -0
  12. agent_containers/_assets/agent-images/aider/Dockerfile +114 -0
  13. agent_containers/_assets/agent-images/aider/examples/egress-allowlist.txt +16 -0
  14. agent_containers/_assets/agent-images/aider/packages-apt.txt +16 -0
  15. agent_containers/_assets/agent-images/aider/packages-npm.txt +7 -0
  16. agent_containers/_assets/agent-images/aider/packages-uv.txt +9 -0
  17. agent_containers/_assets/agent-images/aider/tools-uv.txt +9 -0
  18. agent_containers/_assets/agent-images/claude-code/Dockerfile +171 -0
  19. agent_containers/_assets/agent-images/claude-code/entrypoint.sh +22 -0
  20. agent_containers/_assets/agent-images/claude-code/examples/egress-allowlist.txt +14 -0
  21. agent_containers/_assets/agent-images/claude-code/examples/settings.local-model.json +7 -0
  22. agent_containers/_assets/agent-images/claude-code/packages-apt.txt +16 -0
  23. agent_containers/_assets/agent-images/claude-code/packages-npm.txt +7 -0
  24. agent_containers/_assets/agent-images/claude-code/packages-uv.txt +9 -0
  25. agent_containers/_assets/agent-images/claude-code/plugin-marketplaces.txt +23 -0
  26. agent_containers/_assets/agent-images/claude-code/plugins.txt +13 -0
  27. agent_containers/_assets/agent-images/claude-code/proxy-ca.d/.keep +1 -0
  28. agent_containers/_assets/agent-images/claude-code/tools-uv.txt +9 -0
  29. agent_containers/_assets/agent-images/codex/Dockerfile +147 -0
  30. agent_containers/_assets/agent-images/codex/codex-wrapper.sh +13 -0
  31. agent_containers/_assets/agent-images/codex/examples/egress-allowlist.txt +10 -0
  32. agent_containers/_assets/agent-images/codex/packages-apt.txt +13 -0
  33. agent_containers/_assets/agent-images/codex/packages-npm.txt +2 -0
  34. agent_containers/_assets/agent-images/codex/packages-uv.txt +6 -0
  35. agent_containers/_assets/agent-images/codex/plugin-marketplaces.txt +2 -0
  36. agent_containers/_assets/agent-images/codex/plugins.txt +2 -0
  37. agent_containers/_assets/agent-images/codex/proxy-ca.d/.keep +1 -0
  38. agent_containers/_assets/agent-images/codex/tools-uv.txt +4 -0
  39. agent_containers/_assets/agent-images/hermes/Dockerfile +122 -0
  40. agent_containers/_assets/agent-images/hermes/entrypoint.sh +49 -0
  41. agent_containers/_assets/agent-images/hermes/packages-apt.txt +16 -0
  42. agent_containers/_assets/agent-images/hermes/packages-npm.txt +7 -0
  43. agent_containers/_assets/agent-images/hermes/packages-uv.txt +9 -0
  44. agent_containers/_assets/agent-images/hermes/proxy-ca.d/.keep +1 -0
  45. agent_containers/_assets/agent-images/hermes/tools-uv.txt +9 -0
  46. agent_containers/_assets/agent-images/kilo-code/Dockerfile +101 -0
  47. agent_containers/_assets/agent-images/kilo-code/examples/egress-allowlist.txt +7 -0
  48. agent_containers/_assets/agent-images/kilo-code/packages-apt.txt +5 -0
  49. agent_containers/_assets/agent-images/kilo-code/packages-npm.txt +1 -0
  50. agent_containers/_assets/agent-images/kilo-code/packages-uv.txt +4 -0
  51. agent_containers/_assets/agent-images/kilo-code/tools-uv.txt +4 -0
  52. agent_containers/_assets/agent-images/opencode/Dockerfile +126 -0
  53. agent_containers/_assets/agent-images/opencode/examples/egress-allowlist.txt +6 -0
  54. agent_containers/_assets/agent-images/opencode/packages-apt.txt +13 -0
  55. agent_containers/_assets/agent-images/opencode/packages-npm.txt +2 -0
  56. agent_containers/_assets/agent-images/opencode/packages-uv.txt +6 -0
  57. agent_containers/_assets/agent-images/opencode/proxy-ca.d/.keep +1 -0
  58. agent_containers/_assets/agent-images/opencode/tools-uv.txt +4 -0
  59. agent_containers/_assets/agent-images/qwen-code/Dockerfile +104 -0
  60. agent_containers/_assets/agent-images/qwen-code/examples/egress-allowlist.txt +10 -0
  61. agent_containers/_assets/agent-images/qwen-code/packages-apt.txt +13 -0
  62. agent_containers/_assets/agent-images/qwen-code/packages-npm.txt +2 -0
  63. agent_containers/_assets/agent-images/qwen-code/packages-uv.txt +6 -0
  64. agent_containers/_assets/agent-images/qwen-code/tools-uv.txt +4 -0
  65. agent_containers/_assets/agent-images/shared/egress-allowlist.sh +138 -0
  66. agent_containers/_assets/agent-images/shared/install-additional-packages.sh +79 -0
  67. agent_containers/_assets/agent-images/shared/install-workload-base.sh +46 -0
  68. agent_containers/_assets/agent-images/shared/workload-entrypoint.sh +82 -0
  69. agent_containers/_assets/agent-images/volume-bridge/Dockerfile +37 -0
  70. agent_containers/_assets/agent-images/volume-bridge/entrypoint.sh +29 -0
  71. agent_containers/_assets/agent-images/volume-bridge/serve.sh +66 -0
  72. agent_containers/assets.py +38 -0
  73. agent_containers/build_context.py +116 -0
  74. agent_containers/cli.py +212 -0
  75. agent_containers/creation.py +177 -0
  76. agent_containers/docker.py +562 -0
  77. agent_containers/lifecycle.py +325 -0
  78. agent_containers/planner.py +99 -0
  79. agent_containers/profile.py +377 -0
  80. agent_containers/shortcuts.py +159 -0
  81. agent_containers/state.py +154 -0
  82. agent_containers-0.1.0.dev0.dist-info/METADATA +195 -0
  83. agent_containers-0.1.0.dev0.dist-info/RECORD +86 -0
  84. agent_containers-0.1.0.dev0.dist-info/WHEEL +4 -0
  85. agent_containers-0.1.0.dev0.dist-info/entry_points.txt +3 -0
  86. agent_containers-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
@@ -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,23 @@
1
+ # Additional plugin marketplaces to add at build time, one per line.
2
+ # Blank lines and lines starting with '#' are ignored.
3
+ #
4
+ # Each line is passed as-is to `claude plugin marketplace add <source>`.
5
+ # Accepted forms of <source>:
6
+ # owner/repo GitHub shorthand
7
+ # owner/repo@branch-or-tag GitHub shorthand, pinned to a ref
8
+ # https://git-host/team/plugins.git Any git URL
9
+ # https://git-host/team/plugins.git#ref Git URL, pinned to a ref
10
+ # https://example.com/marketplace.json Raw marketplace.json URL
11
+ # /absolute/or/./relative/path Local filesystem path
12
+ #
13
+ # The official Anthropic marketplace (claude-plugins-official) is added
14
+ # automatically and must not be listed here.
15
+ #
16
+ # NOTE: the name used in plugins.txt as "<plugin>@<marketplace-name>" is
17
+ # whatever that marketplace's own .claude-plugin/marketplace.json declares as
18
+ # its "name" field -- it is NOT necessarily derived from the owner/repo slug
19
+ # or URL above. Check the marketplace's manifest (or its docs) to find the
20
+ # right suffix to use in plugins.txt.
21
+ #
22
+ # Example:
23
+ # my-org/my-claude-marketplace
@@ -0,0 +1,13 @@
1
+ clangd-lsp@claude-plugins-official
2
+ claude-md-management@claude-plugins-official
3
+ code-review@claude-plugins-official
4
+ gopls-lsp@claude-plugins-official
5
+ playground@claude-plugins-official
6
+ playwright@claude-plugins-official
7
+ pydantic-ai@claude-plugins-official
8
+ remember@claude-plugins-official
9
+ rust-analyzer-lsp@claude-plugins-official
10
+ security-guidance@claude-plugins-official
11
+ skill-creator@claude-plugins-official
12
+ superpowers@claude-plugins-official
13
+ typescript-lsp@claude-plugins-official
@@ -0,0 +1 @@
1
+ # Profile-specific CA certificates are injected into this directory by the onboarding CLI.
@@ -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,147 @@
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; see claude-code/Dockerfile.
11
+ COPY proxy-ca.d/ /tmp/agent-containers-proxy-ca/
12
+ RUN set -eu; \
13
+ mkdir -p /usr/local/share/ca-certificates/custom; \
14
+ found=0; \
15
+ for cert in /tmp/agent-containers-proxy-ca/*; do \
16
+ [ -f "$cert" ] || continue; \
17
+ name="$(basename "$cert")"; \
18
+ [ "$name" = ".keep" ] && continue; \
19
+ case "$name" in *.crt) ;; *) name="${name%.*}.crt" ;; esac; \
20
+ cp "$cert" "/usr/local/share/ca-certificates/custom/$name"; \
21
+ found=1; \
22
+ done; \
23
+ if [ "$found" -eq 1 ]; then update-ca-certificates; fi; \
24
+ rm -rf /tmp/agent-containers-proxy-ca
25
+
26
+ COPY --from=shared install-additional-packages.sh /usr/local/lib/agent/install-additional-packages.sh
27
+ COPY packages-apt.txt /tmp/packages-apt.txt
28
+ RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
29
+ && install_apt_packages /tmp/packages-apt.txt' \
30
+ && rm -f /tmp/packages-apt.txt
31
+
32
+ # Keep Python tooling available for Python-based MCP servers and project work,
33
+ # matching the other first-party CLI workload image.
34
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
35
+
36
+ # packages-uv.txt — general Python libraries (not CLI tools; see
37
+ # tools-uv.txt below), installed via `uv pip install --system` into this
38
+ # system Python. Runs as root, before the codex user exists, since it writes
39
+ # into the image's system site-packages rather than $HOME.
40
+ COPY packages-uv.txt /tmp/packages-uv.txt
41
+ RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
42
+ && install_uv_packages /tmp/packages-uv.txt' \
43
+ && rm -f /tmp/packages-uv.txt
44
+
45
+ RUN npm install -g @openai/codex \
46
+ && codex_path="$(command -v codex)" \
47
+ && codex_target="$(readlink -f "$codex_path")" \
48
+ && ln -s "$codex_target" /usr/local/bin/codex-cli \
49
+ && rm -f "$codex_path"
50
+
51
+ COPY codex-wrapper.sh /usr/local/bin/codex
52
+ RUN chmod +x /usr/local/bin/codex
53
+
54
+ # The codex account is created here, at a fixed placeholder UID/GID
55
+ # (1000:1000), purely so the root-only installers below (npm, uv tool) have
56
+ # a real non-root account to run as. `final` remaps this account to the
57
+ # operator's actual host UID/GID instead of recreating it, so a rebuild for
58
+ # a different host user reuses everything installed below unchanged rather
59
+ # than reinstalling it over the network.
60
+ RUN groupadd -g 1000 codex \
61
+ && useradd -m -u 1000 -g 1000 -s /bin/bash codex \
62
+ && mkdir -p /home/codex/.codex /workspace \
63
+ && chown -R codex:codex /home/codex /workspace
64
+
65
+ ENV HOME=/home/codex
66
+ ENV npm_config_prefix=/home/codex/.npm-global
67
+ # Home-managed installations intentionally precede profile-managed tools. The
68
+ # latter are image-owned under /opt so a populated home volume cannot hide a
69
+ # rebuilt profile image's requested versions.
70
+ ENV PATH="/home/codex/.local/bin:/home/codex/.npm-global/bin:/opt/agent-tools/bin:/opt/agent-tools/npm/bin:${PATH}"
71
+
72
+ # $HOME (uv's default cache location) and /workspace are deliberately
73
+ # separate mounts in this containment model, so uv's hardlink/reflink
74
+ # optimisation can never apply between them — declare the copy fallback
75
+ # explicit rather than have uv warn about it on every install.
76
+ ENV UV_LINK_MODE=copy
77
+
78
+ COPY packages-npm.txt /tmp/packages-npm.txt
79
+ COPY tools-uv.txt /tmp/tools-uv.txt
80
+ RUN mkdir -p /opt/agent-tools/npm /opt/agent-tools/uv-tools /opt/agent-tools/bin \
81
+ && chown -R codex:codex /opt/agent-tools \
82
+ && gosu codex env \
83
+ npm_config_prefix=/opt/agent-tools/npm \
84
+ UV_TOOL_DIR=/opt/agent-tools/uv-tools \
85
+ UV_TOOL_BIN_DIR=/opt/agent-tools/bin \
86
+ bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
87
+ && install_npm_packages /tmp/packages-npm.txt \
88
+ && install_uv_tools /tmp/tools-uv.txt' \
89
+ && rm -f /tmp/packages-npm.txt /tmp/tools-uv.txt
90
+
91
+ COPY plugin-marketplaces.txt /tmp/plugin-marketplaces.txt
92
+ COPY plugins.txt /tmp/plugins.txt
93
+ RUN while IFS= read -r marketplace; do \
94
+ case "$marketplace" in ''|'#'*) continue ;; esac; \
95
+ gosu codex codex-cli plugin marketplace add "${marketplace}"; \
96
+ done < /tmp/plugin-marketplaces.txt \
97
+ && while IFS= read -r plugin; do \
98
+ case "$plugin" in ''|'#'*) continue ;; esac; \
99
+ gosu codex codex-cli plugin add "${plugin}"; \
100
+ done < /tmp/plugins.txt \
101
+ && rm -f /tmp/plugin-marketplaces.txt /tmp/plugins.txt
102
+
103
+ # Everything above this line is UID/GID-independent and layer-cacheable
104
+ # across hosts: the codex account exists at the fixed placeholder UID/GID
105
+ # only so the installers above have a non-root account to run as. From here
106
+ # down, `final` remaps that account to the operator's actual host UID/GID
107
+ # with --build-arg UID=$(id -u) --build-arg GID=$(id -g) — the base stage
108
+ # above is reused unchanged from cache, and nothing installed above is
109
+ # reinstalled, rather than rebuilding per host user.
110
+ FROM base AS final
111
+ ARG UID=1000
112
+ ARG GID=1000
113
+
114
+ # Only remap when the caller supplied a different UID/GID than the fixed
115
+ # placeholder baked into `base` above — usermod/groupmod plus a chown -R
116
+ # walk over already-installed content is far cheaper than rerunning the
117
+ # npm/uv-tool installs above, but it's skippable entirely for the common
118
+ # case where the caller never overrides UID/GID. Deliberately no -o:
119
+ # without it, usermod/groupmod refuse to reuse an ID already assigned to
120
+ # another account, so a caller can never remap codex onto UID/GID 0 (root)
121
+ # or collide it with another in-image identity — the build fails instead of
122
+ # silently succeeding.
123
+ RUN if [ "$UID" != "1000" ] || [ "$GID" != "1000" ]; then \
124
+ groupmod -g "$GID" codex \
125
+ && usermod -u "$UID" -g "$GID" codex \
126
+ && chown -R "$UID":"$GID" /home/codex /workspace /opt/agent-tools; \
127
+ fi
128
+
129
+ COPY --from=shared egress-allowlist.sh /usr/local/lib/agent/egress-allowlist.sh
130
+ COPY --from=shared workload-entrypoint.sh /usr/local/lib/agent/workload-entrypoint.sh
131
+ RUN chmod +x /usr/local/lib/agent/workload-entrypoint.sh
132
+
133
+ # Strip setuid/setgid bits from the base image and every package installed
134
+ # above — required infra (install-workload-base.sh), packages-apt.txt,
135
+ # packages-npm.txt, packages-uv.txt, and tools-uv.txt — run last so nothing
136
+ # installed by any of those is missed. NoNewPrivs=1 (set by every documented
137
+ # run invocation) already makes these bits inert on execve, but that's an
138
+ # argument for stripping them, not against: if any single control ever
139
+ # regresses, a setuid-root `mount` becomes a live escalation primitive. None
140
+ # of these binaries are needed at runtime, so none should ship
141
+ # exploitable-shaped.
142
+ RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} + || true
143
+
144
+ WORKDIR /workspace
145
+
146
+ ENTRYPOINT ["/usr/local/lib/agent/workload-entrypoint.sh", "codex", "codex"]
147
+ CMD ["codex"]
@@ -0,0 +1,13 @@
1
+ #!/bin/sh
2
+
3
+ # The outer container provides the filesystem and process sandbox. Avoid
4
+ # starting Codex's nested sandbox, which cannot create its required namespaces
5
+ # under the container's deliberately restricted runtime security flags. Keep
6
+ # an explicit caller-selected sandbox mode intact.
7
+ for arg in "$@"; do
8
+ case "$arg" in
9
+ --sandbox|--sandbox=*|-s) exec /usr/local/bin/codex-cli "$@" ;;
10
+ esac
11
+ done
12
+
13
+ exec /usr/local/bin/codex-cli --sandbox danger-full-access "$@"
@@ -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
+ # The documented default supports ChatGPT-account authentication and the
7
+ # `codex_apps` MCP integration, both of which need all three hosts.
8
+ api.openai.com
9
+ auth.openai.com
10
+ chatgpt.com
@@ -0,0 +1,13 @@
1
+ # Optional apt packages installed at build time, one per line.
2
+ # Blank lines and lines starting with '#' are ignored.
3
+ gh
4
+ jq
5
+ ripgrep
6
+ fd-find
7
+ tree
8
+ unzip
9
+ less
10
+ file
11
+ lsof
12
+ yq
13
+ miller
@@ -0,0 +1,2 @@
1
+ # Optional global npm packages installed at build time, one per line.
2
+ # Blank lines and lines starting with '#' are ignored.
@@ -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,2 @@
1
+ # Optional profile-managed Codex plugin marketplaces.
2
+ # The onboarding CLI adds the Langfuse marketplace only when explicitly enabled.
@@ -0,0 +1,2 @@
1
+ # Optional profile-managed Codex plugins.
2
+ # The onboarding CLI adds the Langfuse tracing plugin only when explicitly enabled.
@@ -0,0 +1 @@
1
+ # Profile-specific CA certificates are injected into this directory by the onboarding CLI.
@@ -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,122 @@
1
+ FROM nousresearch/hermes-agent:latest
2
+
3
+ ARG TARGETARCH
4
+
5
+ # iptables/ipset/dnsmasq/dnsutils enforce the in-container egress allowlist;
6
+ # openssh-client/sshuttle are the gateway-client tunnel (see entrypoint.sh);
7
+ # curl fetches cloudflared below. Upstream's own Node/Python/Playwright
8
+ # toolchain and its s6-overlay boot sequence are untouched by any of this —
9
+ # it only adds the network-containment layer claude-code also has.
10
+ RUN apt-get update \
11
+ && apt-get upgrade -y \
12
+ && apt-get install -y --no-install-recommends \
13
+ ca-certificates \
14
+ curl \
15
+ iptables \
16
+ ipset \
17
+ dnsmasq \
18
+ dnsutils \
19
+ openssh-client \
20
+ sshuttle \
21
+ && rm -rf /var/lib/apt/lists/*
22
+
23
+ # Optional profile-managed organisational CAs; see claude-code/Dockerfile.
24
+ COPY proxy-ca.d/ /tmp/agent-containers-proxy-ca/
25
+ RUN set -eu; \
26
+ mkdir -p /usr/local/share/ca-certificates/custom; \
27
+ found=0; \
28
+ for cert in /tmp/agent-containers-proxy-ca/*; do \
29
+ [ -f "$cert" ] || continue; \
30
+ name="$(basename "$cert")"; \
31
+ [ "$name" = ".keep" ] && continue; \
32
+ case "$name" in *.crt) ;; *) name="${name%.*}.crt" ;; esac; \
33
+ cp "$cert" "/usr/local/share/ca-certificates/custom/$name"; \
34
+ found=1; \
35
+ done; \
36
+ if [ "$found" -eq 1 ]; then update-ca-certificates; fi; \
37
+ rm -rf /tmp/agent-containers-proxy-ca
38
+
39
+ # User-configurable default software (gh, jq, ripgrep, ...) — see
40
+ # packages-apt.txt. Kept as a separate RUN from the baseline block above so
41
+ # the required-infra list stays untouched by editing that file.
42
+ COPY --from=shared install-additional-packages.sh /usr/local/lib/agent/install-additional-packages.sh
43
+ COPY packages-apt.txt /tmp/packages-apt.txt
44
+ RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
45
+ && install_apt_packages /tmp/packages-apt.txt' \
46
+ && rm -f /tmp/packages-apt.txt
47
+
48
+ # packages-uv.txt — general Python libraries (not CLI tools; see
49
+ # tools-uv.txt below), installed via `uv pip install --system` into
50
+ # upstream's system Python (already on PATH in this base image — no
51
+ # separate uv/uvx COPY needed here, unlike claude-code). Runs as root, since
52
+ # it writes into the image's system site-packages rather than $HOME.
53
+ COPY packages-uv.txt /tmp/packages-uv.txt
54
+ RUN bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
55
+ && install_uv_packages /tmp/packages-uv.txt' \
56
+ && rm -f /tmp/packages-uv.txt
57
+
58
+ # Unlike claude-code, this image has no build-time privilege-drop tool
59
+ # installed (upstream's own runtime drop uses s6-setuidgid, not gosu) — `su`,
60
+ # already present in the base image, does the same job here without adding a
61
+ # new apt dependency. HOME/npm_config_prefix/PATH point at /opt/data
62
+ # (upstream's HERMES_HOME, already owned by the hermes user at this layer)
63
+ # so npm/uv installs below, and any further installs the hermes user makes
64
+ # at runtime, land there instead of under root's home or the read-only
65
+ # rootfs.
66
+ ENV HOME=/opt/data
67
+ ENV npm_config_prefix=/opt/data/.npm-global
68
+ # Home-managed installations intentionally precede profile-managed tools. The
69
+ # latter are image-owned under /opt so a populated data volume cannot hide a
70
+ # rebuilt profile image's requested versions.
71
+ ENV PATH="/opt/data/.local/bin:/opt/data/.npm-global/bin:/opt/agent-tools/bin:/opt/agent-tools/npm/bin:${PATH}"
72
+
73
+ # $HOME (uv's default cache location) and /workspace are deliberately
74
+ # separate mounts in this containment model, so uv's hardlink/reflink
75
+ # optimisation can never apply between them — declare the copy fallback
76
+ # explicit rather than have uv warn about it on every install.
77
+ ENV UV_LINK_MODE=copy
78
+
79
+ # packages-npm.txt / tools-uv.txt — see packages-apt.txt above. These managed
80
+ # tools are deliberately separate from /opt/data, the upstream persistent home.
81
+ COPY packages-npm.txt /tmp/packages-npm.txt
82
+ COPY tools-uv.txt /tmp/tools-uv.txt
83
+ RUN mkdir -p /opt/agent-tools/npm /opt/agent-tools/uv-tools /opt/agent-tools/bin \
84
+ && chown -R hermes:hermes /opt/agent-tools \
85
+ && su hermes -s /bin/bash -c 'export \
86
+ npm_config_prefix=/opt/agent-tools/npm \
87
+ UV_TOOL_DIR=/opt/agent-tools/uv-tools \
88
+ UV_TOOL_BIN_DIR=/opt/agent-tools/bin; \
89
+ . /usr/local/lib/agent/install-additional-packages.sh \
90
+ && install_npm_packages /tmp/packages-npm.txt \
91
+ && install_uv_tools /tmp/tools-uv.txt' \
92
+ && rm -f /tmp/packages-npm.txt /tmp/tools-uv.txt
93
+
94
+ # cloudflared is always installed but only invoked (see entrypoint.sh) when
95
+ # AGENT_GATEWAY_ACCESS_HOSTNAME is set, as an alternative to reaching the
96
+ # gateway by direct TCP — TARGETARCH is supplied automatically by Buildx and
97
+ # matches cloudflared's own release-asset naming (amd64/arm64/386/...).
98
+ RUN curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${TARGETARCH}" \
99
+ -o /usr/local/bin/cloudflared \
100
+ && chmod +x /usr/local/bin/cloudflared
101
+
102
+ COPY --from=shared egress-allowlist.sh /usr/local/lib/agent/egress-allowlist.sh
103
+
104
+ COPY entrypoint.sh /usr/local/bin/entrypoint.sh
105
+ RUN chmod +x /usr/local/bin/entrypoint.sh
106
+
107
+ # Strip setuid/setgid bits picked up from upstream's base image and the
108
+ # packages installed above (mount, passwd, su, ...). --security-opt
109
+ # no-new-privileges (every documented run invocation) already makes these
110
+ # inert on execve; stripping them removes the exploitable shape instead of
111
+ # relying solely on that flag never regressing. Safe after this point:
112
+ # upstream's runtime privilege drop is s6-setuidgid, which runs as root
113
+ # under the container's own granted CAP_SETUID/CAP_SETGID rather than
114
+ # through any setuid-bit binary.
115
+ RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} + || true
116
+
117
+ # No useradd/UID build-arg here (unlike claude-code): upstream already
118
+ # ships its own non-root user (hermes, UID 10000, home /opt/data) and its
119
+ # own privilege-drop chain; see entrypoint.sh. CMD is deliberately left at
120
+ # whatever upstream's own image sets it to, so every documented invocation
121
+ # shape keeps working unmodified.
122
+ ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ if [ -n "${AGENT_GATEWAY_HOST:-}" ]; then
5
+ echo "[entrypoint] AGENT_GATEWAY_HOST=$AGENT_GATEWAY_HOST — tunneling all egress through the gateway." >&2
6
+ if [ -n "${AGENT_ALLOWED_EGRESS:-}" ] || [ -f /etc/agent/egress-allowlist.txt ]; then
7
+ echo "[entrypoint] AGENT_ALLOWED_EGRESS/egress-allowlist.txt are ignored in gateway mode — set the allowlist on the gateway container instead." >&2
8
+ fi
9
+ if [ -n "${AGENT_GATEWAY_BOOTSTRAP_ALLOW:-}" ]; then
10
+ echo "[entrypoint] Seeding a bootstrap allow rule for ${AGENT_GATEWAY_BOOTSTRAP_ALLOW} until the tunnel is up." >&2
11
+ iptables -P OUTPUT DROP
12
+ iptables -A OUTPUT -m addrtype --dst-type LOCAL -j ACCEPT
13
+ iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
14
+ ip6tables -P OUTPUT DROP
15
+ ip6tables -A OUTPUT -m addrtype --dst-type LOCAL -j ACCEPT
16
+ ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
17
+ IFS=',' read -ra BOOTSTRAP_ALLOW <<< "$AGENT_GATEWAY_BOOTSTRAP_ALLOW"
18
+ for addr in "${BOOTSTRAP_ALLOW[@]}"; do
19
+ if [[ "$addr" == *:* ]]; then
20
+ ip6tables -A OUTPUT -d "$addr" -j ACCEPT
21
+ else
22
+ iptables -A OUTPUT -d "$addr" -j ACCEPT
23
+ fi
24
+ done
25
+ fi
26
+ install -m 600 /etc/agent/gateway-key /tmp/gateway-key
27
+ SSH_CMD="ssh -i /tmp/gateway-key -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/agent/gateway-known-hosts"
28
+ if [ -n "${AGENT_GATEWAY_ACCESS_HOSTNAME:-}" ]; then
29
+ echo "[entrypoint] Reaching the gateway via Cloudflare Access hostname ${AGENT_GATEWAY_ACCESS_HOSTNAME}." >&2
30
+ SSH_CMD="$SSH_CMD -o ProxyCommand='cloudflared access ssh --hostname ${AGENT_GATEWAY_ACCESS_HOSTNAME}'"
31
+ fi
32
+ sshuttle -r "${AGENT_GATEWAY_USER:-tunnel}@${AGENT_GATEWAY_HOST}:${AGENT_GATEWAY_PORT:-2222}" \
33
+ 0.0.0.0/0 ::/0 --dns --daemon --pidfile=/tmp/sshuttle.pid \
34
+ -e "$SSH_CMD"
35
+ else
36
+ source /usr/local/lib/agent/egress-allowlist.sh
37
+ configure_egress_allowlist
38
+ fi
39
+
40
+ # Root has done its network setup above; hand off to upstream's own boot
41
+ # sequence unmodified. /init runs the s6-rc service tree (including
42
+ # stage2-hook.sh, which chowns /opt/data to the hermes user on first boot —
43
+ # hence this image needing CAP_CHOWN under --cap-drop=ALL) and its own
44
+ # s6-setuidgid drop to the unprivileged hermes user, so no gosu/privilege
45
+ # drop is needed on our side. DAC_OVERRIDE is also required under
46
+ # --cap-drop=ALL: 02-reconcile-profiles registers the main-hermes/dashboard
47
+ # services after the static supervise trees are already chowned, so
48
+ # s6-supervise can't open their supervise/lock files without it.
49
+ exec /init /opt/hermes/docker/main-wrapper.sh "$@"
@@ -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 (iptables, dnsmasq, sshuttle, etc.), which this
5
+ # file 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 @@
1
+ # Profile-specific CA certificates are injected into this directory by the onboarding CLI.
@@ -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,101 @@
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 kilo 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
+ RUN npm install -g @kilocode/cli
28
+
29
+ # The kilo account is created here, at a fixed placeholder UID/GID
30
+ # (1000:1000), purely so the root-only installers below (npm, uv tool) have
31
+ # a real non-root account to run as. `final` remaps this account to the
32
+ # operator's actual host UID/GID instead of recreating it, so a rebuild for
33
+ # a different host user reuses everything installed below unchanged rather
34
+ # than reinstalling it over the network.
35
+ RUN groupadd -g 1000 kilo \
36
+ && useradd -m -u 1000 -g 1000 -s /bin/bash kilo \
37
+ && mkdir -p /home/kilo/.config/kilo /workspace \
38
+ && chown -R kilo:kilo /home/kilo /workspace
39
+
40
+ ENV HOME=/home/kilo
41
+ ENV npm_config_prefix=/home/kilo/.npm-global
42
+ ENV PATH="/home/kilo/.local/bin:/home/kilo/.npm-global/bin:${PATH}"
43
+
44
+ # $HOME (uv's default cache location) and /workspace are deliberately
45
+ # separate mounts in this containment model, so uv's hardlink/reflink
46
+ # optimisation can never apply between them — declare the copy fallback
47
+ # explicit rather than have uv warn about it on every install.
48
+ ENV UV_LINK_MODE=copy
49
+
50
+ COPY packages-npm.txt /tmp/packages-npm.txt
51
+ COPY tools-uv.txt /tmp/tools-uv.txt
52
+ RUN gosu kilo bash -c '. /usr/local/lib/agent/install-additional-packages.sh \
53
+ && install_npm_packages /tmp/packages-npm.txt \
54
+ && install_uv_tools /tmp/tools-uv.txt' \
55
+ && rm -f /tmp/packages-npm.txt /tmp/tools-uv.txt
56
+
57
+ # Everything above this line is UID/GID-independent and layer-cacheable
58
+ # across hosts: the kilo account exists at the fixed placeholder UID/GID
59
+ # only so the installers above have a non-root account to run as. From here
60
+ # down, `final` remaps that account to the operator's actual host UID/GID
61
+ # with --build-arg UID=$(id -u) --build-arg GID=$(id -g) — the base stage
62
+ # above is reused unchanged from cache, and nothing installed above is
63
+ # reinstalled, rather than rebuilding per host user.
64
+ FROM base AS final
65
+ ARG UID=1000
66
+ ARG GID=1000
67
+
68
+ # Only remap when the caller supplied a different UID/GID than the fixed
69
+ # placeholder baked into `base` above — usermod/groupmod plus a chown -R
70
+ # walk over already-installed content is far cheaper than rerunning the
71
+ # npm/uv-tool installs above, but it's skippable entirely for the common
72
+ # case where the caller never overrides UID/GID. Deliberately no -o:
73
+ # without it, usermod/groupmod refuse to reuse an ID already assigned to
74
+ # another account, so a caller can never remap kilo onto UID/GID 0 (root)
75
+ # or collide it with another in-image identity — the build fails instead of
76
+ # silently succeeding.
77
+ RUN if [ "$UID" != "1000" ] || [ "$GID" != "1000" ]; then \
78
+ groupmod -g "$GID" kilo \
79
+ && usermod -u "$UID" -g "$GID" kilo \
80
+ && chown -R "$UID":"$GID" /home/kilo /workspace; \
81
+ fi
82
+
83
+ COPY --from=shared egress-allowlist.sh /usr/local/lib/agent/egress-allowlist.sh
84
+ COPY --from=shared workload-entrypoint.sh /usr/local/lib/agent/workload-entrypoint.sh
85
+ RUN chmod +x /usr/local/lib/agent/workload-entrypoint.sh
86
+
87
+ # Strip setuid/setgid bits from the base image and every package installed
88
+ # above — required infra (install-workload-base.sh), packages-apt.txt,
89
+ # packages-npm.txt, packages-uv.txt, and tools-uv.txt — run last so nothing
90
+ # installed by any of those is missed. NoNewPrivs=1 (set by every documented
91
+ # run invocation) already makes these bits inert on execve, but that's an
92
+ # argument for stripping them, not against: if any single control ever
93
+ # regresses, a setuid-root `mount` becomes a live escalation primitive. None
94
+ # of these binaries are needed at runtime, so none should ship
95
+ # exploitable-shaped.
96
+ RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} + || true
97
+
98
+ WORKDIR /workspace
99
+
100
+ ENTRYPOINT ["/usr/local/lib/agent/workload-entrypoint.sh", "kilo", "kilo"]
101
+ CMD ["kilo"]
@@ -0,0 +1,7 @@
1
+ # One hostname or IP/CIDR per line. This file takes precedence over
2
+ # AGENT_ALLOWED_EGRESS when mounted at /etc/agent/egress-allowlist.txt.
3
+ #
4
+ # Kilo's hosted AI Gateway. Use this only when the Kilo gateway is the selected
5
+ # provider; add a configured direct provider, source-control host, MCP server,
6
+ # and package registry explicitly as needed.
7
+ api.kilo.ai