- Document manifest configuration and web terminal access - Add environment variables and workspace mount details - Include usage instructions and differences from code-server - Add local testing commands for OpenCode container
11 KiB
Development Guide
Prerequisites
- Node.js ≥ 20 and pnpm ≥ 9
- Python ≥ 3.11 with
venvsupport - Docker and Docker Compose (for local services)
Installation
# Install Node dependencies and Python virtualenv + packages
make install
# Or manually:
pnpm install
cd apps/api && python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
Environment Setup
Copy the root environment example and fill in local values:
cp .env.example .env
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:
- User clicks login → redirected to OIDC provider authorize endpoint
- User authenticates with provider
- Provider redirects to
/callbackwith authorization code - Frontend exchanges code for access token (PKCE)
- 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
cd apps/web
pnpm dev # Vite dev server on http://localhost:5173
Backend only
cd apps/api
.venv/bin/uvicorn app.main:app --reload --port 8000
Both (via root script)
pnpm dev # Runs frontend and backend in parallel
With Docker Compose
docker compose up --build -d
Alembic Migrations
Generate a new migration after modifying models:
cd apps/api
.venv/bin/alembic revision --autogenerate -m "description"
Apply migrations:
cd apps/api
.venv/bin/alembic upgrade head
Downgrade one revision:
cd apps/api
.venv/bin/alembic downgrade -1
Or use the Makefile targets:
cd apps/api
make revision msg="description"
make upgrade
make downgrade
Testing
Frontend
pnpm --filter @headquarter/web test
Uses Vitest + Testing Library + jsdom.
Backend
pnpm --filter @headquarter/api test
Or directly with pytest:
cd apps/api && .venv/bin/pytest
All tests
make test
# or
pnpm test
CI / Testing
A GitHub Actions workflow (.github/workflows/ci.yml) runs on every push and pull request to main. It executes two jobs in parallel:
- web-ci — checks out the repo, installs Node.js ≥ 20 and pnpm, then runs
lint,typecheck, andtestfor@headquarter/web. - api-ci — checks out the repo, sets up Python 3.11, installs API dev dependencies (
pytest,ruff,mypy,httpx), starts a PostgreSQL service container, then runsruff check,mypy, andpytestfor the API.
The workflow reports pass/fail status directly on pull requests as required status checks. All commands must pass before a PR can be merged.
Linting and Type Checking
Frontend
pnpm --filter @headquarter/web lint
pnpm --filter @headquarter/web typecheck
Backend
pnpm --filter @headquarter/api lint
pnpm --filter @headquarter/api typecheck
All
make lint
make typecheck
Building
make build
# or
pnpm build
Project Layout
├── apps/
│ ├── web/ # Vite React TypeScript frontend
│ └── api/ # FastAPI Python backend
├── packages/ # Shared packages (future)
├── docs/ # Documentation
├── deploy/ # Deployment skeleton files
├── docker-compose.yml
└── package.json # Root monorepo scripts
Conventions
- Frontend: React functional components, TypeScript strict mode, ESLint + Ruff-like rules.
- Backend: FastAPI, Pydantic settings, pytest, ruff, mypy.
- Commits: Conventional commits with task ID prefix, e.g.
feat(FN-002): description.
Git Abstraction
The app/git/ package in the backend provides provider-independent Git
orchestration. It is split into two layers so that remote provider API logic
and local CLI operations evolve independently:
| Module | Responsibility |
|---|---|
types |
Enumerations (ProviderKind, CredentialKind, ConnectionStatus, SshKeyStatus) |
provider |
Abstract GitProvider — remote operations (validate_connection, list_repositories, create_deploy_key, …) |
credentials |
GitCredential / AccessTokenCredential models and CredentialStorage ABC |
ssh_key |
SshKeyPair model and SshKeyLifecycle (Ed25519 generation via cryptography) |
connection |
RepositoryConnection ORM mapping and ConnectionManager orchestration |
operations |
Abstract GitOperations and concrete LocalGitOperations (subprocess-based get_status) |
Security rules for the package:
- Credential models store only
encrypted_payload— no plaintexttokenorprivate_keyfields. - 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
-
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
-
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
-
API Endpoints (
apps/api/app/routers/tool_instances.py):POST /projects/{id}/tool-instances— Spawn a new tool instancePOST /projects/{id}/tool-instances/{id}/stop— Stop a running instancePOST /projects/{id}/tool-instances/{id}/start— Restart a stopped instanceGET /projects/{id}/tool-instances/{id}/status— Get container status
-
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/validatefor 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:
docker network create tools # One-time setup
Spawned containers use the tools network for Traefik routing.
Config & Secrets
Overview
The platform supports scoped configuration values and encrypted secrets that are injected into tool containers at spawn time.
Scopes
Configs and secrets support four scope levels (closest match wins):
- Global — Available to all users and projects
- User — Available to a specific user across all projects
- Project — Available within a specific project
- Instance — Available to a specific tool instance
Configs
Configs are plaintext JSON values mounted as files into containers:
- Mount path:
/app/config/<key>.json - Permissions:
0400(read-only, owner-only) - Scope resolution: instance > project > user > global
API Endpoints:
POST /configs— Create configGET /configs— List configs (filter by scope_type, scope_id)PUT /configs/{id}— Update config valueDELETE /configs/{id}— Delete config
Frontend:
/projects/{id}/configs— Config management UI
Secrets
Secrets are encrypted with Fernet and injected as environment variables:
- Env var format:
<UPPERCASE_KEY>=<decrypted_value> - Values are never sent to the frontend decrypted (displayed as
••••••) - Scope resolution: instance > project > user > global
API Endpoints:
POST /secrets— Create secretGET /secrets— List secrets (filter by scope_type, scope_id)PUT /secrets/{id}— Update secret valueDELETE /secrets/{id}— Delete secret
Frontend:
/projects/{id}/secrets— Secret management UI
Runtime Injection
When a tool instance is spawned:
- Configs are resolved from all applicable scopes
- Secrets are resolved and decrypted
- Config files are generated in
/tmp/headquarter-configs/{instance_id}/ - Config files are mounted as read-only volumes
- Secrets are injected as environment variables
- Missing required secrets will fail the spawn with a clear error
Validation
Before spawning, the system validates that all required secrets exist. If any are missing, the spawn fails with an error message listing the missing secrets.
OpenCode Tool
Overview
OpenCode is an AI-powered terminal-based development environment accessible via web browser. It is included as a built-in tool manifest alongside code-server.
Manifest
File: apps/api/app/tools/manifests/opencode.yml
id: opencode
name: OpenCode
image: ghcr.io/opencode-ai/opencode:latest
ports:
- container_port: 3000
primary: true
Web Terminal Access
OpenCode exposes a terminal interface on port 3000:
- Subdomain:
opencode-{project}-{user}.{domain} - Health Check:
GET /on port 3000 - Terminal: Full xterm-256color support with color output
Environment Variables
The following environment variables are configured for terminal support:
| Variable | Value | Description |
|---|---|---|
TERM |
xterm-256color |
Terminal type with color support |
FORCE_COLOR |
"1" |
Force color output |
Workspace Mount
OpenCode mounts the project workspace at /workspace for persistent file access.
Config Mount
User-specific OpenCode configuration is mounted at /root/.config/opencode.
Usage
- Navigate to
/tools/spawnin the frontend - Select "OpenCode" from the tool dropdown
- Choose a project
- Enter an instance name
- Click "Spawn Tool"
- Once running, click "Open Tool" to access the web terminal
Differences from code-server
| Feature | OpenCode | code-server |
|---|---|---|
| Interface | Terminal (web-based) | VS Code (web-based) |
| Port | 3000 | 8080 |
| Primary Use | Terminal/CLI tasks | Code editing/IDE |
| AI Features | Built-in AI assistance | Extensions required |
Local Testing
To test OpenCode locally without the full platform:
docker run -it --rm \
-p 3000:3000 \
-e TERM=xterm-256color \
-e FORCE_COLOR=1 \
ghcr.io/opencode-ai/opencode:latest
Then open http://localhost:3000 in your browser.