mirror of
https://github.com/Heretek-AI/heretek-devcontainers.git
synced 2026-11-07 10:56:02 -10:00
Pin images that exist; add pinned local builds for upstreams without one
11 of 15 pinned images did not exist in their registries. Published images now use real release tags (bifrost v2.3.0, searxng 2026.10.16-0ce72dd2c, docker-android emulator_14.0_v3.8.0-p1, devcontainers/base 2.2.3-bookworm). Upstreams with no usable image get images/<service>/Dockerfile on a pinned base, installing a checksum- or commit-verified upstream release, tagged localhost/heretek/<service>:<version> with pull_policy: build. Stdio-only servers are wrapped with a pinned mcp-proxy so every MCP service serves streamable HTTP; compose labels declare each endpoint. radare2-mcp now runs with -R (read-only); the previous -r enabled the dangerous run_* tools. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_012AuVk4nEvBXaTjs4G6wdW4
This commit is contained in:
22 files changed
+765
-43
No files matched your search
@@ -118,7 +118,7 @@ No `VERDICT: PASS`, no merge. No exceptions.
|
||||
├── tsconfig.json
|
||||
├── docker-compose.yml # Root compose using `include:` for compose/core.yml + 4 specialist profiles
|
||||
├── compose/
|
||||
│ ├── core.yml # Profile `core`: maximhq/bifrost:v1.0.0 (:8080), agentgateway:v1.6.0 (:3000), nats:2.15.1-alpine (:4222)
|
||||
│ ├── core.yml # Profile `core`: maximhq/bifrost:v2.3.0 (:8080), agentgateway:v1.6.0 (:3000), nats:2.15.1-alpine (:4222)
|
||||
│ ├── bifrost.yaml # 3-Tier LLM Failover config (OpenCode Go -> OpenRouter -> Local Ollama/vLLM) + Session Affinity
|
||||
│ ├── agentgateway.yaml # CEL RBAC rules & MCP target routing
|
||||
│ ├── profile-appsec.yml # Profile `appsec`: bethington/ghidra-mcp-headless:v6.0.0, radareorg/radare2-mcp:1.8.8, morluto/rea:6.5.0, mdb-mcp:1.2.0
|
||||
@@ -134,14 +134,17 @@ No `VERDICT: PASS`, no merge. No exceptions.
|
||||
│ │ └── pixi.toml # Conda-forge/PyTorch 2.4/CUDA 12.4/CMake/Ninja/OpenCV deduplicated via rattler hardlinks
|
||||
│ └── headless-wayland-desktop/
|
||||
│ └── Dockerfile # Debian trixie-slim + headless Sway + at-spi2-core + ydotool + agent-sh/computer-use-linux:v0.9.4
|
||||
├── quadlets/ # Systemd rootless Podman Quadlets (`StopWhenUnneeded=true` -> 0 MB idle RAM)
|
||||
│ ├── heretek-android-kvm.container
|
||||
│ ├── heretek-ghidra-mcp.container
|
||||
│ └── heretek-blender-mcp.container
|
||||
├── images/ # Pinned local builds for upstreams with no usable published image
|
||||
│ └── <service>/Dockerfile # tagged localhost/heretek/<service>:<version> (see README "Images")
|
||||
├── quadlets/ # Systemd rootless Podman Quadlets, socket-activated (0 MB idle RAM)
|
||||
│ ├── heretek-android-kvm.container (+ -vnc-proxy / -adb-proxy .socket/.service)
|
||||
│ ├── heretek-ghidra-mcp.container (+ -proxy.socket/.service)
|
||||
│ └── heretek-blender-mcp.container (+ -proxy.socket/.service)
|
||||
├── scripts/
|
||||
│ ├── heretek-exec.sh # Sub-5ms local worktree wrapper: bwrap + landrun + mise exec (with /dev/kvm & SELinux support)
|
||||
│ ├── heretek-box-ctl.sh # Operator CLI: `up <profile>`, `down <profile>`, `status`, `verify`, `exec <task-id> <cmd>`
|
||||
│ ├── verify-stack.ts # Deterministic static contract verifier auditing all Compose/Devcontainer/Quadlet/Env files
|
||||
│ ├── check-images.ts # `make images`: resolves every pinned remote image in its registry
|
||||
│ └── accept/
|
||||
│ ├── wave-1.sh
|
||||
│ ├── wave-2.sh
|
||||
@@ -156,6 +159,12 @@ No `VERDICT: PASS`, no merge. No exceptions.
|
||||
|
||||
## 2. EXACT MODULE SPECIFICATIONS
|
||||
|
||||
> **Image pins (2026-10 remediation):** several image tags below never existed
|
||||
> in their registries. The current pins (real published tags, or pinned local
|
||||
> builds `localhost/heretek/<service>:<version>` from `images/`) are listed in
|
||||
> README "Images" and enforced by `make images`. radare2-mcp runs with `-R`
|
||||
> (read-only); `-r` would enable its dangerous run_* tools.
|
||||
|
||||
### 2.1 Wave 1: Root Compose, `core.yml`, `bifrost.yaml`, Fail-Closed Static Verifier
|
||||
|
||||
1. **`docker-compose.yml` & `compose/core.yml`:**
|
||||
|
||||
@@ -20,6 +20,58 @@ All host ports bind `127.0.0.1` loopback only. Every image is pinned to an
|
||||
exact version tag. The root `docker-compose.yml` orchestrates the five
|
||||
fragments via `include:`.
|
||||
|
||||
### Images
|
||||
|
||||
Published images are pinned to tags that exist in their registry
|
||||
(`make images` checks this). Upstreams that publish no usable image are
|
||||
built locally from `images/<service>/Dockerfile` and tagged
|
||||
`localhost/heretek/<service>:<upstream version>`; compose builds them
|
||||
(`pull_policy: build`) and never pulls those names. Each Dockerfile starts
|
||||
from a pinned base image and installs a pinned upstream release, verified by
|
||||
SHA-256/SHA-512 or by checking out the exact upstream commit.
|
||||
|
||||
| Service | Image | Source |
|
||||
|---|---|---|
|
||||
| `bifrost` | `ghcr.io/maximhq/bifrost:v2.3.0` | published |
|
||||
| `agentgateway` | `ghcr.io/agentgateway/agentgateway:v1.6.0` | published |
|
||||
| `nats` | `nats:2.15.1-alpine` | published |
|
||||
| `android-kvm` | `budtmo/docker-android:emulator_14.0_v3.8.0-p1` | published |
|
||||
| `searxng` | `searxng/searxng:2026.10.16-0ce72dd2c` | published |
|
||||
| `crawl4ai` | `unclecode/crawl4ai:0.9.5` | published |
|
||||
| `ghidra-mcp` | `localhost/heretek/ghidra-mcp:6.0.0` | bethington/ghidra-mcp v6.0.0 + Ghidra 12.1.2 |
|
||||
| `radare2-mcp` | `localhost/heretek/radare2-mcp:1.9.0` | r2mcp 1.9.0 + radare2 6.2.4 release .debs |
|
||||
| `rea-mcp` | `localhost/heretek/rea-mcp:6.5.0` | npm `[email protected]` |
|
||||
| `mdb-mcp` | `localhost/heretek/mdb-mcp:0.2.0-5d03b25` | smadi0x86/mdb-mcp commit 5d03b25 (upstream has no tags) |
|
||||
| `adb-mcp` | `localhost/heretek/adb-mcp:0.23.0` | binary from `ghcr.io/iksnerd/adb-mcp:0.23.0` |
|
||||
| `maestro-mcp` | `localhost/heretek/maestro-mcp:2.11.0` | Maestro CLI cli-2.11.0 release |
|
||||
| `godot-mcp` | `localhost/heretek/godot-mcp:3.1.0` | tugcantopaloglu/godot-mcp v3.1.0 + Godot 4.3-stable |
|
||||
| `blender-mcp` | `localhost/heretek/blender-mcp:0.11.1` | sandraschi/blender-mcp v0.11.1 + Blender 4.2.3 LTS |
|
||||
| `computer-use-linux` | `localhost/heretek/computer-use-linux:0.7.17` | agent-sh/computer-use-linux v0.7.17 release binary |
|
||||
|
||||
Stdio-only MCP servers are wrapped with a pinned `mcp-proxy` (0.13.0) so every
|
||||
MCP service speaks streamable HTTP at `/mcp` on its container port (crawl4ai:
|
||||
SSE at `/mcp/sse`). Compose labels (`heretek.mcp.target`, `.transport`,
|
||||
`.path`) declare those endpoints.
|
||||
|
||||
```sh
|
||||
make images # resolve every pinned remote image against Docker Hub / GHCR / MCR (network)
|
||||
```
|
||||
|
||||
### Agent gateway
|
||||
|
||||
`compose/agentgateway.yaml` is an agentgateway v1.6.0 standalone config
|
||||
(`agentgateway -f /etc/agentgateway.yaml`). It serves one route per MCP target
|
||||
at `/mcp/<target>` (`ghidra`, `radare2`, `rea`, `mdb`, `adb`, `maestro`,
|
||||
`godot`, `blender`, `crawl4ai`, `gui`), each holding exactly one target so
|
||||
tool names are not prefixed, plus an OpenAI-compatible `/v1` route to Bifrost.
|
||||
Every request needs `Authorization: Bearer <AGENTGATEWAY_ADMIN_KEY|AGENTGATEWAY_AGENT_KEY>`;
|
||||
`mdb` and `gui` are admin-only. There is no `core` or `searxng` route yet:
|
||||
no heretek-core MCP service exists here, and SearXNG is not an MCP server.
|
||||
|
||||
```sh
|
||||
make validate-gateway AGENTGATEWAY=/path/to/agentgateway # real binary, --validate-only
|
||||
```
|
||||
|
||||
## Quickstart
|
||||
|
||||
```sh
|
||||
@@ -58,11 +110,21 @@ violation:
|
||||
| `SELINUX_VOLUME_LABELS` | any bind mount missing an SELinux label (`:z`, `:Z`, `:ro,Z`) |
|
||||
| `NO_AGPL_IN_DEV_OR_GAME` | AGPL-licensed or root-escape images |
|
||||
| `ENV_PLACEHOLDER_LEAK` | a real `.env` still carrying template placeholder tokens |
|
||||
| `SELINUX_CONFINED` | a quadlet that disables SELinux confinement (`SecurityLabelType=spc`, `SecurityLabelDisable=true`, privileged) |
|
||||
| `QUADLET_SOCKET_ACTIVATION` | a quadlet container that can start on its own or lacks its loopback `.socket` + idle-exiting proxy `.service` |
|
||||
|
||||
`NO_FLOATING_TAGS` also covers local builds (a `build:` service must tag
|
||||
`localhost/<name>:<version>`) and Dockerfile `FROM` / `COPY --from=` images.
|
||||
Each rule has a failing fixture tree under `test/fixtures/verify/`
|
||||
(`bun run scripts/verify-stack.ts --root <dir>`), exercised by
|
||||
`test/verify-stack.test.ts`.
|
||||
|
||||
```sh
|
||||
bun run scripts/verify-stack.ts # or: ./scripts/heretek-box-ctl.sh verify
|
||||
make check # verifier + bun test
|
||||
make accept # all three wave acceptance gates
|
||||
make validate-compose # docker compose config for every profile
|
||||
make validate-units # systemd-analyze verify on the quadlet sockets/proxies
|
||||
```
|
||||
|
||||
## Devcontainers
|
||||
@@ -81,18 +143,33 @@ and commit the bumped pins.
|
||||
|
||||
## Quadlets
|
||||
|
||||
Rootless Podman units for the heavy services, all with
|
||||
`StopWhenUnneeded=true` so they sit at 0 MB idle RAM until socket-activated:
|
||||
Rootless Podman units for the heavy services, socket-activated so they use
|
||||
no memory until something connects:
|
||||
|
||||
| Unit | Image | Loopback ports |
|
||||
- `<name>-proxy.socket` owns the loopback port (the same one compose uses)
|
||||
and is the only unit you enable (`WantedBy=sockets.target`).
|
||||
- `<name>-proxy.service` runs `systemd-socket-proxyd --exit-idle-time=15min`
|
||||
and `Requires=`/`After=` the container unit, so the first connection starts
|
||||
the container (`Notify=healthy` holds the proxy until it answers).
|
||||
- `<name>.container` has `StopWhenUnneeded=true` and no `[Install]` section,
|
||||
so it never starts at login and stops once its proxy exits idle.
|
||||
|
||||
| Container unit | Image | Activation sockets |
|
||||
|---|---|---|
|
||||
| `quadlets/heretek-android-kvm.container` | `budtmo/docker-android:emulator_14.0` (+ `/dev/kvm` device grant) | `127.0.0.1:6080`, `127.0.0.1:5555` |
|
||||
| `quadlets/heretek-ghidra-mcp.container` | `bethington/ghidra-mcp-headless:v6.0.0` (token via `~/.config/heretek/ghidra-mcp.env`, mode 600) | `127.0.0.1:8089` |
|
||||
| `quadlets/heretek-blender-mcp.container` | `ghcr.io/sandraschi/blender-mcp:v1.2.0` | `127.0.0.1:8781` |
|
||||
| `heretek-android-kvm.container` | `budtmo/docker-android:emulator_14.0_v3.8.0-p1` (+ `AddDevice=/dev/kvm`) | `127.0.0.1:6080` (vnc), `127.0.0.1:5555` (adb) |
|
||||
| `heretek-ghidra-mcp.container` | `localhost/heretek/ghidra-mcp:6.0.0` (token via `~/.config/heretek/ghidra-mcp.env`, mode 600) | `127.0.0.1:8089` |
|
||||
| `heretek-blender-mcp.container` | `localhost/heretek/blender-mcp:0.11.1` | `127.0.0.1:8781` |
|
||||
|
||||
All containers keep the default `container_t` SELinux confinement. If an
|
||||
enforcing host denies the emulator `/dev/kvm`, use
|
||||
`setsebool -P container_use_devices=true` rather than disabling labels.
|
||||
|
||||
```sh
|
||||
podman build -t localhost/heretek/ghidra-mcp:6.0.0 images/ghidra-mcp # local images first
|
||||
cp quadlets/*.container ~/.config/containers/systemd/
|
||||
cp quadlets/*.socket quadlets/*.service ~/.config/systemd/user/
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now heretek-blender-mcp.service
|
||||
systemctl --user enable --now heretek-ghidra-mcp-proxy.socket
|
||||
```
|
||||
|
||||
## heretek-exec and box-ctl exec
|
||||
|
||||
@@ -8,9 +8,16 @@
|
||||
#
|
||||
# `android-kvm` passes through /dev/kvm via `devices:` (a device mapping,
|
||||
# not a volume mount — no SELinux label rule applies to it).
|
||||
#
|
||||
# android-kvm pins the immutable release tag emulator_14.0_v3.8.0-p1 (the bare
|
||||
# emulator_14.0 tag is re-pointed on every docker-android release). The two MCP
|
||||
# bridges have no usable published HTTP image, so they are pinned local builds
|
||||
# (images/<service>/Dockerfile) tagged localhost/heretek/<service>:<version>;
|
||||
# `pull_policy: build` keeps compose from pulling the local names. Both speak
|
||||
# MCP over streamable HTTP at /mcp (see the `heretek.mcp.*` labels).
|
||||
services:
|
||||
android-kvm:
|
||||
image: budtmo/docker-android:emulator_14.0
|
||||
image: budtmo/docker-android:emulator_14.0_v3.8.0-p1
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["android", "all"]
|
||||
@@ -31,7 +38,10 @@ services:
|
||||
retries: 3
|
||||
|
||||
adb-mcp:
|
||||
image: ghcr.io/iksnerd/adb-mcp:v1.4.0
|
||||
image: localhost/heretek/adb-mcp:0.23.0
|
||||
build:
|
||||
context: ../images/adb-mcp
|
||||
pull_policy: build
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["android", "all"]
|
||||
@@ -41,6 +51,10 @@ services:
|
||||
environment:
|
||||
ANDROID_HOST: android-kvm
|
||||
ADB_PORT: "5555"
|
||||
labels:
|
||||
heretek.mcp.target: adb
|
||||
heretek.mcp.transport: streamable-http
|
||||
heretek.mcp.path: /mcp
|
||||
ports:
|
||||
- "127.0.0.1:8770:8770"
|
||||
volumes:
|
||||
@@ -49,23 +63,31 @@ services:
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8770/healthz"]
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8770/status', timeout=4)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
maestro-mcp:
|
||||
image: ghcr.io/mobile-dev-inc/maestro:v1.39.8
|
||||
image: localhost/heretek/maestro-mcp:2.11.0
|
||||
build:
|
||||
context: ../images/maestro-mcp
|
||||
pull_policy: build
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["android", "all"]
|
||||
depends_on:
|
||||
android-kvm:
|
||||
condition: service_started
|
||||
command: ["maestro", "mcp"]
|
||||
# The image entrypoint attaches adb to android-kvm, then serves
|
||||
# `maestro mcp` over streamable HTTP via mcp-proxy.
|
||||
environment:
|
||||
ANDROID_HOST: android-kvm
|
||||
ADB_PORT: "5555"
|
||||
labels:
|
||||
heretek.mcp.target: maestro
|
||||
heretek.mcp.transport: streamable-http
|
||||
heretek.mcp.path: /mcp
|
||||
ports:
|
||||
- "127.0.0.1:8771:8771"
|
||||
volumes:
|
||||
@@ -74,7 +96,7 @@ services:
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8771/healthz"]
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8771/status', timeout=4)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
+53
-10
@@ -9,14 +9,32 @@
|
||||
# NOTE: `mdb-mcp` carries the SOLE documented capability exception in this
|
||||
# repo — a narrow SYS_PTRACE grant required for debugger attachment.
|
||||
# It still drops all other capabilities and keeps no-new-privileges.
|
||||
#
|
||||
# None of these upstreams publishes a usable container image, so each service
|
||||
# is a pinned local build (images/<service>/Dockerfile) tagged
|
||||
# localhost/heretek/<service>:<upstream version>. `pull_policy: build` keeps
|
||||
# compose from ever trying to pull the local name from a registry.
|
||||
# Every service speaks MCP over streamable HTTP at /mcp on its container port;
|
||||
# the `heretek.mcp.*` labels are what compose/agentgateway.yaml routes to
|
||||
# (enforced by test/agentgateway-config.test.ts).
|
||||
services:
|
||||
ghidra-mcp:
|
||||
image: bethington/ghidra-mcp-headless:v6.0.0
|
||||
image: localhost/heretek/ghidra-mcp:6.0.0
|
||||
build:
|
||||
context: ../images/ghidra-mcp
|
||||
pull_policy: build
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["appsec", "all"]
|
||||
environment:
|
||||
GHIDRA_MCP_AUTH_TOKEN: ${GHIDRA_MCP_AUTH_TOKEN:?GHIDRA_MCP_AUTH_TOKEN is mandatory}
|
||||
# The bridge keeps DNS-rebinding protection on; allow the compose DNS
|
||||
# name the agent gateway uses as its Host header, plus host loopback.
|
||||
GHIDRA_MCP_ALLOWED_HOSTS: ghidra-mcp,localhost,127.0.0.1
|
||||
labels:
|
||||
heretek.mcp.target: ghidra
|
||||
heretek.mcp.transport: streamable-http
|
||||
heretek.mcp.path: /mcp
|
||||
ports:
|
||||
- "127.0.0.1:8089:8089"
|
||||
volumes:
|
||||
@@ -25,19 +43,29 @@ services:
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8089/healthz"]
|
||||
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8089"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 120s
|
||||
|
||||
radare2-mcp:
|
||||
image: ghcr.io/radareorg/radare2-mcp:1.8.8
|
||||
image: localhost/heretek/radare2-mcp:1.9.0
|
||||
build:
|
||||
context: ../images/radare2-mcp
|
||||
pull_policy: build
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["appsec", "all"]
|
||||
# Container-internal listen address (NOT a published host port — the
|
||||
# published port below is loopback-only). `-r` forces read-only mode.
|
||||
command: ["r2mcp", "-H", "0.0.0.0:8765", "-r"]
|
||||
# published port below is loopback-only). `-R` exposes only the read-only
|
||||
# tool set (`-r` would ENABLE the dangerous run_* tools); `-s /data`
|
||||
# sandboxes file access to the data volume.
|
||||
command: ["r2mcp", "-H", "0.0.0.0:8765", "-R", "-s", "/data"]
|
||||
labels:
|
||||
heretek.mcp.target: radare2
|
||||
heretek.mcp.transport: streamable-http
|
||||
heretek.mcp.path: /mcp
|
||||
ports:
|
||||
- "127.0.0.1:8765:8765"
|
||||
volumes:
|
||||
@@ -46,16 +74,23 @@ services:
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8765/healthz"]
|
||||
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8765"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
rea-mcp:
|
||||
image: ghcr.io/morluto/rea:6.5.0
|
||||
image: localhost/heretek/rea-mcp:6.5.0
|
||||
build:
|
||||
context: ../images/rea-mcp
|
||||
pull_policy: build
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["appsec", "all"]
|
||||
labels:
|
||||
heretek.mcp.target: rea
|
||||
heretek.mcp.transport: streamable-http
|
||||
heretek.mcp.path: /mcp
|
||||
ports:
|
||||
- "127.0.0.1:8766:8766"
|
||||
volumes:
|
||||
@@ -64,16 +99,24 @@ services:
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8766/healthz"]
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8766/status', timeout=4)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
mdb-mcp:
|
||||
image: ghcr.io/smadi0x86/mdb-mcp:1.2.0
|
||||
# Upstream has no release tag; the build pins commit 5d03b25 (version 0.2.0).
|
||||
image: localhost/heretek/mdb-mcp:0.2.0-5d03b25
|
||||
build:
|
||||
context: ../images/mdb-mcp
|
||||
pull_policy: build
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["appsec", "all"]
|
||||
labels:
|
||||
heretek.mcp.target: mdb
|
||||
heretek.mcp.transport: streamable-http
|
||||
heretek.mcp.path: /mcp
|
||||
ports:
|
||||
- "127.0.0.1:8767:8767"
|
||||
volumes:
|
||||
@@ -84,7 +127,7 @@ services:
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8767/healthz"]
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8767/status', timeout=4)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
# compose/profile-game.yml — `game` on-demand domain profile (Wave 2)
|
||||
#
|
||||
# Headless Godot 4.3 + Blender FastMCP 3.2 MCP servers. Activated on demand via:
|
||||
# Headless Godot 4.3 + Blender 4.2 LTS FastMCP servers. Activated on demand via:
|
||||
# docker compose --profile game up
|
||||
# (or `--profile all` for everything). Every service is hardened fail-closed:
|
||||
# pinned image, 127.0.0.1 loopback ports only, cap_drop ALL,
|
||||
# no-new-privileges, non-root user, zero socket mounts.
|
||||
#
|
||||
# Neither upstream publishes a container image, so both are pinned local
|
||||
# builds (images/<service>/Dockerfile) tagged localhost/heretek/<service>:<version>;
|
||||
# `pull_policy: build` keeps compose from pulling the local names. Both speak
|
||||
# MCP over streamable HTTP at /mcp (see the `heretek.mcp.*` labels).
|
||||
services:
|
||||
godot-mcp:
|
||||
image: ghcr.io/tugcantopaloglu/godot-mcp:v3.1.0
|
||||
image: localhost/heretek/godot-mcp:3.1.0
|
||||
build:
|
||||
context: ../images/godot-mcp
|
||||
pull_policy: build
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["game", "all"]
|
||||
environment:
|
||||
GODOT_HEADLESS: "1"
|
||||
labels:
|
||||
heretek.mcp.target: godot
|
||||
heretek.mcp.transport: streamable-http
|
||||
heretek.mcp.path: /mcp
|
||||
ports:
|
||||
- "127.0.0.1:8780:8780"
|
||||
volumes:
|
||||
@@ -21,20 +33,27 @@ services:
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8780/healthz"]
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8780/status', timeout=4)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
blender-mcp:
|
||||
image: ghcr.io/sandraschi/blender-mcp:v1.2.0
|
||||
image: localhost/heretek/blender-mcp:0.11.1
|
||||
build:
|
||||
context: ../images/blender-mcp
|
||||
pull_policy: build
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["game", "all"]
|
||||
# Headless Blender background mode serving the FastMCP 3.2 bridge.
|
||||
command: ["blender", "--background"]
|
||||
# The image serves the FastMCP server over streamable HTTP; it drives
|
||||
# Blender 4.2.3 headless (`blender --background`) per operation.
|
||||
environment:
|
||||
BLENDER_HEADLESS: "1"
|
||||
labels:
|
||||
heretek.mcp.target: blender
|
||||
heretek.mcp.transport: streamable-http
|
||||
heretek.mcp.path: /mcp
|
||||
ports:
|
||||
- "127.0.0.1:8781:8781"
|
||||
volumes:
|
||||
@@ -43,7 +62,7 @@ services:
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8781/healthz"]
|
||||
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8781"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
@@ -7,9 +7,15 @@
|
||||
# (or `--profile all` for everything). Every service is hardened fail-closed:
|
||||
# pinned image, 127.0.0.1 loopback ports only, cap_drop ALL,
|
||||
# no-new-privileges, non-root user, zero socket mounts.
|
||||
#
|
||||
# searxng and crawl4ai are published images pinned to exact release tags.
|
||||
# computer-use-linux publishes no image, so it is a pinned local build
|
||||
# (images/computer-use-linux/Dockerfile) tagged localhost/heretek/...:<version>;
|
||||
# `pull_policy: build` keeps compose from pulling the local name.
|
||||
# MCP endpoints are declared with `heretek.mcp.*` labels for the gateway.
|
||||
services:
|
||||
searxng:
|
||||
image: searxng/searxng:2026.10.14
|
||||
image: searxng/searxng:2026.10.16-0ce72dd2c
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["research", "all"]
|
||||
@@ -35,6 +41,11 @@ services:
|
||||
restart: unless-stopped
|
||||
profiles: ["research", "all"]
|
||||
shm_size: "1g"
|
||||
# crawl4ai's server exposes its MCP tools over SSE at /mcp/sse.
|
||||
labels:
|
||||
heretek.mcp.target: crawl4ai
|
||||
heretek.mcp.transport: sse
|
||||
heretek.mcp.path: /mcp/sse
|
||||
ports:
|
||||
- "127.0.0.1:11235:11235"
|
||||
volumes:
|
||||
@@ -49,7 +60,10 @@ services:
|
||||
retries: 3
|
||||
|
||||
computer-use-linux:
|
||||
image: ghcr.io/agent-sh/computer-use-linux:v0.9.4
|
||||
image: localhost/heretek/computer-use-linux:0.7.17
|
||||
build:
|
||||
context: ../images/computer-use-linux
|
||||
pull_policy: build
|
||||
user: "1000:1000"
|
||||
restart: unless-stopped
|
||||
profiles: ["research", "all"]
|
||||
@@ -57,6 +71,10 @@ services:
|
||||
environment:
|
||||
WLR_BACKENDS: headless
|
||||
SWAY_HEADLESS: "1"
|
||||
labels:
|
||||
heretek.mcp.target: gui
|
||||
heretek.mcp.transport: streamable-http
|
||||
heretek.mcp.path: /mcp
|
||||
ports:
|
||||
- "127.0.0.1:8790:8790"
|
||||
volumes:
|
||||
@@ -65,7 +83,7 @@ services:
|
||||
security_opt:
|
||||
- "no-new-privileges:true"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8790/healthz"]
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8790/status', timeout=4)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
# devcontainers/headless-wayland-desktop/Dockerfile — headless Sway + AT-SPI2 desktop (Wave 3)
|
||||
#
|
||||
# Debian trixie-slim base (pinned, never floating) with a headless Sway
|
||||
# Debian trixie-slim base (pinned to a dated release, never floating) with a headless Sway
|
||||
# compositor, AT-SPI2 accessibility bus, and ydotool for synthetic input.
|
||||
# Pairs at runtime with the pinned computer-use-linux agent image noted below
|
||||
# (see compose/profile-research.yml `computer-use-linux` service), which
|
||||
# consumes this desktop over the headless Wayland socket.
|
||||
#
|
||||
# Runtime peer (pinned, Apache-2.0/MIT stack — no AGPL tooling):
|
||||
# ghcr.io/agent-sh/computer-use-linux:v0.9.4
|
||||
# Runtime peer (pinned local build of the MIT agent-sh/computer-use-linux
|
||||
# v0.7.17 release — no AGPL tooling; see images/computer-use-linux/Dockerfile):
|
||||
# localhost/heretek/computer-use-linux:0.7.17
|
||||
#
|
||||
# Security posture: non-root `agent` user (uid 1000), no container-socket
|
||||
# mounts, no privileged capabilities required (KVM passthrough, where needed,
|
||||
# is granted by the orchestrator via device mapping, not by this image).
|
||||
|
||||
FROM debian:trixie-slim
|
||||
FROM debian:trixie-20261005-slim
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
WLR_BACKENDS=headless \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "heretek-polyglot-dev",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:1.2.2-bookworm",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:2.2.3-bookworm",
|
||||
"remoteUser": "agent",
|
||||
"containerUser": "agent",
|
||||
"capAdd": [],
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# images/adb-mcp/Dockerfile — adb-mcp (MIT) with a streamable-HTTP front end
|
||||
#
|
||||
# The upstream image ghcr.io/iksnerd/adb-mcp:0.23.0 exists but speaks MCP over
|
||||
# stdio only, so this build copies its pinned binary next to the Debian adb
|
||||
# client and a pinned mcp-proxy. Compose tags the result
|
||||
# localhost/heretek/adb-mcp:0.23.0; the agent gateway reaches it at
|
||||
# http://adb-mcp:8770/mcp.
|
||||
|
||||
FROM ghcr.io/iksnerd/adb-mcp:0.23.0 AS upstream
|
||||
|
||||
FROM python:3.12.15-slim-trixie
|
||||
|
||||
ARG MCP_PROXY_VERSION=0.13.0
|
||||
|
||||
COPY --from=upstream /usr/local/bin/adb-mcp /usr/local/bin/adb-mcp
|
||||
COPY start.sh /usr/local/bin/heretek-adb-mcp
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends adb; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
pip install --no-cache-dir "mcp-proxy==${MCP_PROXY_VERSION}"; \
|
||||
chmod 0755 /usr/local/bin/heretek-adb-mcp; \
|
||||
useradd -m -u 1000 -s /bin/bash mcp; \
|
||||
mkdir -p /data; \
|
||||
chown 1000:1000 /data
|
||||
|
||||
ENV HOME=/home/mcp
|
||||
USER 1000:1000
|
||||
WORKDIR /data
|
||||
EXPOSE 8770
|
||||
|
||||
CMD ["heretek-adb-mcp"]
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
# Attach the local adb server to the emulator, then serve adb-mcp over
|
||||
# streamable HTTP. A failed connect is not fatal: the emulator may still be
|
||||
# booting, and adb-mcp reports device errors per tool call.
|
||||
set -eu
|
||||
adb start-server
|
||||
adb connect "${ANDROID_HOST:-android-kvm}:${ADB_PORT:-5555}" || true
|
||||
exec mcp-proxy --host 0.0.0.0 --port "${MCP_PORT:-8770}" --pass-environment -- adb-mcp
|
||||
@@ -0,0 +1,55 @@
|
||||
# images/blender-mcp/Dockerfile — pinned local build of sandraschi/blender-mcp (MIT)
|
||||
#
|
||||
# Upstream publishes no container image, so this build installs the v0.11.1
|
||||
# tagged source (commit verified after checkout) next to Blender 4.2.3 LTS —
|
||||
# the Blender version upstream's own Dockerfile targets — whose tarball is
|
||||
# verified against the SHA-256 list blender.org publishes for that release.
|
||||
# Compose tags the result localhost/heretek/blender-mcp:0.11.1. blender-mcp
|
||||
# serves streamable HTTP natively (FastMCP), at http://blender-mcp:8781/mcp.
|
||||
|
||||
FROM debian:trixie-20261005-slim AS src
|
||||
|
||||
ARG BLENDER_MCP_TAG=v0.11.1
|
||||
ARG BLENDER_MCP_COMMIT=5a9e923f6ae68cfc34a221d31bd1fd5c8aff07f6
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends ca-certificates git; \
|
||||
git clone -q --depth 1 --branch "${BLENDER_MCP_TAG}" https://github.com/sandraschi/blender-mcp.git /src; \
|
||||
test "$(git -C /src rev-parse HEAD)" = "${BLENDER_MCP_COMMIT}"; \
|
||||
rm -rf /src/.git
|
||||
|
||||
FROM python:3.12.15-slim-trixie
|
||||
|
||||
ARG BLENDER_SERIES=4.2
|
||||
ARG BLENDER_VERSION=4.2.3
|
||||
|
||||
COPY --from=src /src /opt/blender-mcp-src
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl xz-utils \
|
||||
libgl1 libglib2.0-0 libx11-6 libxi6 libxxf86vm1 libxfixes3 libxrender1 libxkbcommon0 libsm6 libice6; \
|
||||
cd /tmp; \
|
||||
curl -fsSLO "https://download.blender.org/release/Blender${BLENDER_SERIES}/blender-${BLENDER_VERSION}-linux-x64.tar.xz"; \
|
||||
curl -fsSLO "https://download.blender.org/release/Blender${BLENDER_SERIES}/blender-${BLENDER_VERSION}.sha256"; \
|
||||
grep " blender-${BLENDER_VERSION}-linux-x64.tar.xz\$" "blender-${BLENDER_VERSION}.sha256" | sha256sum -c -; \
|
||||
tar -xJf "blender-${BLENDER_VERSION}-linux-x64.tar.xz"; \
|
||||
mv "blender-${BLENDER_VERSION}-linux-x64" /opt/blender; \
|
||||
rm -f /tmp/blender-*; \
|
||||
apt-get purge -y curl xz-utils; \
|
||||
apt-get autoremove -y; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
pip install --no-cache-dir /opt/blender-mcp-src; \
|
||||
useradd -m -u 1000 -s /bin/bash mcp; \
|
||||
mkdir -p /data; \
|
||||
chown 1000:1000 /data
|
||||
|
||||
ENV HOME=/home/mcp \
|
||||
BLENDER_EXECUTABLE=/opt/blender/blender
|
||||
USER 1000:1000
|
||||
WORKDIR /data
|
||||
EXPOSE 8781
|
||||
|
||||
CMD ["python", "-c", "from blender_mcp.server import main_http; main_http(host='0.0.0.0', port=8781)"]
|
||||
@@ -0,0 +1,50 @@
|
||||
# images/computer-use-linux/Dockerfile — pinned local build of agent-sh/computer-use-linux (MIT)
|
||||
#
|
||||
# Upstream publishes no container image, so this build installs the v0.7.17
|
||||
# release binary (verified against the release's published SHA-256) on a
|
||||
# pinned Debian trixie base with a headless Sway compositor and the AT-SPI2
|
||||
# accessibility bus. Compose tags the result
|
||||
# localhost/heretek/computer-use-linux:0.7.17. The stdio MCP server
|
||||
# (`computer-use-linux mcp`) is wrapped with a pinned mcp-proxy so the agent
|
||||
# gateway can reach it over streamable HTTP at http://computer-use-linux:8790/mcp.
|
||||
|
||||
FROM debian:trixie-20261005-slim
|
||||
|
||||
ARG CUL_VERSION=0.7.17
|
||||
ARG CUL_SHA256=4b9dccfda2e1b593d7ce333f30f88f48dce14c2235efa4f3ef37468e6fbc10b5
|
||||
ARG MCP_PROXY_VERSION=0.13.0
|
||||
|
||||
COPY start.sh /usr/local/bin/heretek-computer-use
|
||||
COPY sway-config /etc/heretek/sway-config
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl \
|
||||
sway at-spi2-core dbus dbus-user-session wtype grim fonts-dejavu-core \
|
||||
python3 python3-venv; \
|
||||
curl -fsSL -o /usr/local/bin/computer-use-linux \
|
||||
"https://github.com/agent-sh/computer-use-linux/releases/download/v${CUL_VERSION}/computer-use-linux-x86_64-unknown-linux-gnu"; \
|
||||
echo "${CUL_SHA256} /usr/local/bin/computer-use-linux" | sha256sum -c -; \
|
||||
chmod 0755 /usr/local/bin/computer-use-linux /usr/local/bin/heretek-computer-use; \
|
||||
apt-get purge -y curl; \
|
||||
apt-get autoremove -y; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
python3 -m venv /opt/mcp-proxy; \
|
||||
/opt/mcp-proxy/bin/pip install --no-cache-dir "mcp-proxy==${MCP_PROXY_VERSION}"; \
|
||||
ln -s /opt/mcp-proxy/bin/mcp-proxy /usr/local/bin/mcp-proxy; \
|
||||
useradd -m -u 1000 -s /bin/bash agent; \
|
||||
mkdir -p /data; \
|
||||
chown 1000:1000 /data
|
||||
|
||||
ENV HOME=/home/agent \
|
||||
XDG_RUNTIME_DIR=/tmp/xdg-runtime \
|
||||
WLR_BACKENDS=headless \
|
||||
WLR_RENDERER=pixman \
|
||||
WLR_LIBINPUT_NO_DEVICES=1 \
|
||||
COMPUTER_USE_LINUX_INDICATOR=0
|
||||
USER 1000:1000
|
||||
WORKDIR /data
|
||||
EXPOSE 8790
|
||||
|
||||
CMD ["heretek-computer-use"]
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Start a private D-Bus session bus and a headless Sway compositor, wait for
|
||||
# the Wayland socket, then serve `computer-use-linux mcp` over streamable HTTP.
|
||||
set -eu
|
||||
|
||||
mkdir -p "$XDG_RUNTIME_DIR"
|
||||
chmod 0700 "$XDG_RUNTIME_DIR"
|
||||
|
||||
DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"
|
||||
export DBUS_SESSION_BUS_ADDRESS
|
||||
dbus-daemon --session --address="$DBUS_SESSION_BUS_ADDRESS" --fork --nopidfile
|
||||
|
||||
sway --config /etc/heretek/sway-config &
|
||||
|
||||
tries=0
|
||||
while [ ! -S "$XDG_RUNTIME_DIR/wayland-1" ] && [ ! -S "$XDG_RUNTIME_DIR/wayland-0" ]; do
|
||||
tries=$((tries + 1))
|
||||
if [ "$tries" -gt 100 ]; then
|
||||
echo "heretek-computer-use: sway did not create a Wayland socket" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
if [ -S "$XDG_RUNTIME_DIR/wayland-1" ]; then WAYLAND_DISPLAY=wayland-1; else WAYLAND_DISPLAY=wayland-0; fi
|
||||
export WAYLAND_DISPLAY
|
||||
|
||||
exec mcp-proxy --host 0.0.0.0 --port "${MCP_PORT:-8790}" --pass-environment -- computer-use-linux mcp
|
||||
@@ -0,0 +1,8 @@
|
||||
# Minimal headless Sway session for computer-use-linux (no input devices,
|
||||
# software rendering, one virtual output).
|
||||
output HEADLESS-1 resolution 1920x1080 position 0,0
|
||||
default_border none
|
||||
focus_follows_mouse no
|
||||
|
||||
# AT-SPI2 accessibility bus for the accessibility-tree tools.
|
||||
exec /usr/libexec/at-spi-bus-launcher --launch-immediately
|
||||
@@ -0,0 +1,106 @@
|
||||
# images/ghidra-mcp/Dockerfile — pinned local build of bethington/ghidra-mcp (Apache-2.0)
|
||||
#
|
||||
# No image is published for ghidra-mcp v6.0.0, so this build follows the
|
||||
# upstream docker/Dockerfile recipe from the v6.0.0 tag (commit verified after
|
||||
# checkout) against the official Ghidra 12.1.2 release (pinned SHA-256; the
|
||||
# Ghidra version upstream's pom.xml requires). Compose tags the result
|
||||
# localhost/heretek/ghidra-mcp:6.0.0.
|
||||
#
|
||||
# The container runs two processes: the headless Ghidra REST server bound to
|
||||
# 127.0.0.1:8080 inside the container only, and upstream's MCP bridge serving
|
||||
# streamable HTTP on 0.0.0.0:8089/mcp for the agent gateway. Both enforce
|
||||
# GHIDRA_MCP_AUTH_TOKEN.
|
||||
|
||||
FROM debian:trixie-20261005-slim AS src
|
||||
|
||||
ARG GHIDRA_MCP_TAG=v6.0.0
|
||||
ARG GHIDRA_MCP_COMMIT=8cd2078e10b9ba28b188cb84ce5b9051a904b995
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends ca-certificates git; \
|
||||
git clone -q --depth 1 --branch "${GHIDRA_MCP_TAG}" https://github.com/bethington/ghidra-mcp.git /src; \
|
||||
test "$(git -C /src rev-parse HEAD)" = "${GHIDRA_MCP_COMMIT}"; \
|
||||
rm -rf /src/.git
|
||||
|
||||
FROM eclipse-temurin:21.0.12.1_1-jdk-noble AS ghidra
|
||||
|
||||
ARG GHIDRA_VERSION=12.1.2
|
||||
ARG GHIDRA_DATE=20260605
|
||||
ARG GHIDRA_SHA256=b62e81a0390618466c019c60d8c2f796ced2509c4c1aea4a37644a77272cf99d
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends ca-certificates curl unzip; \
|
||||
curl -fsSL -o /tmp/ghidra.zip \
|
||||
"https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${GHIDRA_VERSION}_build/ghidra_${GHIDRA_VERSION}_PUBLIC_${GHIDRA_DATE}.zip"; \
|
||||
echo "${GHIDRA_SHA256} /tmp/ghidra.zip" | sha256sum -c -; \
|
||||
unzip -q /tmp/ghidra.zip -d /opt; \
|
||||
mv "/opt/ghidra_${GHIDRA_VERSION}_PUBLIC" /opt/ghidra; \
|
||||
rm /tmp/ghidra.zip; \
|
||||
jython="$(ls /opt/ghidra/Extensions/Ghidra/*_Jython.zip | head -n1)"; \
|
||||
mkdir -p /opt/ghidra/Ghidra/Extensions; \
|
||||
unzip -q "$jython" -d /opt/ghidra/Ghidra/Extensions/
|
||||
|
||||
FROM eclipse-temurin:21.0.12.1_1-jdk-noble AS build
|
||||
|
||||
ARG GHIDRA_VERSION=12.1.2
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends maven; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=ghidra /opt/ghidra /opt/ghidra
|
||||
COPY --from=src /src/pom.xml /build/pom.xml
|
||||
COPY --from=src /src/src /build/src
|
||||
COPY --from=src /src/lib /build/lib
|
||||
|
||||
# Install the Ghidra framework JARs the plugin compiles against into the local
|
||||
# Maven repository at the exact version pom.xml expects (same list as
|
||||
# upstream's docker/Dockerfile).
|
||||
RUN set -eux; \
|
||||
for jar in \
|
||||
Framework/Generic Framework/SoftwareModeling Framework/Project Framework/Docking \
|
||||
Framework/Utility Framework/Gui Framework/FileSystem Framework/Help \
|
||||
Features/Base Features/Decompiler Framework/DB Framework/Emulation \
|
||||
Debug/Debugger-api Debug/Framework-TraceModeling Debug/Debugger-rmi-trace; do \
|
||||
name="${jar##*/}"; \
|
||||
mvn -q install:install-file -Dfile="/opt/ghidra/Ghidra/${jar}/lib/${name}.jar" \
|
||||
-DgroupId=ghidra -DartifactId="${name}" -Dversion="${GHIDRA_VERSION}" -Dpackaging=jar; \
|
||||
done; \
|
||||
cd /build; \
|
||||
mvn -q clean package -P headless -DskipTests
|
||||
|
||||
FROM eclipse-temurin:21.0.12.1_1-jdk-noble
|
||||
|
||||
COPY --from=ghidra /opt/ghidra /opt/ghidra
|
||||
COPY --from=build /build/target/GhidraMCP-*.jar /app/GhidraMCP.jar
|
||||
COPY --from=src /src/docker/entrypoint.sh /app/entrypoint.sh
|
||||
COPY --from=src /src /opt/ghidra-mcp-src
|
||||
COPY start.sh /usr/local/bin/heretek-ghidra-mcp
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends python3 python3-venv; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
sed -i 's/\r$//' /app/entrypoint.sh; \
|
||||
chmod 0755 /app/entrypoint.sh /usr/local/bin/heretek-ghidra-mcp; \
|
||||
python3 -m venv /opt/ghidra-mcp-bridge; \
|
||||
/opt/ghidra-mcp-bridge/bin/pip install --no-cache-dir /opt/ghidra-mcp-src; \
|
||||
rm -rf /opt/ghidra-mcp-src; \
|
||||
ln -s /opt/ghidra-mcp-bridge/bin/bridge-mcp-ghidra /usr/local/bin/bridge-mcp-ghidra; \
|
||||
mkdir -p /data /projects /home/ghidra; \
|
||||
chown 1000:1000 /data /projects /home/ghidra
|
||||
|
||||
ENV GHIDRA_HOME=/opt/ghidra \
|
||||
HOME=/home/ghidra \
|
||||
JAVA_OPTS="-Xmx4g -XX:+UseG1GC" \
|
||||
GHIDRA_MCP_BIND_ADDRESS=127.0.0.1 \
|
||||
GHIDRA_MCP_PORT=8080 \
|
||||
GHIDRA_MCP_URL=http://127.0.0.1:8080
|
||||
USER 1000:1000
|
||||
WORKDIR /app
|
||||
EXPOSE 8089
|
||||
|
||||
CMD ["heretek-ghidra-mcp"]
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
# Run the headless Ghidra REST server on the container-internal loopback
|
||||
# (127.0.0.1:${GHIDRA_MCP_PORT}) and expose upstream's MCP bridge over
|
||||
# streamable HTTP on ${MCP_PORT}/mcp. Fails closed without the auth token.
|
||||
set -eu
|
||||
|
||||
if [ -z "${GHIDRA_MCP_AUTH_TOKEN:-}" ]; then
|
||||
echo "heretek-ghidra-mcp: GHIDRA_MCP_AUTH_TOKEN is mandatory" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
/app/entrypoint.sh &
|
||||
server_pid=$!
|
||||
trap 'kill "$server_pid" 2>/dev/null || true' EXIT INT TERM
|
||||
|
||||
bridge-mcp-ghidra --transport streamable-http --mcp-host 0.0.0.0 --mcp-port "${MCP_PORT:-8089}" &
|
||||
bridge_pid=$!
|
||||
|
||||
# Exit (and let the orchestrator restart us) as soon as either process dies.
|
||||
while kill -0 "$server_pid" 2>/dev/null && kill -0 "$bridge_pid" 2>/dev/null; do
|
||||
sleep 2
|
||||
done
|
||||
kill "$server_pid" "$bridge_pid" 2>/dev/null || true
|
||||
exit 1
|
||||
@@ -0,0 +1,60 @@
|
||||
# images/godot-mcp/Dockerfile — pinned local build of tugcantopaloglu/godot-mcp (MIT)
|
||||
#
|
||||
# Upstream publishes no container image and its npm package lags the v3.1.0
|
||||
# tag, so this build compiles the tagged source (commit verified after
|
||||
# checkout) and pairs it with the official Godot 4.3-stable Linux binary
|
||||
# (verified against the release's published SHA-512). Compose tags the result
|
||||
# localhost/heretek/godot-mcp:3.1.0. The stdio MCP server is wrapped with a
|
||||
# pinned mcp-proxy so the agent gateway can reach it over streamable HTTP at
|
||||
# http://godot-mcp:8780/mcp.
|
||||
|
||||
FROM node:22.23.3-trixie-slim AS build
|
||||
|
||||
ARG GODOT_MCP_TAG=v3.1.0
|
||||
ARG GODOT_MCP_COMMIT=fcbc29e03297900b9a1fcc6c7cbae116fe9d532a
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends ca-certificates git; \
|
||||
git clone -q --depth 1 --branch "${GODOT_MCP_TAG}" https://github.com/tugcantopaloglu/godot-mcp.git /src; \
|
||||
test "$(git -C /src rev-parse HEAD)" = "${GODOT_MCP_COMMIT}"; \
|
||||
cd /src; \
|
||||
npm ci --ignore-scripts; \
|
||||
npm run build; \
|
||||
npm prune --omit=dev
|
||||
|
||||
FROM node:22.23.3-trixie-slim
|
||||
|
||||
ARG GODOT_VERSION=4.3-stable
|
||||
ARG GODOT_SHA512=fd52bb4ba8acc30ca5accd1c566d470ad7282f891ccc0995dfafabcf92bcf76280ce182bf9d80ebd885f3ed2165d01e1fc3f2928436b15498dfbd98656c2a45a
|
||||
ARG MCP_PROXY_VERSION=0.13.0
|
||||
|
||||
COPY --from=build /src/build /opt/godot-mcp/build
|
||||
COPY --from=build /src/node_modules /opt/godot-mcp/node_modules
|
||||
COPY --from=build /src/package.json /opt/godot-mcp/package.json
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends ca-certificates curl fontconfig python3 python3-venv unzip; \
|
||||
curl -fsSL -o /tmp/godot.zip "https://github.com/godotengine/godot/releases/download/${GODOT_VERSION}/Godot_v${GODOT_VERSION}_linux.x86_64.zip"; \
|
||||
echo "${GODOT_SHA512} /tmp/godot.zip" | sha512sum -c -; \
|
||||
unzip -q /tmp/godot.zip -d /opt/godot; \
|
||||
mv "/opt/godot/Godot_v${GODOT_VERSION}_linux.x86_64" /opt/godot/godot; \
|
||||
rm /tmp/godot.zip; \
|
||||
apt-get purge -y curl unzip; \
|
||||
apt-get autoremove -y; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
python3 -m venv /opt/mcp-proxy; \
|
||||
/opt/mcp-proxy/bin/pip install --no-cache-dir "mcp-proxy==${MCP_PROXY_VERSION}"; \
|
||||
ln -s /opt/mcp-proxy/bin/mcp-proxy /usr/local/bin/mcp-proxy; \
|
||||
mkdir -p /projects; \
|
||||
chown 1000:1000 /projects
|
||||
|
||||
# The node base image already ships the non-root `node` user as uid/gid 1000.
|
||||
ENV HOME=/home/node \
|
||||
GODOT_PATH=/opt/godot/godot
|
||||
USER 1000:1000
|
||||
WORKDIR /projects
|
||||
EXPOSE 8780
|
||||
|
||||
CMD ["mcp-proxy", "--host", "0.0.0.0", "--port", "8780", "--pass-environment", "--", "node", "/opt/godot-mcp/build/index.js"]
|
||||
@@ -0,0 +1,40 @@
|
||||
# images/maestro-mcp/Dockerfile — pinned local build of mobile-dev-inc/maestro (Apache-2.0)
|
||||
#
|
||||
# Maestro publishes no container image, so compose builds this one as
|
||||
# localhost/heretek/maestro-mcp:2.11.0. It installs the cli-2.11.0 release
|
||||
# archive (verified against a pinned SHA-256) on a pinned JRE and wraps the
|
||||
# stdio `maestro mcp` server with a pinned mcp-proxy so the agent gateway can
|
||||
# reach it over streamable HTTP at http://maestro-mcp:8771/mcp.
|
||||
|
||||
FROM eclipse-temurin:21.0.12.1_1-jre-noble
|
||||
|
||||
ARG MAESTRO_VERSION=2.11.0
|
||||
ARG MAESTRO_SHA256=5384593cb4e7a106489e75a821d157dd43f4e438df6bc308b72e82c685e1283a
|
||||
ARG MCP_PROXY_VERSION=0.13.0
|
||||
|
||||
COPY start.sh /usr/local/bin/heretek-maestro-mcp
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends adb ca-certificates curl python3 python3-venv unzip; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
curl -fsSL -o /tmp/maestro.zip "https://github.com/mobile-dev-inc/maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip"; \
|
||||
echo "${MAESTRO_SHA256} /tmp/maestro.zip" | sha256sum -c -; \
|
||||
unzip -q /tmp/maestro.zip -d /opt; \
|
||||
rm /tmp/maestro.zip; \
|
||||
ln -s /opt/maestro/bin/maestro /usr/local/bin/maestro; \
|
||||
python3 -m venv /opt/mcp-proxy; \
|
||||
/opt/mcp-proxy/bin/pip install --no-cache-dir "mcp-proxy==${MCP_PROXY_VERSION}"; \
|
||||
ln -s /opt/mcp-proxy/bin/mcp-proxy /usr/local/bin/mcp-proxy; \
|
||||
chmod 0755 /usr/local/bin/heretek-maestro-mcp; \
|
||||
if ! getent passwd 1000 >/dev/null; then useradd -m -u 1000 -s /bin/bash mcp; fi; \
|
||||
mkdir -p /data /home/mcp; \
|
||||
chown 1000:1000 /data /home/mcp
|
||||
|
||||
ENV HOME=/home/mcp \
|
||||
MAESTRO_CLI_NO_ANALYTICS=1
|
||||
USER 1000:1000
|
||||
WORKDIR /data
|
||||
EXPOSE 8771
|
||||
|
||||
CMD ["heretek-maestro-mcp"]
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
# Attach the local adb server to the emulator, then serve `maestro mcp` over
|
||||
# streamable HTTP. A failed connect is not fatal: the emulator may still be
|
||||
# booting, and Maestro reports device errors per tool call.
|
||||
set -eu
|
||||
adb start-server
|
||||
adb connect "${ANDROID_HOST:-android-kvm}:${ADB_PORT:-5555}" || true
|
||||
exec mcp-proxy --host 0.0.0.0 --port "${MCP_PORT:-8771}" --pass-environment -- maestro mcp
|
||||
@@ -0,0 +1,43 @@
|
||||
# images/mdb-mcp/Dockerfile — pinned local build of smadi0x86/mdb-mcp (GPL-3.0)
|
||||
#
|
||||
# Upstream publishes no image, no PyPI package and no release tag, so this
|
||||
# build pins an exact upstream commit (package version 0.2.0) and verifies it
|
||||
# after checkout. Compose tags the result localhost/heretek/mdb-mcp:0.2.0-5d03b25.
|
||||
# GDB is the debugger backend; the compose service grants only SYS_PTRACE.
|
||||
# The stdio MCP server is wrapped with a pinned mcp-proxy so the agent gateway
|
||||
# can reach it over streamable HTTP at http://mdb-mcp:8767/mcp.
|
||||
|
||||
FROM debian:trixie-20261005-slim AS src
|
||||
|
||||
ARG MDB_MCP_COMMIT=5d03b25632c572e3640fd1004e9507600ed431d3
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends ca-certificates git; \
|
||||
git init -q /src; \
|
||||
cd /src; \
|
||||
git fetch -q --depth 1 https://github.com/smadi0x86/mdb-mcp.git "${MDB_MCP_COMMIT}"; \
|
||||
git checkout -q FETCH_HEAD; \
|
||||
test "$(git rev-parse HEAD)" = "${MDB_MCP_COMMIT}"; \
|
||||
rm -rf .git
|
||||
|
||||
FROM python:3.12.15-slim-trixie
|
||||
|
||||
ARG MCP_PROXY_VERSION=0.13.0
|
||||
|
||||
COPY --from=src /src /opt/mdb-mcp-src
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends gdb; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
pip install --no-cache-dir /opt/mdb-mcp-src "mcp-proxy==${MCP_PROXY_VERSION}"; \
|
||||
useradd -m -u 1000 -s /bin/bash mcp; \
|
||||
mkdir -p /data; \
|
||||
chown 1000:1000 /data
|
||||
|
||||
USER 1000:1000
|
||||
WORKDIR /data
|
||||
EXPOSE 8767
|
||||
|
||||
CMD ["mcp-proxy", "--host", "0.0.0.0", "--port", "8767", "--pass-environment", "--", "mdb-mcp"]
|
||||
@@ -0,0 +1,39 @@
|
||||
# images/radare2-mcp/Dockerfile — pinned local build of radareorg/radare2-mcp
|
||||
#
|
||||
# No container image is published for radare2-mcp, so compose builds this one
|
||||
# as localhost/heretek/radare2-mcp:1.9.0. It installs the upstream release
|
||||
# Debian packages (MIT r2mcp 1.9.0, LGPL radare2 6.2.4 — the radare2 version
|
||||
# the 1.9.0 release was built against), each verified against a pinned SHA-256.
|
||||
# r2mcp serves MCP over HTTP natively (`-H`), so no stdio bridge is needed.
|
||||
|
||||
FROM debian:trixie-20261005-slim
|
||||
|
||||
ARG R2_VERSION=6.2.4
|
||||
ARG R2_SHA256=7019eedc0e0e87d1f53d6b8f5fc62b898567679c5efdce7fbfc557c9e7655e90
|
||||
ARG R2MCP_VERSION=1.9.0
|
||||
ARG R2MCP_SHA256=d426861a726f7e9bc13a44cc3bbfd8553c51b33fc05f1ff0eda75b433222d9e3
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends ca-certificates curl; \
|
||||
cd /tmp; \
|
||||
curl -fsSLO "https://github.com/radareorg/radare2/releases/download/${R2_VERSION}/radare2_${R2_VERSION}_amd64.deb"; \
|
||||
curl -fsSLO "https://github.com/radareorg/radare2-mcp/releases/download/${R2MCP_VERSION}/r2mcp_${R2MCP_VERSION}_amd64.deb"; \
|
||||
echo "${R2_SHA256} radare2_${R2_VERSION}_amd64.deb" | sha256sum -c -; \
|
||||
echo "${R2MCP_SHA256} r2mcp_${R2MCP_VERSION}_amd64.deb" | sha256sum -c -; \
|
||||
apt-get install -y --no-install-recommends "./radare2_${R2_VERSION}_amd64.deb" "./r2mcp_${R2MCP_VERSION}_amd64.deb"; \
|
||||
apt-get purge -y curl; \
|
||||
apt-get autoremove -y; \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/*.deb; \
|
||||
useradd -m -u 1000 -s /bin/bash mcp; \
|
||||
mkdir -p /data; \
|
||||
chown 1000:1000 /data; \
|
||||
r2mcp -v
|
||||
|
||||
USER 1000:1000
|
||||
WORKDIR /data
|
||||
EXPOSE 8765
|
||||
|
||||
# -R: read-only tool set (never -r, which enables the dangerous run_* tools);
|
||||
# -s /data: sandbox file access to the data volume.
|
||||
CMD ["r2mcp", "-H", "0.0.0.0:8765", "-R", "-s", "/data"]
|
||||
@@ -0,0 +1,32 @@
|
||||
# images/rea-mcp/Dockerfile — pinned local build of morluto/rea (MIT)
|
||||
#
|
||||
# No container image is published for REA, so compose builds this one as
|
||||
# localhost/heretek/rea-mcp:6.5.0. It installs the pinned npm release
|
||||
# [email protected] (the package behind the upstream rea-agents-6.5.0 tag) and
|
||||
# wraps its stdio MCP server (`rea mcp`) with a pinned mcp-proxy so the agent
|
||||
# gateway can reach it over streamable HTTP at http://rea-mcp:8766/mcp.
|
||||
|
||||
FROM node:22.23.3-trixie-slim
|
||||
|
||||
ARG REA_VERSION=6.5.0
|
||||
ARG MCP_PROXY_VERSION=0.13.0
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends python3 python3-venv ca-certificates; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
npm install --global --omit=dev "rea-agents@${REA_VERSION}"; \
|
||||
npm cache clean --force; \
|
||||
python3 -m venv /opt/mcp-proxy; \
|
||||
/opt/mcp-proxy/bin/pip install --no-cache-dir "mcp-proxy==${MCP_PROXY_VERSION}"; \
|
||||
ln -s /opt/mcp-proxy/bin/mcp-proxy /usr/local/bin/mcp-proxy; \
|
||||
mkdir -p /data; \
|
||||
chown 1000:1000 /data
|
||||
|
||||
# The node base image already ships the non-root `node` user as uid/gid 1000.
|
||||
ENV HOME=/home/node
|
||||
USER 1000:1000
|
||||
WORKDIR /data
|
||||
EXPOSE 8766
|
||||
|
||||
CMD ["mcp-proxy", "--host", "0.0.0.0", "--port", "8766", "--pass-environment", "--", "rea", "mcp"]
|
||||
Reference in new issue
Block a user