Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
**/node_modules
.git
.gitignore
.env
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/server-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,29 @@ jobs:
with:
node-version: 22

- name: Set up Python for ACP interoperability tests
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install pinned ACP bridge and client
run: |
python -m venv "$RUNNER_TEMP/acp"
"$RUNNER_TEMP/acp/bin/python" -m pip install -r server/runtime/acp/requirements.txt
echo "AGENT_PROXY_TEST_ACPREMOTE=$RUNNER_TEMP/acp/bin/acpremote" >> "$GITHUB_ENV"

- name: Set up Bun for pinned Pi packages
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.4.0"

- name: Install pinned Pi reference runtime
working-directory: server/runtime/acp/pi
run: |
bun install --frozen-lockfile --ignore-scripts
node patch-adapter.mjs
echo "AGENT_PI_TEST_RUNTIME=$PWD" >> "$GITHUB_ENV"

# categorygen's checks (unclassified route, category that isn't control or
# platform, classified route with no handler) only run when the generator
# does, and its only other caller is `make oapi-generate`, which needs the
Expand Down
17 changes: 17 additions & 0 deletions images/chromium-headful/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,19 @@ RUN --mount=type=cache,target=/tmp/cache/ffmpeg,sharing=locked,id=$CACHEIDPREFIX
EOT

FROM ghcr.io/kernel/neko/base:3.0.8-v1.6.0 AS neko
FROM ghcr.io/astral-sh/uv:0.8.14 AS agent-uv
FROM oven/bun:1.4.0 AS agent-bun
FROM node:22-bullseye-slim AS agent-runtime
COPY --from=agent-uv /uv /usr/local/bin/uv
COPY --from=agent-bun /usr/local/bin/bun /usr/local/bin/bun
ENV UV_PYTHON_INSTALL_DIR=/opt/kernel-agent/python
RUN uv python install 3.12.11 && uv venv --python 3.12.11 /opt/kernel-agent/venv
COPY server/runtime/acp/requirements.txt /opt/kernel-agent/requirements.txt
RUN uv pip install --python /opt/kernel-agent/venv/bin/python -r /opt/kernel-agent/requirements.txt
COPY server/runtime/acp/pi /opt/kernel-agent/pi
RUN cd /opt/kernel-agent/pi && bun install --frozen-lockfile --ignore-scripts && node patch-adapter.mjs
COPY server/runtime/acp/catalog.json /opt/kernel-agent/catalog.json

FROM node:22-bullseye-slim AS node-22
FROM docker.io/ubuntu:22.04

Expand Down Expand Up @@ -397,4 +410,8 @@ RUN useradd -m -s /bin/bash kernel
COPY shared/envoy/bake-certs.sh /usr/local/bin/bake-certs.sh
RUN chmod +x /usr/local/bin/bake-certs.sh && /usr/local/bin/bake-certs.sh && rm /usr/local/bin/bake-certs.sh

COPY --from=agent-runtime /opt/kernel-agent /opt/kernel-agent
COPY --from=agent-bun /usr/local/bin/bun /usr/local/bin/bun
ENV AGENT_CONFIG_PATH=/opt/kernel-agent/catalog.json

ENTRYPOINT [ "/wrapper" ]
17 changes: 17 additions & 0 deletions images/chromium-headless/image/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ RUN --mount=type=cache,target=/tmp/cache/ffmpeg,sharing=locked,id=$CACHEIDPREFIX
rm -rf /tmp/ffmpeg*
EOT

FROM ghcr.io/astral-sh/uv:0.8.14 AS agent-uv
FROM oven/bun:1.4.0 AS agent-bun
FROM node:22-bullseye-slim AS agent-runtime
COPY --from=agent-uv /uv /usr/local/bin/uv
COPY --from=agent-bun /usr/local/bin/bun /usr/local/bin/bun
ENV UV_PYTHON_INSTALL_DIR=/opt/kernel-agent/python
RUN uv python install 3.12.11 && uv venv --python 3.12.11 /opt/kernel-agent/venv
COPY server/runtime/acp/requirements.txt /opt/kernel-agent/requirements.txt
RUN uv pip install --python /opt/kernel-agent/venv/bin/python -r /opt/kernel-agent/requirements.txt
COPY server/runtime/acp/pi /opt/kernel-agent/pi
RUN cd /opt/kernel-agent/pi && bun install --frozen-lockfile --ignore-scripts && node patch-adapter.mjs
COPY server/runtime/acp/catalog.json /opt/kernel-agent/catalog.json

