f33a563003
- Add ToolManifest Pydantic models with validators for ports, mounts, health checks, and traefik config - Implement in-memory ToolRegistry with YAML loading and built-in manifest scanning - Add FastAPI CRUD routes for listing, retrieving, and creating tool manifests - Include built-in manifests for runfusion and code-server - Harden web Dockerfile with unprivileged nginx and port 8080 - Add tool manifest specification documentation and architecture updates Fusion-Task-Id: FN-003
210 lines
8.9 KiB
Markdown
210 lines
8.9 KiB
Markdown
# Tool Manifest Specification
|
||
|
||
> Canonical schema reference for Headquarter's manifest-driven tool registry.
|
||
> Version: 1.0.0 — aligned with FN-003.
|
||
|
||
## Overview
|
||
|
||
Headquarter is a manifest-driven platform: every containerized tool (RunFusion, code-server, and future tools) is declared by a YAML manifest. The orchestration backend reads these manifests to generate Docker Compose services, Traefik routing labels, volume mounts, and resource constraints.
|
||
|
||
**Design goal:** Adding a new standard container tool requires only a YAML manifest—no backend code changes.
|
||
|
||
## Manifest File Format
|
||
|
||
Manifests are YAML files with a single top-level mapping. They are validated on load by Pydantic v2 models.
|
||
|
||
### Built-in location
|
||
|
||
Built-in manifests live in `apps/api/app/tools/manifests/*.yml` and are loaded automatically on API startup.
|
||
|
||
### Minimal valid manifest
|
||
|
||
```yaml
|
||
id: my-tool
|
||
name: My Tool
|
||
image: my-org/my-tool:latest
|
||
ports:
|
||
- container_port: 8080
|
||
primary: true
|
||
traefik:
|
||
enabled: false
|
||
```
|
||
|
||
## Field Reference
|
||
|
||
### `ToolManifest` (top level)
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|-------|------|----------|---------|-------------|
|
||
| `id` | `string` | yes | — | Lowercase slug with hyphens only (`^[a-z0-9\-]+$`). Used as the registry key. |
|
||
| `name` | `string` | yes | — | Human-readable tool name. |
|
||
| `description` | `string` | no | `""` | Short description of the tool. |
|
||
| `version` | `string` | no | `"1.0.0"` | Manifest version (semver-ish). |
|
||
| `image` | `string` | yes | — | Docker image reference. |
|
||
| `runtime_command` | `string[] \| null` | no | `null` | Override the container default command. |
|
||
| `runtime_entrypoint` | `string[] \| null` | no | `null` | Override the container entrypoint. |
|
||
| `runtime_user` | `string \| null` | no | `null` | User to run as inside the container. |
|
||
| `runtime_working_dir` | `string \| null` | no | `null` | Working directory inside the container. |
|
||
| `ports` | `PortConfig[]` | no | `[]` | Exposed ports. |
|
||
| `workspace_mounts` | `MountConfig[]` | no | `[]` | Workspace volume mounts (project-scoped). |
|
||
| `config_mounts` | `MountConfig[]` | no | `[]` | Config volume mounts (user or tool-scoped). |
|
||
| `env` | `dict<string, string>` | no | `{}` | Static environment variables. |
|
||
| `secrets` | `SecretRef[]` | no | `[]` | Secrets injected as environment variables. |
|
||
| `health_check` | `HealthCheckConfig \| null` | no | `null` | Health check definition. |
|
||
| `resource_limits` | `ResourceLimits \| null` | no | `null` | CPU and memory constraints. |
|
||
| `executable` | `ExecutableConfig \| null` | no | `null` | Node.js runtime metadata for executable environments. |
|
||
| `traefik` | `TraefikConfig \| null` | no | `null` | Traefik routing configuration. |
|
||
|
||
### `PortConfig`
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|-------|------|----------|---------|-------------|
|
||
| `container_port` | `int` | yes | — | Port inside the container. Range: 1–65535. |
|
||
| `protocol` | `"tcp" \| "udp"` | no | `"tcp"` | Transport protocol. |
|
||
| `name` | `string \| null` | no | `null` | Logical name, e.g. `"http"`, `"websocket"`. |
|
||
| `primary` | `bool` | no | `false` | The port used for default routing and health checks. |
|
||
|
||
### `MountConfig`
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|-------|------|----------|---------|-------------|
|
||
| `type` | `"volume" \| "bind"` | no | `"volume"` | Mount type. |
|
||
| `source_pattern` | `string` | yes | — | Template pattern resolved at spawn time, e.g. `"{project_repo}"`. |
|
||
| `target` | `string` | yes | — | Absolute path inside the container. Must start with `/`. |
|
||
| `read_only` | `bool` | no | `false` | Mount read-only. |
|
||
|
||
### `SecretRef`
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|-------|------|----------|---------|-------------|
|
||
| `name` | `string` | yes | — | Secret identifier in the secret store. |
|
||
| `env_var` | `string` | yes | — | Name of the environment variable injected into the container. |
|
||
| `required` | `bool` | no | `true` | Whether the tool fails to start if the secret is missing. |
|
||
|
||
### `HealthCheckConfig`
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|-------|------|----------|---------|-------------|
|
||
| `type` | `"http" \| "tcp" \| "command"` | no | `"http"` | Health check mechanism. |
|
||
| `path` | `string \| null` | no | `null` | HTTP path. Required when `type == "http"`. |
|
||
| `command` | `string[] \| null` | no | `null` | Command to execute. Required when `type == "command"`. |
|
||
| `port` | `int \| null` | no | `null` | Override port; defaults to the primary port if unset. |
|
||
| `interval_seconds` | `int` | no | `10` | Check interval. ≥ 1. |
|
||
| `timeout_seconds` | `int` | no | `5` | Check timeout. ≥ 1. |
|
||
| `retries` | `int` | no | `3` | Retries before marking unhealthy. ≥ 1. |
|
||
| `start_period_seconds` | `int` | no | `5` | Grace period before checks count. ≥ 0. |
|
||
|
||
### `ResourceLimits`
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|-------|------|----------|---------|-------------|
|
||
| `cpus` | `float \| null` | no | `null` | CPU limit. If set, ≥ 0.01. |
|
||
| `memory_mb` | `int \| null` | no | `null` | Memory limit in MiB. If set, ≥ 16. |
|
||
| `memory_swap_mb` | `int \| null` | no | `null` | Swap limit in MiB. `-1` disables swap limit. |
|
||
|
||
### `ExecutableConfig`
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|-------|------|----------|---------|-------------|
|
||
| `node_version` | `string \| null` | no | `null` | Expected Node.js version, e.g. `"22"`, `"lts"`. |
|
||
| `npm_version` | `string \| null` | no | `null` | Expected npm version. |
|
||
| `package_manager` | `"npm" \| "pnpm" \| "yarn" \| "bun"` | no | `"npm"` | Preferred package manager. |
|
||
| `bootstrap_commands` | `string[]` | no | `[]` | One-time setup commands run on first start. |
|
||
| `install_commands` | `string[]` | no | `[]` | Commands run before the main command. |
|
||
|
||
### `TraefikConfig`
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|-------|------|----------|---------|-------------|
|
||
| `enabled` | `bool` | no | `true` | Whether Traefik routing is generated for this tool. |
|
||
| `subdomain_prefix` | `string \| null` | no | `null` | Subdomain prefix. Defaults to the tool `id`. |
|
||
| `port` | `int \| null` | no | `null` | Container port to route traffic to. |
|
||
| `middlewares` | `string[]` | no | `[]` | Traefik middleware names to apply. |
|
||
| `strip_prefix` | `bool` | no | `false` | Strip path prefix before forwarding. |
|
||
| `entrypoint` | `string \| null` | no | `null` | Override the environment default Traefik entrypoint. |
|
||
| `cert_resolver` | `string \| null` | no | `null` | Override the environment default cert resolver. |
|
||
|
||
## Validation Rules
|
||
|
||
1. `id` must match `^[a-z0-9\-]+$` (lowercase, digits, hyphens only).
|
||
2. `MountConfig.target` must be an absolute path (`starts with "/"`).
|
||
3. When `health_check.type == "http"`, `path` must be set and non-empty.
|
||
4. When `health_check.type == "command"`, `command` must be set and non-empty.
|
||
5. `container_port` must be between 1 and 65535.
|
||
6. `cpus`, if set, must be ≥ 0.01.
|
||
7. `memory_mb`, if set, must be ≥ 16.
|
||
8. If `traefik.enabled` is `true`, at least one port must have `primary: true`.
|
||
|
||
## Example: RunFusion Manifest
|
||
|
||
```yaml
|
||
id: runfusion
|
||
name: RunFusion
|
||
description: Executable Node.js environment for running and developing applications.
|
||
version: "1.0.0"
|
||
image: node:22-slim
|
||
runtime_working_dir: /workspace
|
||
ports:
|
||
- container_port: 8080
|
||
protocol: tcp
|
||
name: http
|
||
primary: true
|
||
workspace_mounts:
|
||
- type: volume
|
||
source_pattern: "{project_repo}"
|
||
target: /workspace
|
||
read_only: false
|
||
config_mounts:
|
||
- type: volume
|
||
source_pattern: "{user_config}/runfusion"
|
||
target: /home/node/.config
|
||
read_only: false
|
||
env:
|
||
NODE_ENV: development
|
||
health_check:
|
||
type: http
|
||
path: /
|
||
port: 8080
|
||
interval_seconds: 10
|
||
timeout_seconds: 5
|
||
retries: 3
|
||
start_period_seconds: 10
|
||
resource_limits:
|
||
cpus: 2.0
|
||
memory_mb: 2048
|
||
memory_swap_mb: -1
|
||
executable:
|
||
node_version: "22"
|
||
package_manager: npm
|
||
bootstrap_commands: []
|
||
install_commands: []
|
||
traefik:
|
||
enabled: true
|
||
subdomain_prefix: runfusion
|
||
port: 8080
|
||
middlewares: []
|
||
strip_prefix: false
|
||
```
|
||
|
||
## Extension Guide: Adding a New Tool
|
||
|
||
To add a new standard container tool:
|
||
|
||
1. Create a new YAML file in `apps/api/app/tools/manifests/{tool-id}.yml`.
|
||
2. Populate all required fields (`id`, `name`, `image`, `ports`).
|
||
3. Set `traefik.enabled: true` and mark one port as `primary: true` if the tool needs HTTP routing.
|
||
4. Declare `workspace_mounts` and `config_mounts` as needed.
|
||
5. Restart the API (or call `registry.load_builtin_manifests()`).
|
||
|
||
No backend code changes are required for standard containers that expose an HTTP port and need volume mounts.
|
||
|
||
## Registry API
|
||
|
||
The in-memory registry exposes FastAPI routes under `/api/v1/tools`:
|
||
|
||
- `GET /api/v1/tools` — list all registered manifests.
|
||
- `GET /api/v1/tools/{id}` — retrieve a single manifest.
|
||
- `POST /api/v1/tools` — register a new manifest (returns 409 if `id` already exists).
|
||
|
||
Built-in manifests are loaded automatically on application startup via the FastAPI lifespan context manager.
|