diff --git a/docs/architecture.md b/docs/architecture.md index 3411c8b..c15f280 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -5,11 +5,11 @@ ## 1. Overview & Goals -Headquarter is a hosted control plane where authenticated users create projects, connect Git repositories, and spawn containerized tools (RunFusion, code-server, and future tools). The platform is manifest-driven and provider-abstracted so new tools, Git providers, runtimes, and access providers can be added without rewriting core orchestration logic. +Headquarter is a hosted control plane where authenticated users create projects, connect Git repositories, and spawn containerized tools (OpenCode, code-server, and future tools). The platform is manifest-driven and provider-abstracted so new tools, Git providers, runtimes, and access providers can be added without rewriting core orchestration logic. **Product purpose:** Give individual developers and small teams a self-hosted alternative to cloud IDEs and CI dashboards by combining Git-backed project workspaces with on-demand tool containers, all routed through a unified subdomain scheme. -**MVP scope:** Single-user projects, Authentik OIDC auth, Docker runtime, Traefik subdomain routing, Portainer-managed Docker Compose deployment. The MVP supports two built-in tools (RunFusion and code-server) and provides extension points for additional tools, Git providers, and runtimes. +**MVP scope:** Single-user projects, Authentik OIDC auth, Docker runtime, Traefik subdomain routing, Portainer-managed Docker Compose deployment. The MVP supports two built-in tools (OpenCode and code-server) and provides extension points for additional tools, Git providers, and runtimes. **Non-goals (explicitly out of MVP scope):** - Multi-user teams or shared projects @@ -43,7 +43,7 @@ flowchart TB subgraph Runtime R[Docker Compose Stack
Portainer-managed] - TC[Tool Containers
RunFusion / code-server] + TC[Tool Containers
OpenCode / code-server] end U -->|HTTPS| T @@ -81,27 +81,54 @@ flowchart TB - Consume the FastAPI backend via a typed API client layer - Handle Vite environment variables for runtime configuration +**Technology Stack:** +- React 19 + TypeScript with strict mode +- Vite for build tooling +- React Router v7 for client-side routing +- TanStack Query v5 for server state management +- Zustand for client state management (auth store) +- Tailwind CSS v4 for styling +- Headless UI for accessible components +- Heroicons for iconography + +**Project Structure:** +``` + apps/web/src/ + ├── api/ # API client and error handling + ├── auth/ # OIDC utilities and AuthProvider + ├── components/ # Reusable UI components (Header, Sidebar, RouteGuard, DashboardLayout) + ├── pages/ # Page components (Dashboard, Projects, Tools, Settings, etc.) + ├── stores/ # Zustand stores (auth store) + ├── types/ # TypeScript type definitions matching backend schemas + └── router.tsx # React Router configuration +``` + **Routing:** -- `/` — Dashboard -- `/projects` — Project list -- `/projects/new` — Create project -- `/projects/:id/repositories` — Repository management -- `/tools` — Tool registry and spawn surface -- `/tools/spawn` — Spawn tool form -- `/settings` — User and platform settings -- `/access/:instanceId` — Tool access URL presentation +- `/` — Dashboard (protected) +- `/projects` — Project list (protected) +- `/projects/new` — Create project (protected) +- `/projects/:id` — Project detail (protected) +- `/projects/:id/edit` — Edit project (protected) +- `/repositories` — Repository management (protected) +- `/tools` — Tool registry and spawn surface (protected) +- `/settings` — User and platform settings (protected) +- `/login` — Login redirect (public) +- `/callback` — OIDC callback handler (public) **Auth state:** -- Managed via a central auth context/provider +- Managed via Zustand auth store (`useAuthStore`) - States: `loading`, `authenticated`, `unauthenticated`, `error` -- Access token stored in **httpOnly cookie** (recommended) or secure storage; never `localStorage` for sensitive tokens -- On 401 from API, redirect to Authentik login +- Access token stored in `localStorage` (MVP simplification; httpOnly cookie recommended for production) +- PKCE flow for OIDC authentication +- On 401 from API, redirect to login **API client conventions:** -- Base URL from `VITE_API_BASE_URL` +- Base URL from `VITE_API_URL` - JSON request/response with standard HTTP status codes -- Normalize errors into a consistent `{ message, statusCode, details? }` shape -- Include bearer token or cookie credentials on every request +- `ApiError` class for consistent error handling +- Bearer token injected via `Authorization` header +- Debug mode request/response logging +- Typed API methods for all endpoints ### 3.2 Backend (`apps/api/`) @@ -233,7 +260,7 @@ All tables use `uuid` primary keys. All models include `created_at` and `updated | Column | Type | Constraints | Default | Notes | |--------|------|-------------|---------|-------| | `id` | `UUID` | `PRIMARY KEY` | `uuid_generate_v4()` | | -| `key` | `VARCHAR(100)` | `UNIQUE`, `NOT NULL`, `INDEX` | | Machine identifier (e.g., `runfusion`, `code-server`) | +| `key` | `VARCHAR(100)` | `UNIQUE`, `NOT NULL`, `INDEX` | | Machine identifier (e.g., `opencode`, `code-server`) | | `name` | `VARCHAR(255)` | `NOT NULL` | | Human-readable name | | `version` | `VARCHAR(50)` | `NOT NULL` | `'1.0.0'` | | | `description` | `TEXT` | `NULLABLE` | `NULL` | | @@ -449,7 +476,7 @@ Tools are defined by manifests that declare runtime behavior, resource needs, an | Field | Type | Required | Description | |-------|------|----------|-------------| -| `key` | `str` | Yes | Machine identifier (`runfusion`, `code-server`) | +| `key` | `str` | Yes | Machine identifier (`opencode`, `code-server`) | | `name` | `str` | Yes | Human-readable name | | `version` | `str` | No | SemVer string; default `1.0.0` | | `description` | `str` | No | Markdown-friendly description | @@ -730,7 +757,7 @@ Variable interpolation rules: Example: ``` -https://runfusion-myapp-alice.tools.example.com +https://opencode-myapp-alice.tools.example.com https://code-server-myapp-alice.tools.example.com ``` @@ -780,6 +807,25 @@ Tool containers **must** attach to the external Traefik Docker network. If the n - **TLS is enabled by default** for all tool instances. - HTTP-only mode (`web` entrypoint, no TLS) is supported for local development via environment configuration. +### 10.6 Auth Proxy for Tool Instances + +Spawned tools are protected behind the platform's authentication via Traefik forwardAuth middleware: + +1. **Middleware Configuration:** + - Traefik forwards incoming requests to `/api/v1/auth/validate` + - The endpoint validates the Bearer token and returns 200 for authenticated users + - Unauthenticated requests receive 401 and are blocked + +2. **Tool Configuration:** + - code-server built-in auth is disabled (`PASSWORD: ""`) + - OpenCode relies entirely on the platform auth layer + - Tools run on internal networks only, inaccessible directly + +3. **Security Model:** + - Only platform-authenticated users can access spawned tools + - Each tool instance has its own subdomain with isolated routing + - No shared containers between users or projects + --- ## 11. Storage Layout diff --git a/docs/conversation-handoff.md b/docs/conversation-handoff.md index aae6c1c..635a607 100644 --- a/docs/conversation-handoff.md +++ b/docs/conversation-handoff.md @@ -62,7 +62,7 @@ This document captures the key architectural decisions, assumptions, and open lo - **FN-004:** Backend Foundation — depends on the PostgreSQL domain model in Section 4 - **FN-005:** Frontend Foundation — depends on component boundaries in Section 3.1 - **FN-006:** Deployment Config — depends on Traefik routing model in Section 10 -- **FN-008:** RunFusion POC — depends on spawn lifecycle in Section 8 and Docker runtime in Section 9 +- **FN-008:** OpenCode POC — depends on spawn lifecycle in Section 8 and Docker runtime in Section 9 - **FN-009:** Config & Secrets — depends on storage layout in Section 11 and security in Section 13 - **FN-010:** code-server Spawn — depends on tool manifest in Section 7 and Docker runtime in Section 9 - **FN-011:** Git Provider — depends on Git provider abstraction in Section 5 diff --git a/docs/deployment.md b/docs/deployment.md index d301f98..8acc339 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -2,61 +2,132 @@ ## Overview -The MVP deployment target is a **Portainer-managed Docker Compose stack** with an existing **Traefik** reverse proxy. +The MVP deployment target is a **Portainer-managed Docker Compose stack** with **Traefik** as the reverse proxy. All services run in Docker containers with automatic TLS via Let's Encrypt. -This document covers the scaffold-level deployment assumptions created in FN-002. Detailed deployment automation (dynamic labels for spawned tool containers, secret rotation, CI/CD pipelines) is follow-up scope for **FN-006**. +## Architecture -## Stack Assumptions +``` +Internet + | + v +Traefik (443/80) ──► Let's Encrypt TLS + | + ├──► api.example.com ──► FastAPI backend + ├──► example.com ──► React frontend + ├──► traefik.example.com ──► Traefik dashboard + └──► {tool}-{project}-{user}.tools.example.com ──► Spawned tool containers +``` -- **Reverse proxy**: Traefik (already running on the target host) -- **Orchestration**: Portainer managing Docker Compose stacks -- **Network**: External Traefik network named `traefik` (or as configured) -- **Routing**: Subdomain-based (`{tool}-{project}-{user}.tools.{ROOT_DOMAIN}`) -- **TLS**: Traefik cert resolver (e.g., `letsencrypt` or Cloudflare) +## Prerequisites + +- Docker and Docker Compose +- A domain with DNS A/AAAA records pointing to your server +- Ports 80 and 443 open + +## Quick Start + +### 1. Configure Environment + +Copy the production environment example and fill in all values: + +```bash +cp deploy/.env.example deploy/.env +``` + +Required variables: + +| Variable | Description | Example | +|----------|-------------|---------| +| `ROOT_DOMAIN` | Your domain | `example.com` | +| `ACME_EMAIL` | Let's Encrypt contact email | `admin@example.com` | +| `POSTGRES_PASSWORD` | Database password | (strong random) | +| `AUTHENTIK_CLIENT_SECRET` | OIDC client secret | (from Authentik) | +| `SECRET_ENCRYPTION_KEY` | Fernet encryption key | (32-byte base64) | + +### 2. Deploy Locally (Testing) + +```bash +docker compose -f docker-compose.prod.yml up --build -d +``` + +This starts: Traefik, API, web frontend, and PostgreSQL. + +### 3. Deploy to Production (Portainer) + +1. In Portainer, create a new stack +2. Upload `deploy/portainer-stack.yml` +3. Set environment variables from `deploy/portainer.env.example` +4. Deploy the stack ## Deployment Files -The following deployment files are part of the scaffold: - | File | Purpose | |------|---------| | `docker-compose.yml` | Local development (API, web, Postgres) | -| `docker-compose.traefik.yml` | Deployment overlay with Traefik labels | -| `deploy/portainer.env.example` | Deployment environment variables | -| `deploy/traefik-labels.example.yml` | Example Traefik labels for services | -| `deploy/README.md` | Deploy skeleton usage notes | +| `docker-compose.prod.yml` | Production compose with Traefik | +| `deploy/portainer-stack.yml` | Portainer stack definition | +| `deploy/portainer.env.example` | Portainer environment variables | +| `deploy/.env.example` | Production environment variables | -## Environment Variables +## Traefik Configuration -See `.env.example` for the full variable list. Key deployment variables: +Traefik handles all routing and TLS: -```env -APP_NAME=Headquarter -ROOT_DOMAIN=example.com -TOOL_DOMAIN=tools.example.com -TRAEFIK_NETWORK=traefik -TRAEFIK_ENTRYPOINT=websecure -TRAEFIK_CERT_RESOLVER=letsencrypt -AUTHENTIK_ISSUER_URL=https://auth.example.com/application/o/headquarter/ -AUTHENTIK_CLIENT_ID= -AUTHENTIK_CLIENT_SECRET= -POSTGRES_PASSWORD= -SECRET_ENCRYPTION_KEY= +- **Entrypoints**: `web` (80) → redirects to `websecure` (443) +- **Certificates**: Let's Encrypt via TLS challenge +- **Dashboard**: Available at `traefik.${ROOT_DOMAIN}` (protected by middleware) +- **Metrics**: Prometheus metrics exposed on `/metrics` + +### Subdomain Routing + +Tool containers are routed via subdomains: + +``` +{tool}-{project}-{user}.tools.{ROOT_DOMAIN} ``` -## Local vs Production +Example: `vscode-myproject-john.tools.example.com` -- **Local**: `docker compose up --build -d` uses `docker-compose.yml` only. -- **Production**: Portainer deploys the stack using the main compose file plus the Traefik overlay. +## DNS Requirements -## Scoped Secrets +Create DNS A records for: -Do not commit real secrets. Use: +- `example.com` → your server IP +- `*.example.com` → your server IP (wildcard for subdomains) +- `*.tools.example.com` → your server IP (tool subdomains) -- Portainer environment variables (stored in Portainer, not in Git) -- `.env` files (ignored by Git, documented in `.env.example`) -- Docker secrets (to be evaluated in FN-006) +## Security -## Follow-up Work +- All services communicate over HTTPS +- Traefik adds security headers (HSTS, XSS protection, etc.) +- Database is not exposed externally +- Secrets are injected via environment variables -- **FN-006**: Full deployment automation, dynamic Traefik labels for spawned tool containers, Portainer stack definitions, and CI/CD integration. +## Updating + +To update the deployment: + +```bash +# Pull new images +docker compose -f docker-compose.prod.yml pull + +# Restart services +docker compose -f docker-compose.prod.yml up -d +``` + +## Troubleshooting + +Check Traefik logs: +```bash +docker logs traefik +``` + +Check service health: +```bash +docker compose -f docker-compose.prod.yml ps +``` + +Verify certificates: +```bash +curl -v https://api.example.com +``` diff --git a/docs/development.md b/docs/development.md index 000a086..fc92ada 100644 --- a/docs/development.md +++ b/docs/development.md @@ -31,6 +31,29 @@ Copy the frontend environment example: cp apps/web/.env.example apps/web/.env ``` +### Frontend Authentication (OIDC) + +The frontend uses OpenID Connect (OIDC) with PKCE for authentication. Configure the following environment variables in `apps/web/.env`: + +| Variable | Description | Example | +|----------|-------------|---------| +| `VITE_API_URL` | Backend API base URL | `http://localhost:8000` | +| `VITE_OIDC_ISSUER` | OIDC provider issuer URL | `https://authentik.example.com/application/o/headquarter` | +| `VITE_OIDC_CLIENT_ID` | OIDC client ID | `headquarter-web` | +| `VITE_OIDC_REDIRECT_URI` | Post-login redirect URL | `http://localhost:5173/callback` | + +**Authentication Flow:** +1. User clicks login → redirected to OIDC provider authorize endpoint +2. User authenticates with provider +3. Provider redirects to `/callback` with authorization code +4. Frontend exchanges code for access token (PKCE) +5. Token stored in `localStorage`, user info fetched from `/api/v1/users/me` + +**Logout:** +- Clears local token +- Redirects to login page +- User can re-authenticate via OIDC flow + ## Running Locally ### Frontend only @@ -195,3 +218,44 @@ Security rules for the package: - Credential models store **only** `encrypted_payload` — no plaintext `token` or `private_key` fields. - SSH private keys are encrypted before storage; the field uses `repr=False`. - Real encryption of the payload is deferred to FN-009; the current placeholder is base64-only. + +## Tool Spawn Workflow + +The platform supports spawning development tools (e.g., code-server) as Docker containers via Docker Compose. + +### Architecture + +1. **Tool Manifest** (`apps/api/app/tools/manifests/*.yml`): + - Defines Docker image, ports, volumes, environment variables, health checks + - Loaded into in-memory registry at application startup + +2. **Spawn Service** (`apps/api/app/services/spawn.py`): + - Generates Docker Compose service definitions from manifests + - Handles container lifecycle: spawn, stop, status polling + - Integrates Traefik label generation for subdomain routing + +3. **API Endpoints** (`apps/api/app/routers/tool_instances.py`): + - `POST /projects/{id}/tool-instances` — Spawn a new tool instance + - `POST /projects/{id}/tool-instances/{id}/stop` — Stop a running instance + - `POST /projects/{id}/tool-instances/{id}/start` — Restart a stopped instance + - `GET /projects/{id}/tool-instances/{id}/status` — Get container status + +4. **Frontend UI**: + - `/tools/spawn` — Form to select tool, project, and spawn + - `/projects/{id}/instances/{id}` — Instance detail with status, controls, and "Open Tool" link + +### Auth Proxy + +Spawned tools are protected behind Traefik forwardAuth middleware: +- Traefik forwards requests to `/api/v1/auth/validate` for session validation +- code-server built-in auth is disabled (`PASSWORD: ""`) +- Only authenticated platform users can access spawned tools + +### Local Development + +Ensure Docker socket is accessible and the `tools` network exists: +```bash +docker network create tools # One-time setup +``` + +Spawned containers use the `tools` network for Traefik routing. diff --git a/docs/mvp-scope.md b/docs/mvp-scope.md index 5f5160f..7a4d613 100644 --- a/docs/mvp-scope.md +++ b/docs/mvp-scope.md @@ -7,7 +7,7 @@ ## 1. Product Vision -Headquarter is a hosted workspace and tool-orchestration platform for developers who want self-hosted control over their development environments. It gives authenticated users a single dashboard to create Git-backed projects, connect repositories from any provider, and spawn containerized tools—starting with RunFusion and code-server—on demand, each accessible via its own HTTPS subdomain. Headquarter is for individual developers and small teams who outgrow cloud IDEs but do not want to build their own orchestration layer from scratch. +Headquarter is a hosted workspace and tool-orchestration platform for developers who want self-hosted control over their development environments. It gives authenticated users a single dashboard to create Git-backed projects, connect repositories from any provider, and spawn containerized tools—starting with OpenCode and code-server—on demand, each accessible via its own HTTPS subdomain. Headquarter is for individual developers and small teams who outgrow cloud IDEs but do not want to build their own orchestration layer from scratch. --- @@ -36,7 +36,7 @@ An MVP user can complete the following end-to-end flows without assistance: - The public key is displayed to the user for manual registration at the provider, or registered automatically via the provider adapter when available. ### 2.5 Spawn a Tool Instance -- User navigates to "Tools" and selects a tool (RunFusion or code-server). +- User navigates to "Tools" and selects a tool (OpenCode or code-server). - User chooses a project and optional config overrides. - The backend generates a Docker Compose service definition, Traefik labels, and starts the container. - The tool instance receives workspace mounts, config mounts, and secret injection. @@ -70,7 +70,7 @@ An MVP user can complete the following end-to-end flows without assistance: - **Project management** (CRUD, ownership-based) - **Repository connections** with provider-agnostic Git URL storage - **Per-repository SSH key generation** (Ed25519) with encrypted private-key storage -- **Tool registry** with manifest-driven definitions for RunFusion and code-server +- **Tool registry** with manifest-driven definitions for OpenCode and code-server - **Tool instance spawning** via Docker Compose with Traefik subdomain routing - **Tool instance lifecycle** (start, stop, health checks, status tracking) - **Persistent config storage** at global, user, project, and tool-instance scopes @@ -111,12 +111,12 @@ Slices are ordered by dependency. Each slice corresponds to a task on the Fusion | 2 | **FN-019** | Architecture & Specification | Enhanced `docs/architecture.md`, `docs/mvp-scope.md`, doc validation tests | | 3 | **FN-004** | Backend Foundation | Domain models, Alembic migrations, auth boundaries, secret encryption, API routers | | 4 | **FN-005** | Frontend Foundation | Auth shell, navigation, placeholder pages, API client, config layer | -| 5 | **FN-003** | Tool Registry | Manifest schema, in-memory registry, built-in RunFusion/code-server manifests, FastAPI routes | +| 5 | **FN-003** | Tool Registry | Manifest schema, in-memory registry, built-in OpenCode/code-server manifests, FastAPI routes | | 6 | **FN-006** | Deployment Config | Traefik label generator, production Compose stacks, Portainer stack definition | | 7 | **FN-011** | Git Provider Model | Provider abstraction, SSH key lifecycle, credential models, repository connection | | 8 | **FN-009** | Config & Secrets | Encrypted storage, runtime injection, frontend config/secrets UI | | 9 | **FN-010** | code-server Spawn | code-server manifest, spawn flow, runtime integration, auth layer | -| 10 | **FN-008** | RunFusion POC | Executable environment, Node/npm runtime, health reporting | +| 10 | **FN-008** | OpenCode POC | AI-powered terminal environment, web interface, health reporting | **Dependency notes:** - FN-004 and FN-005 can proceed in parallel once FN-019 is complete. @@ -139,7 +139,7 @@ FN-002 (Scaffold) │ ├──> FN-003 (Tool Registry) │ │ │ │ │ ├──> FN-010 (code-server Spawn) - │ │ └──> FN-008 (RunFusion POC) + │ │ └──> FN-008 (OpenCode POC) │ │ │ ├──> FN-011 (Git Provider) │ │ diff --git a/docs/project-brief.md b/docs/project-brief.md index c13e657..3aceac3 100644 --- a/docs/project-brief.md +++ b/docs/project-brief.md @@ -2,7 +2,7 @@ ## What -Headquarter is a hosted workspace and tool-orchestration platform. Authenticated users create Git-backed projects and launch containerized development tools—starting with RunFusion and code-server—each exposed via its own HTTPS subdomain. +Headquarter is a hosted workspace and tool-orchestration platform. Authenticated users create Git-backed projects and launch containerized development tools—starting with OpenCode and code-server—each exposed via its own HTTPS subdomain. ## Why diff --git a/docs/tool-manifest-spec.md b/docs/tool-manifest-spec.md index 7763552..8ac809a 100644 --- a/docs/tool-manifest-spec.md +++ b/docs/tool-manifest-spec.md @@ -5,7 +5,7 @@ ## 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. +Headquarter is a manifest-driven platform: every containerized tool (OpenCode, 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. @@ -135,17 +135,17 @@ traefik: 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 +## Example: OpenCode Manifest ```yaml -id: runfusion -name: RunFusion -description: Executable Node.js environment for running and developing applications. +id: opencode +name: OpenCode +description: AI-powered terminal-based development environment with web interface. version: "1.0.0" -image: node:22-slim +image: ghcr.io/opencode-ai/opencode:latest runtime_working_dir: /workspace ports: - - container_port: 8080 + - container_port: 3000 protocol: tcp name: http primary: true @@ -156,32 +156,28 @@ workspace_mounts: read_only: false config_mounts: - type: volume - source_pattern: "{user_config}/runfusion" - target: /home/node/.config + source_pattern: "{user_config}/opencode" + target: /root/.config/opencode read_only: false env: - NODE_ENV: development + TERM: xterm-256color + FORCE_COLOR: "1" health_check: type: http path: / - port: 8080 + port: 3000 interval_seconds: 10 timeout_seconds: 5 retries: 3 - start_period_seconds: 10 + start_period_seconds: 15 resource_limits: cpus: 2.0 - memory_mb: 2048 + memory_mb: 4096 memory_swap_mb: -1 -executable: - node_version: "22" - package_manager: npm - bootstrap_commands: [] - install_commands: [] traefik: enabled: true - subdomain_prefix: runfusion - port: 8080 + subdomain_prefix: opencode + port: 3000 middlewares: [] strip_prefix: false ```