Feat v0.6.1-v0.6.2: backup/restore, docs surface, dynamic OpenAPI
Some checks failed
CI/CD / detect-changes (pull_request) Successful in 20s
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / test-sqlite (pull_request) Has been cancelled
CI/CD / build-and-deploy (pull_request) Has been cancelled
CI/CD / test-go-pg (pull_request) Has been cancelled
Some checks failed
CI/CD / detect-changes (pull_request) Successful in 20s
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / test-sqlite (pull_request) Has been cancelled
CI/CD / build-and-deploy (pull_request) Has been cancelled
CI/CD / test-go-pg (pull_request) Has been cancelled
v0.6.1 — Backup tooling (.swb ZIP export/import), admin backup section, docs surface with markdown rendering, 5 reference docs, 6 handler tests. v0.6.2 — Dark mode contrast fixes, topbar nav, 📖 docs icon, dynamic OpenAPI spec with extension route merging, docs outline sidebar, scroll/routing fixes; 7 new tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
186
docs/API-REFERENCE.md
Normal file
186
docs/API-REFERENCE.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# API Reference
|
||||
|
||||
Base path: `/api/v1` (all endpoints below are relative to this unless noted).
|
||||
|
||||
## Authentication
|
||||
|
||||
Most endpoints require a Bearer token in the `Authorization` header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
Obtain tokens via the auth endpoints (no auth required):
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| POST | `/auth/register` | Create account (if registration enabled) |
|
||||
| POST | `/auth/login` | Login, returns access + refresh tokens |
|
||||
| POST | `/auth/refresh` | Refresh access token |
|
||||
| POST | `/auth/logout` | Invalidate refresh token |
|
||||
| GET | `/auth/oidc/login` | OIDC login redirect (when `AUTH_MODE=oidc`) |
|
||||
| GET | `/auth/oidc/callback` | OIDC callback handler |
|
||||
|
||||
## Error Format
|
||||
|
||||
All errors return JSON:
|
||||
|
||||
```json
|
||||
{"error": "description of what went wrong"}
|
||||
```
|
||||
|
||||
Standard HTTP status codes: 400 (bad request), 401 (unauthorized), 403 (forbidden), 404 (not found), 500 (server error).
|
||||
|
||||
## Pagination
|
||||
|
||||
List endpoints accept query parameters:
|
||||
|
||||
| Param | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `limit` | 50 | Max items to return |
|
||||
| `offset` | 0 | Number of items to skip |
|
||||
|
||||
## Endpoints by Category
|
||||
|
||||
### Profile & Settings
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/profile` | Current user profile |
|
||||
| PUT | `/profile` | Update profile |
|
||||
| POST | `/profile/password` | Change password |
|
||||
| GET | `/settings` | User settings |
|
||||
| PUT | `/settings` | Update user settings |
|
||||
| GET | `/profile/permissions` | List granted permissions |
|
||||
| GET | `/profile/bootstrap` | Bootstrap data for frontend |
|
||||
|
||||
### Surfaces & Extensions
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/surfaces` | List enabled surfaces |
|
||||
| GET | `/extensions` | List user extensions |
|
||||
| POST | `/extensions/:id/settings` | Update extension settings |
|
||||
| GET | `/extensions/:id/manifest` | Get extension manifest |
|
||||
| GET | `/settings/public` | Public platform settings |
|
||||
|
||||
### Notifications
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/notifications` | List notifications |
|
||||
| GET | `/notifications/unread-count` | Unread count |
|
||||
| PATCH | `/notifications/:id/read` | Mark as read |
|
||||
| POST | `/notifications/mark-all-read` | Mark all read |
|
||||
| DELETE | `/notifications/:id` | Delete notification |
|
||||
| GET | `/notifications/preferences` | Notification preferences |
|
||||
| PUT | `/notifications/preferences/:type` | Set preference |
|
||||
|
||||
### Workflows
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/workflows` | List workflows |
|
||||
| POST | `/workflows` | Create workflow |
|
||||
| GET | `/workflows/:id` | Get workflow |
|
||||
| PATCH | `/workflows/:id` | Update workflow |
|
||||
| DELETE | `/workflows/:id` | Delete workflow |
|
||||
| POST | `/workflows/:id/publish` | Publish version |
|
||||
| POST | `/workflows/:id/clone` | Clone workflow |
|
||||
| POST | `/workflows/:id/instances` | Start instance |
|
||||
| GET | `/workflows/:id/instances` | List instances |
|
||||
| GET | `/assignments/mine` | My assignments |
|
||||
|
||||
### Teams
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/teams/mine` | List my teams |
|
||||
| GET | `/teams/:id/members` | Team members |
|
||||
| POST | `/teams/:id/members` | Add member |
|
||||
| GET | `/teams/:id/roles` | Team roles |
|
||||
|
||||
### Connections
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/connections` | List connections |
|
||||
| POST | `/connections` | Create connection |
|
||||
| GET | `/connections/resolve` | Resolve connection by type |
|
||||
| PUT | `/connections/:id` | Update connection |
|
||||
| DELETE | `/connections/:id` | Delete connection |
|
||||
|
||||
### Packages (User)
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/packages` | List visible packages |
|
||||
| POST | `/packages/install` | Install personal package |
|
||||
| DELETE | `/packages/:id` | Delete personal package |
|
||||
|
||||
### Admin Endpoints
|
||||
|
||||
All under `/api/v1/admin/` -- require `surface.admin.access` permission.
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/admin/users` | List users |
|
||||
| POST | `/admin/users` | Create user |
|
||||
| GET | `/admin/stats` | Platform statistics |
|
||||
| GET | `/admin/settings` | Global settings |
|
||||
| PUT | `/admin/settings/:key` | Update setting |
|
||||
| GET | `/admin/packages` | List all packages |
|
||||
| POST | `/admin/packages/install` | Install package (.pkg upload) |
|
||||
| POST | `/admin/packages/:id/update` | Update package |
|
||||
| GET | `/admin/packages/:id/export` | Export package as .pkg |
|
||||
| PUT | `/admin/packages/:id/enable` | Enable package |
|
||||
| PUT | `/admin/packages/:id/disable` | Disable package |
|
||||
| DELETE | `/admin/packages/:id` | Delete package |
|
||||
| GET | `/admin/cluster` | Cluster node list (Postgres only) |
|
||||
| POST | `/admin/backup` | Create backup |
|
||||
| GET | `/admin/backups` | List backups |
|
||||
| POST | `/admin/restore` | Restore from backup |
|
||||
|
||||
### Health & Monitoring
|
||||
|
||||
These are at the base path (not under `/api/v1`):
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/health` | Basic health check |
|
||||
| GET | `/healthz/live` | Liveness probe (Kubernetes) |
|
||||
| GET | `/healthz/ready` | Readiness probe (Kubernetes) |
|
||||
| GET | `/metrics` | Prometheus metrics |
|
||||
| GET | `/api/docs` | OpenAPI documentation UI (Swagger) |
|
||||
| GET | `/api/docs/openapi.json` | Dynamic OpenAPI 3.0 spec (includes extension routes) |
|
||||
| GET | `/api/docs/openapi.yaml` | Static kernel-only OpenAPI spec (YAML) |
|
||||
|
||||
### Webhooks (Public)
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET/POST | `/api/v1/hooks/:package_id/:slug` | Inbound webhook for extensions |
|
||||
| POST | `/api/v1/workflows/public/:id/start` | Public workflow entry |
|
||||
|
||||
## WebSocket
|
||||
|
||||
Connect to `/ws` with a ticket-based auth flow:
|
||||
|
||||
1. POST `/api/v1/ws/ticket` with Bearer token to get a one-time ticket.
|
||||
2. Connect to `/ws?ticket=<ticket>`.
|
||||
|
||||
The WebSocket carries JSON-framed events. Subscribe to channels with:
|
||||
|
||||
```json
|
||||
{"type": "subscribe", "channel": "notifications"}
|
||||
```
|
||||
|
||||
Kernel event prefixes: `user.*`, `team.*`, `workflow.*`, `notification.*`, `presence.*`, `extension.*`, `admin.*`, `system.*`.
|
||||
|
||||
### Presence
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| POST | `/presence/heartbeat` | Update presence status |
|
||||
| GET | `/presence` | Query online users |
|
||||
| GET | `/users/search` | Search users |
|
||||
153
docs/DEPLOYMENT.md
Normal file
153
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Deployment Guide
|
||||
|
||||
## Docker Single-Instance
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/switchboard-core/switchboard-core:latest
|
||||
docker run -p 8080:80 \
|
||||
-e SWITCHBOARD_ADMIN_USERNAME=admin \
|
||||
-e SWITCHBOARD_ADMIN_PASSWORD=changeme \
|
||||
-e JWT_SECRET="$(openssl rand -hex 32)" \
|
||||
-e ENCRYPTION_KEY="$(openssl rand -hex 32)" \
|
||||
-v switchboard-data:/data \
|
||||
ghcr.io/switchboard-core/switchboard-core:latest
|
||||
```
|
||||
|
||||
This runs with SQLite and PVC storage. Suitable for evaluation and small teams.
|
||||
|
||||
## Docker Compose with PostgreSQL
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: switchboard
|
||||
POSTGRES_USER: switchboard
|
||||
POSTGRES_PASSWORD: secretpassword
|
||||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
|
||||
switchboard:
|
||||
image: ghcr.io/switchboard-core/switchboard-core:latest
|
||||
ports:
|
||||
- "8080:80"
|
||||
environment:
|
||||
DATABASE_URL: "postgres://switchboard:secretpassword@postgres:5432/switchboard?sslmode=disable"
|
||||
JWT_SECRET: "change-me-in-production"
|
||||
ENCRYPTION_KEY: "change-me-in-production"
|
||||
SWITCHBOARD_ADMIN_USERNAME: admin
|
||||
SWITCHBOARD_ADMIN_PASSWORD: changeme
|
||||
STORAGE_BACKEND: pvc
|
||||
STORAGE_PATH: /data/storage
|
||||
volumes:
|
||||
- sb_storage:/data/storage
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
volumes:
|
||||
pg_data:
|
||||
sb_storage:
|
||||
```
|
||||
|
||||
## Kubernetes
|
||||
|
||||
See the `k8s/` directory for example manifests. Key considerations:
|
||||
|
||||
- Set `POSTGRES_HOST`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` env vars (assembled into DSN automatically).
|
||||
- Liveness probe: `/healthz/live`. Readiness probe: `/healthz/ready`.
|
||||
- Mount a PVC at `/data/storage` or configure S3.
|
||||
- Store `JWT_SECRET` and `ENCRYPTION_KEY` in Kubernetes Secrets.
|
||||
- Registry: `registry.gobha.me:5000/xcaliber/switchboard-core`.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PORT` | `8080` | Backend API port |
|
||||
| `DB_DRIVER` | auto | `postgres` or `sqlite` |
|
||||
| `DATABASE_URL` | | PostgreSQL DSN or SQLite path |
|
||||
| `JWT_SECRET` | `dev-secret-change-me` | Token signing key -- **must change** |
|
||||
| `ENCRYPTION_KEY` | | AES-256 key for credential vault |
|
||||
| `AUTH_MODE` | `builtin` | `builtin`, `mtls`, `oidc` |
|
||||
| `STORAGE_BACKEND` | auto | `pvc` or `s3` |
|
||||
| `STORAGE_PATH` | `/data/storage` | PVC mount point |
|
||||
| `BASE_PATH` | | URL prefix (e.g., `/switchboard`) |
|
||||
| `LOG_FORMAT` | `text` | `text` or `json` |
|
||||
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` |
|
||||
| `CORS_ALLOWED_ORIGINS` | | Comma-separated allowed origins |
|
||||
| `BUNDLED_PACKAGES` | (empty) | `""` defaults, `"*"` all, or comma-separated |
|
||||
| `SKIP_BUNDLED_PACKAGES` | `false` | Disable bundled package install |
|
||||
| `BUNDLED_PACKAGES_DIR` | `/app/bundled-packages` | Custom bundle directory |
|
||||
| `SWITCHBOARD_ADMIN_USERNAME` | | Bootstrap admin username |
|
||||
| `SWITCHBOARD_ADMIN_PASSWORD` | | Bootstrap admin password |
|
||||
| `SEED_USERS` | | Dev seed users (ignored in production) |
|
||||
|
||||
### S3 Storage Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `S3_BUCKET` | Bucket name |
|
||||
| `S3_ENDPOINT` | Endpoint URL (for MinIO, Ceph, etc.) |
|
||||
| `S3_ACCESS_KEY` | Access key |
|
||||
| `S3_SECRET_KEY` | Secret key |
|
||||
| `S3_FORCE_PATH_STYLE` | `true` for MinIO/Ceph |
|
||||
|
||||
### Cluster Variables (Multi-Replica)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `CLUSTER_NODE_ID` | hostname-PID | Override for deterministic node identity |
|
||||
| `CLUSTER_HEARTBEAT_INTERVAL` | `10s` | Heartbeat tick frequency |
|
||||
| `CLUSTER_STALE_THRESHOLD` | `30s` | 3x heartbeat = stale node |
|
||||
| `CLUSTER_ENDPOINT` | auto-detect | Advertised address for peer mesh |
|
||||
|
||||
## Database Configuration
|
||||
|
||||
**PostgreSQL** (recommended for production):
|
||||
|
||||
```bash
|
||||
DATABASE_URL="postgres://user:pass@host:5432/switchboard?sslmode=require"
|
||||
```
|
||||
|
||||
**SQLite** (dev, test, edge deployments):
|
||||
|
||||
```bash
|
||||
DB_DRIVER=sqlite
|
||||
DATABASE_URL=/data/switchboard.db
|
||||
```
|
||||
|
||||
Both databases are first-class -- every query compiles and passes tests on both. The driver is auto-detected from `DATABASE_URL` if `DB_DRIVER` is not set.
|
||||
|
||||
## Cluster Mode
|
||||
|
||||
Multi-replica HA requires PostgreSQL. The kernel uses PG-backed WebSocket tickets and rate limit counters to coordinate across replicas. A cluster registry with heartbeat and stale sweep tracks active nodes.
|
||||
|
||||
View cluster status in Admin > Cluster or via `GET /api/v1/admin/cluster`.
|
||||
|
||||
## Storage Backends
|
||||
|
||||
**PVC**: Auto-detected if the storage path is writable. Mount a volume at `/data/storage`.
|
||||
|
||||
**S3-compatible**: Set `STORAGE_BACKEND=s3` and provide S3 variables. Works with AWS S3, MinIO, and Ceph.
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
Available via Admin UI or API:
|
||||
|
||||
```
|
||||
POST /api/v1/admin/backup # Create backup
|
||||
GET /api/v1/admin/backups # List backups
|
||||
GET /api/v1/admin/backups/:name # Download backup
|
||||
POST /api/v1/admin/restore # Restore from backup
|
||||
DELETE /api/v1/admin/backups/:name # Delete backup
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `/health` | Basic health check |
|
||||
| `/healthz/live` | Kubernetes liveness probe |
|
||||
| `/healthz/ready` | Kubernetes readiness probe |
|
||||
| `/metrics` | Prometheus metrics |
|
||||
183
docs/EXTENSION-GUIDE.md
Normal file
183
docs/EXTENSION-GUIDE.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# Extension Guide
|
||||
|
||||
## Package Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `surface` | A routable UI page rendered in the shell viewport |
|
||||
| `extension` | Starlark hooks, tools, API routes, DB tables |
|
||||
| `full` | Both surface and extension combined |
|
||||
| `library` | Shared code imported by other packages via `lib.require()` |
|
||||
| `workflow` | Bundled workflow definition |
|
||||
|
||||
## Tiers
|
||||
|
||||
| Tier | Runs | Capabilities |
|
||||
|------|------|-------------|
|
||||
| `browser` | Client-side JS only | DOM access, SDK hooks, no server-side logic |
|
||||
| `starlark` | Sandboxed server-side | DB, HTTP, notifications, secrets, API routes, realtime |
|
||||
| `sidecar` | Separate container | Full runtime (future) |
|
||||
|
||||
## manifest.json
|
||||
|
||||
Every package has a `manifest.json` at its root. Example for a surface:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-surface",
|
||||
"title": "My Surface",
|
||||
"type": "full",
|
||||
"tier": "starlark",
|
||||
"version": "1.0.0",
|
||||
"description": "A custom surface with server-side logic.",
|
||||
"icon": "🔧",
|
||||
"author": "you",
|
||||
"route": "/s/my-surface",
|
||||
"auth": "authenticated",
|
||||
"permissions": ["db.write"],
|
||||
"api_routes": [
|
||||
{"method": "GET", "path": "/items"},
|
||||
{"method": "POST", "path": "/items"}
|
||||
],
|
||||
"db_tables": {
|
||||
"items": {
|
||||
"columns": {
|
||||
"title": "text",
|
||||
"done": "int"
|
||||
},
|
||||
"indexes": [["title"]]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"page_size": {
|
||||
"type": "string",
|
||||
"label": "Items Per Page",
|
||||
"description": "Number of items shown per page",
|
||||
"default": "25"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Reference
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `id` | yes | Unique kebab-case identifier |
|
||||
| `title` | yes | Display name |
|
||||
| `type` | yes | `surface`, `extension`, `full`, `library`, `workflow` |
|
||||
| `tier` | yes | `browser`, `starlark`, `sidecar` |
|
||||
| `version` | yes | Semver string |
|
||||
| `description` | no | Short description |
|
||||
| `icon` | no | Emoji icon for sidebar/menu |
|
||||
| `route` | surfaces | URL path (e.g., `/s/my-surface`) |
|
||||
| `auth` | no | `authenticated` (default) or `public` |
|
||||
| `permissions` | no | Capabilities requested: `db.write`, `http`, `notifications`, `secrets`, `realtime.publish` |
|
||||
| `api_routes` | no | Array of `{method, path}` for extension HTTP endpoints |
|
||||
| `api_schema` | no | OpenAPI documentation for extension API routes (see below) |
|
||||
| `db_tables` | no | Table definitions (see below) |
|
||||
| `settings` | no | User-configurable settings schema |
|
||||
| `exports` | libraries | Functions exported for other packages |
|
||||
| `hooks` | no | Event bus subscriptions |
|
||||
| `schema_version` | no | Integer for additive schema migrations |
|
||||
|
||||
## db_tables Schema
|
||||
|
||||
Tables are automatically namespaced as `ext_{package_id}_{table_name}`. Column types: `text`, `int`. Every table gets an auto-generated `id` primary key and `created_at` timestamp.
|
||||
|
||||
```json
|
||||
"db_tables": {
|
||||
"notes": {
|
||||
"columns": {
|
||||
"title": "text",
|
||||
"body": "text",
|
||||
"creator_id": "text",
|
||||
"pinned": "int"
|
||||
},
|
||||
"indexes": [
|
||||
["creator_id"],
|
||||
["pinned"]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## api_schema (OpenAPI Documentation)
|
||||
|
||||
Extensions can optionally declare an `api_schema` array in their manifest to provide rich API documentation. Declared routes appear in Swagger UI at `/api/docs` with parameters, request bodies, and response schemas. Routes without `api_schema` entries still appear as auto-generated stubs.
|
||||
|
||||
```json
|
||||
"api_schema": [
|
||||
{
|
||||
"path": "/items",
|
||||
"method": "GET",
|
||||
"summary": "List items",
|
||||
"description": "Returns paginated items for the current user",
|
||||
"params": {
|
||||
"limit": {"type": "integer", "default": 50, "description": "Max results"},
|
||||
"offset": {"type": "integer", "default": 0}
|
||||
},
|
||||
"response": {
|
||||
"type": "object",
|
||||
"example": {"data": [{"id": "string", "title": "string"}]}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/items",
|
||||
"method": "POST",
|
||||
"summary": "Create item",
|
||||
"body": {
|
||||
"title": {"type": "string", "required": true},
|
||||
"description": {"type": "string"}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Only `path` and `method` are required. All other fields are optional. Malformed entries are logged and skipped without blocking extension loading.
|
||||
|
||||
## Starlark Sandbox API
|
||||
|
||||
Starlark scripts run server-side with a CPU budget and memory ceiling. Available modules (granted per-permission by admin):
|
||||
|
||||
| Module | Permission | API |
|
||||
|--------|-----------|-----|
|
||||
| `db` | `db.write` | `db.query(table, filters)`, `db.insert(table, row)`, `db.update(table, id, row)`, `db.delete(table, id)` |
|
||||
| `http` | `http` | `http.get(url)`, `http.post(url, body)` -- SSRF-safe, no private IPs by default |
|
||||
| `notifications` | `notifications` | `notifications.send(user_id, title, body)` |
|
||||
| `secrets` | `secrets` | `secrets.get(connection_type)` -- reads from the credential vault |
|
||||
| `api` | (implicit) | Registers HTTP routes at `/s/:slug/api/*path` |
|
||||
| `realtime` | `realtime.publish` | `realtime.publish(channel, event, data)` -- push to WebSocket clients |
|
||||
|
||||
The sandbox cannot spawn goroutines, access the filesystem, or import arbitrary packages.
|
||||
|
||||
## Permissions Model
|
||||
|
||||
Extensions declare required permissions in `manifest.json`. The admin must grant each permission before the extension can use the corresponding module. Permission status is visible in Admin > Packages > Permissions.
|
||||
|
||||
Kernel permissions for users/groups: `extension.use`, `extension.install`, `workflow.create`, `workflow.submit`, `admin.view`, `token.unlimited`.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
my-package/
|
||||
manifest.json # required
|
||||
js/ # browser-side JavaScript
|
||||
index.js
|
||||
css/ # stylesheets
|
||||
styles.css
|
||||
script.star # Starlark entry point
|
||||
star/ # additional Starlark modules
|
||||
assets/ # static assets
|
||||
migrations/ # schema migration files
|
||||
```
|
||||
|
||||
## Testing Extensions
|
||||
|
||||
1. Build the package: `cd packages && bash build.sh my-package`
|
||||
2. Upload the `.pkg` file via Admin > Packages > Install.
|
||||
3. Grant permissions in Admin > Packages > Permissions.
|
||||
4. Enable the package.
|
||||
5. If it is a surface, navigate to its route (e.g., `/s/my-package`).
|
||||
|
||||
Extension API routes are accessible at `/s/{slug}/api/{path}` and require an authenticated Bearer token.
|
||||
91
docs/GETTING-STARTED.md
Normal file
91
docs/GETTING-STARTED.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Getting Started
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose
|
||||
|
||||
## Running with Docker Compose
|
||||
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000). Default credentials: `admin` / `admin`.
|
||||
|
||||
Data persists in the `sb_data` named volume. To reset everything:
|
||||
|
||||
```bash
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## First Boot
|
||||
|
||||
On first start, Switchboard Core will:
|
||||
|
||||
1. Run database migrations (SQLite by default in compose).
|
||||
2. Create the admin user from `SWITCHBOARD_ADMIN_USERNAME` / `SWITCHBOARD_ADMIN_PASSWORD` env vars.
|
||||
3. Auto-install the curated default package set (notes, chat-core, dashboard, workflow demos, etc.).
|
||||
|
||||
No manual setup steps are required.
|
||||
|
||||
## Installing Additional Packages
|
||||
|
||||
The Docker image ships with extra packages beyond the default set. To install them:
|
||||
|
||||
- **Via Admin UI**: Go to `/admin` > Packages. Browse, enable, or install packages.
|
||||
- **Via environment variable**: Set `BUNDLED_PACKAGES` before starting:
|
||||
|
||||
```bash
|
||||
# Install all bundled packages
|
||||
BUNDLED_PACKAGES="*" docker compose up --build
|
||||
|
||||
# Install specific extras
|
||||
BUNDLED_PACKAGES="notes,tasks,schedules" docker compose up --build
|
||||
```
|
||||
|
||||
You can also upload `.pkg` archives through the Admin > Packages page.
|
||||
|
||||
## Key URLs
|
||||
|
||||
| URL | Purpose |
|
||||
|-----|---------|
|
||||
| `/` | Redirects to your default surface |
|
||||
| `/admin` | Admin panel (users, packages, settings, teams) |
|
||||
| `/settings` | User settings (profile, preferences, default surface) |
|
||||
| `/welcome` | Welcome page (shown when no surfaces are installed) |
|
||||
| `/api/docs` | Interactive OpenAPI documentation |
|
||||
|
||||
## Key Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PORT` | `8080` | Backend API port |
|
||||
| `DB_DRIVER` | auto | `postgres` or `sqlite` |
|
||||
| `DATABASE_URL` | | PostgreSQL DSN or SQLite file path |
|
||||
| `JWT_SECRET` | `dev-secret-change-me` | **Change in production** |
|
||||
| `ENCRYPTION_KEY` | | AES-256 key for credential encryption |
|
||||
| `AUTH_MODE` | `builtin` | `builtin`, `mtls`, or `oidc` |
|
||||
| `STORAGE_BACKEND` | auto | `pvc` or `s3` |
|
||||
| `STORAGE_PATH` | `/data/storage` | PVC mount point |
|
||||
| `SWITCHBOARD_ADMIN_USERNAME` | | Bootstrap admin username |
|
||||
| `SWITCHBOARD_ADMIN_PASSWORD` | | Bootstrap admin password |
|
||||
| `BUNDLED_PACKAGES` | (empty) | `""` = curated defaults, `"*"` = all, or comma-separated IDs |
|
||||
| `SKIP_BUNDLED_PACKAGES` | `false` | Disable auto-install entirely |
|
||||
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` |
|
||||
| `LOG_FORMAT` | `text` | `text` or `json` |
|
||||
|
||||
## From Source
|
||||
|
||||
```bash
|
||||
git clone <repo-url> && cd switchboard-core
|
||||
cp server/.env.example server/.env # edit DB credentials
|
||||
cd server && go run .
|
||||
# Backend on http://localhost:8080
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Extension Guide](EXTENSION-GUIDE.md) -- Author your own packages
|
||||
- [API Reference](API-REFERENCE.md) -- REST API overview
|
||||
- [Deployment](DEPLOYMENT.md) -- Production deployment
|
||||
- [Architecture](ARCHITECTURE.md) -- System design
|
||||
130
docs/PACKAGE-FORMAT.md
Normal file
130
docs/PACKAGE-FORMAT.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Package Format
|
||||
|
||||
Switchboard packages are distributed as `.pkg` files -- ZIP archives with a standard internal structure.
|
||||
|
||||
## ZIP Structure
|
||||
|
||||
```
|
||||
my-package.pkg
|
||||
├── manifest.json # required -- package metadata
|
||||
├── js/ # browser-side JavaScript
|
||||
│ └── index.js
|
||||
├── css/ # stylesheets
|
||||
│ └── styles.css
|
||||
├── script.star # Starlark entry point
|
||||
├── star/ # additional Starlark modules
|
||||
├── assets/ # static assets (images, etc.)
|
||||
└── migrations/ # schema migration files
|
||||
```
|
||||
|
||||
Only `manifest.json` is required. All other directories are optional and included only if present in the source.
|
||||
|
||||
## manifest.json Reference
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-package",
|
||||
"title": "My Package",
|
||||
"type": "surface",
|
||||
"tier": "browser",
|
||||
"version": "1.0.0",
|
||||
"description": "What this package does.",
|
||||
"icon": "📦",
|
||||
"author": "your-name",
|
||||
"route": "/s/my-package",
|
||||
"auth": "authenticated",
|
||||
"permissions": [],
|
||||
"api_routes": [],
|
||||
"db_tables": {},
|
||||
"settings": {},
|
||||
"hooks": {},
|
||||
"exports": [],
|
||||
"schema_version": 1
|
||||
}
|
||||
```
|
||||
|
||||
### Required Fields
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `id` | Unique kebab-case identifier |
|
||||
| `title` | Human-readable display name |
|
||||
| `type` | `surface`, `extension`, `full`, `library`, `workflow` |
|
||||
| `tier` | `browser`, `starlark`, `sidecar` |
|
||||
| `version` | Semver version string |
|
||||
|
||||
### Optional Fields
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `description` | Short description |
|
||||
| `icon` | Emoji for sidebar/menu display |
|
||||
| `author` | Package author |
|
||||
| `route` | URL path for surfaces (e.g., `/s/my-package`) |
|
||||
| `auth` | `authenticated` or `public` |
|
||||
| `layout` | Surface layout mode (e.g., `single`) |
|
||||
| `permissions` | Array of required capabilities (`db.write`, `http`, `notifications`, `secrets`, `realtime.publish`) |
|
||||
| `api_routes` | Array of `{"method": "GET", "path": "/items"}` |
|
||||
| `db_tables` | Table definitions with columns and indexes |
|
||||
| `settings` | User-configurable settings with type, label, description, default |
|
||||
| `hooks` | Event bus subscription patterns |
|
||||
| `exports` | Functions exported by library packages |
|
||||
| `schema_version` | Integer for additive schema migrations |
|
||||
|
||||
## Package Lifecycle
|
||||
|
||||
1. **Install**: Upload a `.pkg` file via Admin > Packages or `POST /api/v1/admin/packages/install`. The kernel extracts the archive, creates database tables, and registers routes.
|
||||
|
||||
2. **Enable**: Activate the package so its routes, hooks, and UI become available. `PUT /api/v1/admin/packages/:id/enable`.
|
||||
|
||||
3. **Disable**: Deactivate without removing data. `PUT /api/v1/admin/packages/:id/disable`.
|
||||
|
||||
4. **Update**: Upload a new `.pkg` with a higher semver version. Schema changes must be additive (new columns/tables only). `POST /api/v1/admin/packages/:id/update`.
|
||||
|
||||
5. **Export**: Download the installed package as a `.pkg` archive. `GET /api/v1/admin/packages/:id/export`.
|
||||
|
||||
6. **Delete**: Remove the package and its data. `DELETE /api/v1/admin/packages/:id`.
|
||||
|
||||
## Bundled vs User-Installed
|
||||
|
||||
**Bundled packages** ship inside the Docker image at `/app/bundled-packages`. On first boot, a curated default set is auto-installed. Behavior:
|
||||
|
||||
- First boot: curated defaults are installed and enabled.
|
||||
- Subsequent boots: already-installed packages are skipped.
|
||||
- Admin uninstalls: the package stays uninstalled (never force-reinstalled).
|
||||
- Control via `BUNDLED_PACKAGES` env var: empty = defaults, `"*"` = all, or comma-separated IDs.
|
||||
|
||||
**User-installed packages** are uploaded through the Admin UI or API. They follow the same lifecycle but are not tied to the Docker image.
|
||||
|
||||
## Building Packages
|
||||
|
||||
Packages are built by zipping the standard directories alongside `manifest.json`:
|
||||
|
||||
```bash
|
||||
# Build all packages in the packages/ directory
|
||||
cd packages && bash build.sh
|
||||
|
||||
# Build a single package
|
||||
cd packages && bash build.sh my-package
|
||||
```
|
||||
|
||||
Output goes to `dist/my-package.pkg`.
|
||||
|
||||
### Manual Build
|
||||
|
||||
```bash
|
||||
cd packages/my-package
|
||||
zip -r ../../dist/my-package.pkg manifest.json js/ css/ script.star
|
||||
```
|
||||
|
||||
The build script automatically includes whichever standard directories exist: `js/`, `css/`, `assets/`, `script.star`, `star/`, `migrations/`.
|
||||
|
||||
### Custom Docker Image
|
||||
|
||||
To bundle custom packages into a Docker image:
|
||||
|
||||
1. Place your package in `packages/your-package/` with a `manifest.json`.
|
||||
2. Run `cd packages && bash build.sh all`.
|
||||
3. Build the image: `docker build -t my-switchboard .`
|
||||
|
||||
The Dockerfile builds all packages and copies them into the bundled packages directory.
|
||||
Reference in New Issue
Block a user