e0f753803c
- 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
401 lines
11 KiB
Markdown
401 lines
11 KiB
Markdown
# Development Guide
|
|
|
|
## Prerequisites
|
|
|
|
- **Node.js** ≥ 20 and **pnpm** ≥ 9
|
|
- **Python** ≥ 3.11 with `venv` support
|
|
- **Docker** and **Docker Compose** (for local services)
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Copy the frontend environment example:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
cd apps/web
|
|
pnpm dev # Vite dev server on http://localhost:5173
|
|
```
|
|
|
|
### Backend only
|
|
|
|
```bash
|
|
cd apps/api
|
|
.venv/bin/uvicorn app.main:app --reload --port 8000
|
|
```
|
|
|
|
### Both (via root script)
|
|
|
|
```bash
|
|
pnpm dev # Runs frontend and backend in parallel
|
|
```
|
|
|
|
### With Docker Compose
|
|
|
|
```bash
|
|
docker compose up --build -d
|
|
```
|
|
|
|
### Alembic Migrations
|
|
|
|
Generate a new migration after modifying models:
|
|
```bash
|
|
cd apps/api
|
|
.venv/bin/alembic revision --autogenerate -m "description"
|
|
```
|
|
|
|
Apply migrations:
|
|
```bash
|
|
cd apps/api
|
|
.venv/bin/alembic upgrade head
|
|
```
|
|
|
|
Downgrade one revision:
|
|
```bash
|
|
cd apps/api
|
|
.venv/bin/alembic downgrade -1
|
|
```
|
|
|
|
Or use the Makefile targets:
|
|
```bash
|
|
cd apps/api
|
|
make revision msg="description"
|
|
make upgrade
|
|
make downgrade
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Frontend
|
|
|
|
```bash
|
|
pnpm --filter @headquarter/web test
|
|
```
|
|
|
|
Uses **Vitest** + **Testing Library** + **jsdom**.
|
|
|
|
### Backend
|
|
|
|
```bash
|
|
pnpm --filter @headquarter/api test
|
|
```
|
|
|
|
Or directly with pytest:
|
|
|
|
```bash
|
|
cd apps/api && .venv/bin/pytest
|
|
```
|
|
|
|
### All tests
|
|
|
|
```bash
|
|
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`, and `test` for `@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 runs `ruff check`, `mypy`, and `pytest` for 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
|
|
|
|
```bash
|
|
pnpm --filter @headquarter/web lint
|
|
pnpm --filter @headquarter/web typecheck
|
|
```
|
|
|
|
### Backend
|
|
|
|
```bash
|
|
pnpm --filter @headquarter/api lint
|
|
pnpm --filter @headquarter/api typecheck
|
|
```
|
|
|
|
### All
|
|
|
|
```bash
|
|
make lint
|
|
make typecheck
|
|
```
|
|
|
|
## Building
|
|
|
|
```bash
|
|
make build
|
|
# or
|
|
pnpm build
|
|
```
|
|
|
|
## Project Layout
|
|
|
|
```text
|
|
├── 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 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.
|
|
|
|
## 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):
|
|
|
|
1. **Global** — Available to all users and projects
|
|
2. **User** — Available to a specific user across all projects
|
|
3. **Project** — Available within a specific project
|
|
4. **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 config
|
|
- `GET /configs` — List configs (filter by scope_type, scope_id)
|
|
- `PUT /configs/{id}` — Update config value
|
|
- `DELETE /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 secret
|
|
- `GET /secrets` — List secrets (filter by scope_type, scope_id)
|
|
- `PUT /secrets/{id}` — Update secret value
|
|
- `DELETE /secrets/{id}` — Delete secret
|
|
|
|
**Frontend:**
|
|
- `/projects/{id}/secrets` — Secret management UI
|
|
|
|
### Runtime Injection
|
|
|
|
When a tool instance is spawned:
|
|
|
|
1. Configs are resolved from all applicable scopes
|
|
2. Secrets are resolved and decrypted
|
|
3. Config files are generated in `/tmp/headquarter-configs/{instance_id}/`
|
|
4. Config files are mounted as read-only volumes
|
|
5. Secrets are injected as environment variables
|
|
6. 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`
|
|
|
|
```yaml
|
|
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
|
|
|
|
1. Navigate to `/tools/spawn` in the frontend
|
|
2. Select "OpenCode" from the tool dropdown
|
|
3. Choose a project
|
|
4. Enter an instance name
|
|
5. Click "Spawn Tool"
|
|
6. 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:
|
|
|
|
```bash
|
|
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.
|