FROM node:22-bullseye-slim AS node-22
FROM docker.io/ubuntu:22.04

Expand Down Expand Up @@ -285,4 +298,8 @@ RUN esbuild /tmp/playwright-daemon.ts \
--external:esbuild \
&& rm /tmp/playwright-daemon.ts /tmp/page-target-id-cache.ts /tmp/webmcp.ts

COPY --from=agent-runtime /opt/kernel-agent /opt/kernel-agent
COPY --from=agent-bun /usr/local/bin/bun /usr/local/bin/bun
ENV AGENT_CONFIG_PATH=/opt/kernel-agent/catalog.json

ENTRYPOINT [ "/wrapper" ]
9 changes: 9 additions & 0 deletions server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Configure the server using environment variables:
| `MAX_SIZE_MB` | `500` | Default maximum file size (MB) |
| `OUTPUT_DIR` | `.` | Directory to save recordings |
| `FFMPEG_PATH` | `ffmpeg` | Path to the ffmpeg binary |
| `AGENT_CONFIG_PATH` | empty | Trusted launch catalog enabling the optional ACP WebSocket proxy |

#### Example Configuration

Expand All @@ -68,6 +69,14 @@ export OUTPUT_DIR=/tmp/recordings
./bin/api
```

### Optional ACP agents

The [ACP API](lib/agentproxy/README.md) exposes one WebSocket endpoint using
`acpremote expose` and its per-connection process lifecycle. Browser images bundle
a pinned Pi runtime and declarative configuration GET/PUT endpoints. Configure a
provider credential binding before connecting. An empty `AGENT_CONFIG_PATH`
disables these routes when running the server separately.

### API Documentation

- **YAML Spec**: `GET /spec.yaml`
Expand Down
15 changes: 15 additions & 0 deletions server/cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
serverpkg "github.com/kernel/kernel-images/server"
"github.com/kernel/kernel-images/server/cmd/api/api"
"github.com/kernel/kernel-images/server/cmd/config"
"github.com/kernel/kernel-images/server/lib/agentproxy"
"github.com/kernel/kernel-images/server/lib/chromedriverproxy"
"github.com/kernel/kernel-images/server/lib/devtoolsproxy"
"github.com/kernel/kernel-images/server/lib/events"
Expand Down Expand Up @@ -285,6 +286,20 @@ func main() {
apiService.HandleProcessAttachWS(w, r, id, wsRegistry)
})

if config.AgentConfigPath != "" {
agentConfig, err := agentproxy.Load(config.AgentConfigPath)
if err != nil {
slogger.Error("agent endpoints disabled: invalid launch catalog", "err", err)
} else {
agents, err := agentproxy.New(ctx, agentConfig, slogger, wsRegistry)
if err != nil {
slogger.Error("agent endpoints disabled", "err", err)
} else {
r.Handle("/agent/v1/*", agents)
}
}
}

// Serve extension files for Chrome policy-installed extensions
// This allows Chrome to download .crx and update.xml files via HTTP
extensionsDir := "/home/kernel/extensions"
Expand Down
4 changes: 4 additions & 0 deletions server/cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type Config struct {
// Server configuration
Port int `envconfig:"PORT" default:"10001"`

// Optional trusted launch catalog. Empty leaves the ACP endpoints disabled.
AgentConfigPath string `envconfig:"AGENT_CONFIG_PATH" default:""`

// Port for the Prometheus metrics endpoint. Served on a separate
// listener so scrapes bypass the scale-to-zero middleware and the
// external API surface.
Expand Down Expand Up @@ -89,6 +92,7 @@ func (c *Config) LogValue() slog.Value {
}
return slog.GroupValue(
slog.Int("port", c.Port),
slog.String("agent_config_path", c.AgentConfigPath),
slog.Int("metrics_port", c.MetricsPort),
slog.Int("frame_rate", c.FrameRate),
slog.Int("display_num", c.DisplayNum),
Expand Down
Loading
Loading