mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-08-28 02:44:59 +00:00
* chore: prepare v6.1.2 release * chore: refresh reviewed gitleaks fingerprints * docs: record release gate corrections * docs: record v6.1.2 release evidence
754 lines
24 KiB
Markdown
754 lines
24 KiB
Markdown
# Self-Hosting Veritas Kanban
|
|
|
|
This guide walks you through every self-hosting scenario — from running locally for personal use to a production deployment behind a reverse proxy or on Tailscale.
|
|
|
|
> **Credit:** This guide was originally contributed by [@xechehot](https://github.com/xechehot) in [PR #126](https://github.com/BradGroux/veritas-kanban/pull/126). It has been expanded here to cover additional deployment patterns.
|
|
|
|
> **Remote security posture:** v5 remote access is designed around a trusted
|
|
> same-origin host for the web client, `/api`, and `/ws`. Before exposing
|
|
> Veritas beyond loopback, read
|
|
> [ADR 0002: v5 Remote Server Mode and Network Security Posture](../architecture/ADR-0002-v5-remote-server-security-posture.md).
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
- [Prerequisites](#prerequisites)
|
|
- [Build Steps](#build-steps)
|
|
- [Local Hosting](#local-hosting)
|
|
- [LAN Access](#lan-access)
|
|
- [Tailscale Serve](#tailscale-serve)
|
|
- [Reverse Proxy](#reverse-proxy)
|
|
- [nginx](#nginx)
|
|
- [Caddy](#caddy)
|
|
- [Docker](#docker)
|
|
- [Security](#security)
|
|
- [Environment Variables Reference](#environment-variables-reference)
|
|
- [Troubleshooting](#troubleshooting)
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
| Requirement | Version | Install |
|
|
| ----------- | ------- | ------------------------------------------------------------ |
|
|
| Node.js | 22.22.1+ | https://nodejs.org or `nvm install 22` |
|
|
| pnpm | 11.1.1 | `corepack enable && corepack prepare pnpm@11.1.1 --activate` |
|
|
| Git | 2.38+ | https://git-scm.com |
|
|
|
|
Verify:
|
|
|
|
```bash
|
|
node --version # v22.22.1 or newer
|
|
pnpm --version # 11.1.1
|
|
```
|
|
|
|
---
|
|
|
|
## Build Steps
|
|
|
|
```bash
|
|
# 1. Clone the repository
|
|
git clone https://github.com/BradGroux/veritas-kanban.git
|
|
cd veritas-kanban
|
|
|
|
# 2. Install all workspace dependencies
|
|
pnpm install --frozen-lockfile
|
|
|
|
# 3. Build all packages (shared → server + web)
|
|
pnpm build
|
|
|
|
# 4. Configure environment
|
|
cp server/.env.example server/.env
|
|
# Edit server/.env — at minimum set VERITAS_ADMIN_KEY
|
|
```
|
|
|
|
The build produces:
|
|
|
|
| Path | Contents |
|
|
| -------------- | ------------------------------------------------------- |
|
|
| `shared/dist/` | Shared TypeScript types and utilities |
|
|
| `server/dist/` | Compiled Express API server |
|
|
| `web/dist/` | Static React frontend (served by Express in production) |
|
|
|
|
---
|
|
|
|
## Local Hosting
|
|
|
|
Run Veritas Kanban on your own machine for personal use.
|
|
|
|
```bash
|
|
# Start the production server
|
|
NODE_ENV=production node server/dist/index.js
|
|
```
|
|
|
|
The app is now available at **http://localhost:3001**.
|
|
|
|
- **API:** `http://localhost:3001/api`
|
|
- **UI:** `http://localhost:3001`
|
|
- **WebSocket:** `ws://localhost:3001/ws`
|
|
- **API Docs (Swagger):** `http://localhost:3001/api-docs`
|
|
|
|
The example env file keeps `VERITAS_AUTH_LOCALHOST_BYPASS=true` for local
|
|
development convenience, so unauthenticated loopback requests can use the
|
|
configured local role outside production. In `NODE_ENV=production`, the server
|
|
does not honor localhost bypass for HTTP or WebSocket auth. Set
|
|
`VERITAS_AUTH_LOCALHOST_ROLE=admin` only for trusted local development when you
|
|
want full access without a key on your own machine.
|
|
|
|
### Development mode
|
|
|
|
For active development with hot-module replacement:
|
|
|
|
```bash
|
|
pnpm dev
|
|
# Vite dev server → http://localhost:3000 (proxies API to :3001)
|
|
# Express API server → http://localhost:3001
|
|
```
|
|
|
|
> **Note:** `NODE_ENV=development` is for local development only. Never use it in Docker — the Express server does not serve the frontend in development mode.
|
|
|
|
---
|
|
|
|
## LAN Access
|
|
|
|
Serve Veritas Kanban to other devices on your local network (phones, other laptops, tablets).
|
|
|
|
LAN access is server mode, not localhost mode. Keep `VERITAS_AUTH_ENABLED=true`,
|
|
set `VERITAS_AUTH_LOCALHOST_BYPASS=false`, and use HTTPS, VPN, or a trusted
|
|
tunnel for browser/mobile sessions unless the network is strictly private and
|
|
temporary.
|
|
Unauthenticated `/metrics` scrapes are allowed only when
|
|
`PROMETHEUS_METRICS_PUBLIC=true`; otherwise use `PROMETHEUS_METRICS_TOKEN` or
|
|
an API key with telemetry read access.
|
|
|
|
### 1. Bind to all interfaces
|
|
|
|
By default the server listens on `127.0.0.1`. To accept connections from other devices, set `HOST=0.0.0.0`:
|
|
|
|
```bash
|
|
HOST=0.0.0.0 NODE_ENV=production node server/dist/index.js
|
|
```
|
|
|
|
Or add to `server/.env`:
|
|
|
|
```env
|
|
HOST=0.0.0.0
|
|
```
|
|
|
|
### 2. Allow Vite dev server (development only)
|
|
|
|
If you're running the Vite dev server in development mode, set `VITE_ALLOWED_HOSTS` so Vite accepts requests from your LAN IP:
|
|
|
|
```env
|
|
# server/.env or export before running pnpm dev
|
|
VITE_ALLOWED_HOSTS=192.168.1.100,my-machine.local
|
|
# Or allow all hosts (development only — never in production):
|
|
VITE_ALLOWED_HOSTS=*
|
|
```
|
|
|
|
This controls `vite.config.ts`'s `server.allowedHosts` setting.
|
|
|
|
### 3. Update CORS
|
|
|
|
Add your LAN IP or hostname to `CORS_ORIGINS` in `server/.env`:
|
|
|
|
```env
|
|
CORS_ORIGINS=http://localhost:3000,http://localhost:5173,http://192.168.1.100:3001
|
|
```
|
|
|
|
### 4. Find your LAN IP
|
|
|
|
```bash
|
|
# macOS / Linux
|
|
ip route get 1 | awk '{print $7; exit}'
|
|
# or
|
|
hostname -I | awk '{print $1}'
|
|
|
|
# macOS
|
|
ipconfig getifaddr en0
|
|
```
|
|
|
|
Your LAN URL will be `http://<your-ip>:3001`.
|
|
|
|
---
|
|
|
|
## Tailscale Serve
|
|
|
|
Tailscale Serve lets you expose Veritas Kanban securely to your tailnet (all your devices) without opening firewall ports. This was the primary use case from [@xechehot's original PR #126](https://github.com/BradGroux/veritas-kanban/pull/126).
|
|
|
|
Treat the Tailscale URL as a remote origin. Do not rely on localhost bypass for
|
|
tailnet clients, and verify `/api`, `/ws`, `/health/ready`, and
|
|
`/api/auth/status` through the served HTTPS URL.
|
|
|
|
### Option A: Root path (simplest)
|
|
|
|
Expose the app at `https://<your-machine>.ts.net/`:
|
|
|
|
```bash
|
|
# Start Veritas Kanban
|
|
NODE_ENV=production node server/dist/index.js
|
|
|
|
# Expose via Tailscale Serve (proxies HTTPS → localhost:3001)
|
|
tailscale serve https / http://localhost:3001
|
|
```
|
|
|
|
Access from any tailnet device at `https://<your-machine>.ts.net`.
|
|
|
|
### Option B: Sub-path routing (`/kanban/`)
|
|
|
|
If you want to share port 443 with other services and serve Veritas Kanban under `/kanban/`:
|
|
|
|
#### Step 1 — Build the frontend with the base path
|
|
|
|
The frontend must be built with `VITE_BASE_PATH=/kanban/` so all asset URLs, API calls, and client-side routes use the correct prefix:
|
|
|
|
```bash
|
|
VITE_BASE_PATH=/kanban/ pnpm --filter @veritas-kanban/web build
|
|
# Then rebuild the server (if needed)
|
|
pnpm --filter @veritas-kanban/server build
|
|
```
|
|
|
|
Or with Docker:
|
|
|
|
```bash
|
|
docker build --build-arg VITE_BASE_PATH=/kanban/ -t veritas-kanban .
|
|
```
|
|
|
|
#### Step 2 — Start the server
|
|
|
|
```bash
|
|
NODE_ENV=production node server/dist/index.js
|
|
```
|
|
|
|
#### Step 3 — Configure Tailscale Serve
|
|
|
|
```bash
|
|
# Route /kanban/ traffic to localhost:3001
|
|
tailscale serve https /kanban/ http://localhost:3001
|
|
|
|
# Verify the serve config
|
|
tailscale serve status
|
|
```
|
|
|
|
#### Step 4 — Update CORS
|
|
|
|
```env
|
|
CORS_ORIGINS=https://<your-machine>.ts.net
|
|
```
|
|
|
|
Access the app at `https://<your-machine>.ts.net/kanban/`.
|
|
|
|
### Tailscale Funnel (public internet access)
|
|
|
|
To expose beyond your tailnet (public internet):
|
|
|
|
```bash
|
|
tailscale funnel 443 on
|
|
```
|
|
|
|
> **Security:** Funnel makes your instance publicly accessible. Ensure `VERITAS_AUTH_ENABLED=true` and use a strong `VERITAS_ADMIN_KEY` before enabling Funnel.
|
|
|
|
---
|
|
|
|
## Reverse Proxy
|
|
|
|
For production deployments with TLS, use a reverse proxy in front of Veritas Kanban. Always set `TRUST_PROXY` when behind a proxy.
|
|
|
|
The supported reverse-proxy shape is same-origin: the proxy serves the app shell,
|
|
API, WebSocket, health endpoints, and future PWA assets from the same public
|
|
origin. Split-origin setups require exact `CORS_ORIGINS`, WebSocket upgrade
|
|
testing, and explicit token handling as described in ADR 0002.
|
|
|
|
### nginx
|
|
|
|
```env
|
|
# server/.env
|
|
TRUST_PROXY=1
|
|
CORS_ORIGINS=https://kanban.example.com
|
|
```
|
|
|
|
```nginx
|
|
upstream veritas {
|
|
server 127.0.0.1:3001;
|
|
keepalive 32;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name kanban.example.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/kanban.example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/kanban.example.com/privkey.pem;
|
|
|
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
|
|
|
# Proxy everything to Veritas Kanban
|
|
location / {
|
|
proxy_pass http://veritas;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
# WebSocket upgrade (real-time updates)
|
|
location /ws {
|
|
proxy_pass http://veritas;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
# Keep WebSocket connections alive
|
|
proxy_read_timeout 86400s;
|
|
proxy_send_timeout 86400s;
|
|
}
|
|
}
|
|
|
|
# HTTP → HTTPS redirect
|
|
server {
|
|
listen 80;
|
|
server_name kanban.example.com;
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
```
|
|
|
|
Reload nginx after editing:
|
|
|
|
```bash
|
|
sudo nginx -t && sudo systemctl reload nginx
|
|
```
|
|
|
|
### Caddy
|
|
|
|
Caddy handles TLS automatically (no certificate config needed):
|
|
|
|
```caddyfile
|
|
kanban.example.com {
|
|
reverse_proxy localhost:3001
|
|
}
|
|
```
|
|
|
|
```env
|
|
# server/.env
|
|
TRUST_PROXY=1
|
|
CORS_ORIGINS=https://kanban.example.com
|
|
```
|
|
|
|
Caddy handles WebSocket proxying and HTTP→HTTPS redirects automatically.
|
|
|
|
### Sub-path with nginx
|
|
|
|
To serve under `/kanban/` on a shared domain, build with `VITE_BASE_PATH` and strip the prefix in nginx:
|
|
|
|
```bash
|
|
VITE_BASE_PATH=/kanban/ pnpm --filter @veritas-kanban/web build
|
|
pnpm --filter @veritas-kanban/server build
|
|
```
|
|
|
|
```nginx
|
|
location /kanban/ {
|
|
proxy_pass http://127.0.0.1:3001/; # trailing slash strips the prefix
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
location /kanban/ws {
|
|
proxy_pass http://127.0.0.1:3001/ws;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_read_timeout 86400s;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Docker
|
|
|
|
Docker is the recommended approach for production deployments.
|
|
|
|
Do not use the demo Compose files (`docker-compose-demo.yml` or `demo/docker-compose.demo.yml`) for production or shared-network deployments. They are local demo configs and may disable auth for convenience. Use the authenticated production compose example below and generate fresh secrets.
|
|
|
|
### Quick start
|
|
|
|
```bash
|
|
# Clone and configure
|
|
git clone https://github.com/BradGroux/veritas-kanban.git
|
|
cd veritas-kanban
|
|
cp server/.env.example server/.env
|
|
# Edit server/.env — set VERITAS_ADMIN_KEY to a strong secret (≥ 32 chars)
|
|
|
|
# Build and start
|
|
docker compose up -d --build
|
|
|
|
# Verify
|
|
curl http://localhost:3001/health
|
|
# → {"status":"ok","timestamp":"..."}
|
|
```
|
|
|
|
The app is available at **http://localhost:3001**. Data persists in a named Docker volume (`kanban-data`).
|
|
|
|
### docker-compose.yml
|
|
|
|
```yaml
|
|
services:
|
|
veritas-kanban:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: veritas-kanban
|
|
ports:
|
|
- '3001:3001'
|
|
environment:
|
|
- NODE_ENV=production
|
|
- PORT=3001
|
|
- DATA_DIR=/app/data
|
|
- VERITAS_ADMIN_KEY=your-secure-admin-key-here # ≥ 32 chars
|
|
- VERITAS_JWT_SECRET=your-jwt-secret-here # prevents session resets on restart
|
|
# - CORS_ORIGINS=https://kanban.example.com
|
|
# - TRUST_PROXY=1 # if behind nginx/Caddy/Traefik
|
|
# - VERITAS_API_KEYS=agent1:key1:agent,readonly:key2:read-only
|
|
volumes:
|
|
- kanban-data:/app/data
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:3001/health']
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
volumes:
|
|
kanban-data:
|
|
driver: local
|
|
```
|
|
|
|
### Building with a sub-path
|
|
|
|
```bash
|
|
docker build --build-arg VITE_BASE_PATH=/kanban/ -t veritas-kanban .
|
|
```
|
|
|
|
### Common Docker commands
|
|
|
|
```bash
|
|
docker compose up -d --build # Build and start in background
|
|
docker compose logs -f # Follow logs
|
|
docker compose down # Stop and remove containers
|
|
docker compose pull # Pull latest image (if using a registry)
|
|
|
|
# Inspect health status
|
|
docker inspect --format='{{.State.Health.Status}}' veritas-kanban
|
|
```
|
|
|
|
### Data persistence
|
|
|
|
The `DATA_DIR=/app/data` volume holds all persistent data:
|
|
|
|
```
|
|
/app/data/
|
|
├── tasks/
|
|
│ ├── active/ # Active task markdown files
|
|
│ └── archive/ # Archived tasks
|
|
└── .veritas-kanban/
|
|
├── config.json # App settings
|
|
├── security.json # JWT secret (if VERITAS_JWT_SECRET not set)
|
|
└── logs/ # Application logs
|
|
```
|
|
|
|
This tree is illustrative, not exhaustive. Veritas also stores workflows,
|
|
runtime evidence, telemetry, provider records, and other governed domains under
|
|
the same canonical root. Back up the entire stopped-writer volume.
|
|
|
|
**Without a named volume, data is lost on every `docker compose down`.** Always use a volume or bind mount.
|
|
|
|
For `VERITAS_STORAGE=sqlite`, persistence is not enough: the authoritative
|
|
database must resolve to a verified durable local filesystem. Detected NFS,
|
|
SMB/CIFS, FUSE, WebDAV, NAS, volatile filesystems, container overlay, unknown
|
|
probes, and database-file symlinks refuse startup. Synchronized cloud folders
|
|
remain unsupported but may look local to the operating system, so keep them out
|
|
of the authoritative path. Copy only completed Maintenance exports/backups to
|
|
remote storage.
|
|
See [SQLite Filesystem Safety Posture](../SQLITE-SCHEMA.md#filesystem-safety-posture).
|
|
|
|
---
|
|
|
|
## Security
|
|
|
|
### Generate strong keys
|
|
|
|
```bash
|
|
# Admin key (≥ 32 chars)
|
|
openssl rand -hex 32
|
|
|
|
# JWT secret
|
|
openssl rand -hex 64
|
|
```
|
|
|
|
### Minimum production config
|
|
|
|
```env
|
|
VERITAS_AUTH_ENABLED=true
|
|
VERITAS_ADMIN_KEY=<output of openssl rand -hex 32>
|
|
VERITAS_JWT_SECRET=<output of openssl rand -hex 64>
|
|
VERITAS_AUTH_LOCALHOST_BYPASS=false
|
|
```
|
|
|
|
When exposing Veritas beyond loopback, also use `NODE_ENV=production` and keep
|
|
the web client, `/api`, `/ws`, health endpoints, PWA assets, and service-worker
|
|
scope on one trusted origin whenever possible. Split-origin deployments must set
|
|
exact `CORS_ORIGINS` entries and confirm WebSocket origin/upgrade behavior.
|
|
Prometheus `/metrics` fails closed outside explicit loopback development binds;
|
|
use `PROMETHEUS_METRICS_TOKEN` or `PROMETHEUS_METRICS_PUBLIC=true` for external
|
|
scrapers.
|
|
Mobile install steps and offline-shell behavior are documented in
|
|
[PWA install](PWA_INSTALL.md).
|
|
|
|
### API keys for agents
|
|
|
|
Grant agents scoped access without giving them the admin key:
|
|
|
|
```env
|
|
# Format: name:key:role,...
|
|
# Roles: admin | agent | read-only
|
|
VERITAS_API_KEYS=my-agent:vk_abc123:agent,dashboard:vk_xyz456:read-only
|
|
```
|
|
|
|
Role permissions:
|
|
|
|
| Role | Access |
|
|
| ----------- | ---------------------------------------------- |
|
|
| `admin` | Full access to all endpoints |
|
|
| `agent` | Read/write tasks, run agents, manage worktrees |
|
|
| `read-only` | GET endpoints only (view tasks, read config) |
|
|
|
|
### Authentication methods
|
|
|
|
```bash
|
|
# Bearer token
|
|
curl -H "Authorization: Bearer <api-key>" http://localhost:3001/api/tasks
|
|
|
|
# X-API-Key header
|
|
curl -H "X-API-Key: <api-key>" http://localhost:3001/api/tasks
|
|
|
|
# Query parameter (WebSocket)
|
|
wscat -c "ws://localhost:3001/ws?api_key=<api-key>"
|
|
```
|
|
|
|
HTTP requests must pass API keys in `Authorization: Bearer <key>` or
|
|
`X-API-Key`. Do not put long-lived credentials in HTTP query strings, bookmarks,
|
|
QR codes, screenshots, reverse-proxy logs, or shareable URLs. The WebSocket
|
|
`api_key` query fallback exists for clients that cannot set headers during the
|
|
upgrade.
|
|
|
|
### TRUST_PROXY
|
|
|
|
Always set `TRUST_PROXY=1` when running behind a reverse proxy. Without it, Express uses the proxy's IP for rate limiting instead of the real client IP, so all users share one rate limit bucket.
|
|
|
|
```env
|
|
TRUST_PROXY=1 # One proxy hop (nginx, Caddy directly in front)
|
|
TRUST_PROXY=2 # Two hops (CDN + reverse proxy)
|
|
TRUST_PROXY=loopback # Only trust loopback (127.0.0.1, ::1)
|
|
```
|
|
|
|
> **Warning:** `TRUST_PROXY=true` is blocked by default — it trusts all proxies and is unsafe on the public internet. Use a hop count or subnet instead.
|
|
|
|
---
|
|
|
|
## Environment Variables Reference
|
|
|
|
All variables live in `server/.env` (copy from `server/.env.example`).
|
|
|
|
### Server
|
|
|
|
| Variable | Default | Description |
|
|
| ----------- | ----------- | -------------------------------------------------------------- |
|
|
| `PORT` | `3001` | HTTP server port |
|
|
| `HOST` | `127.0.0.1` | Bind address. Set `0.0.0.0` for LAN/container access |
|
|
| `NODE_ENV` | — | `production` for production. **Never `development` in Docker** |
|
|
| `LOG_LEVEL` | `info` | `trace` / `debug` / `info` / `warn` / `error` / `fatal` |
|
|
|
|
### Authentication
|
|
|
|
| Variable | Default | Description |
|
|
| ------------------------------- | ----------- | ---------------------------------------------------------------------- |
|
|
| `VERITAS_AUTH_ENABLED` | `true` | Enable authentication. Set `false` only for trusted local use |
|
|
| `VERITAS_ADMIN_KEY` | — | Admin API key. **Must be ≥ 32 chars.** Required for production |
|
|
| `VERITAS_API_KEYS` | — | Additional keys. Format: `name:key:role,name2:key2:role2` |
|
|
| `VERITAS_JWT_SECRET` | auto-gen | JWT signing secret. Unset = auto-generated (sessions reset on restart) |
|
|
| `VERITAS_AUTH_LOCALHOST_BYPASS` | `false` | Allow unauthenticated localhost requests |
|
|
| `VERITAS_AUTH_LOCALHOST_ROLE` | `read-only` | Role for localhost bypass: `read-only`, `agent`, or `admin` |
|
|
|
|
### Networking
|
|
|
|
| Variable | Default | Description |
|
|
| ---------------- | --------------------------- | ---------------------------------------------------------------------------- |
|
|
| `CORS_ORIGINS` | `http://localhost:3000,...` | Comma-separated allowed CORS origins |
|
|
| `TRUST_PROXY` | — | Express proxy trust. Use `1` for single-hop (nginx/Caddy). `true` is blocked |
|
|
| `RATE_LIMIT_MAX` | `300` | Max API requests/minute/IP (localhost exempt) |
|
|
|
|
### Prometheus metrics
|
|
|
|
`GET /metrics` is public only in local development. In production, scrape it with one of these explicit configurations:
|
|
|
|
```yaml
|
|
scrape_configs:
|
|
- job_name: veritas-kanban
|
|
metrics_path: /metrics
|
|
static_configs:
|
|
- targets: ['veritas.example.com']
|
|
bearer_token: '<PROMETHEUS_METRICS_TOKEN>'
|
|
```
|
|
|
|
Set `PROMETHEUS_METRICS_TOKEN` on the Veritas server to the same secret, or use a normal Veritas API key with `telemetry:read` permission in the `Authorization: Bearer <key>` header. Only set `PROMETHEUS_METRICS_PUBLIC=true` for trusted private networks where unauthenticated metrics are intentional.
|
|
|
|
### Data & Storage
|
|
|
|
| Variable | Default | Description |
|
|
| -------------------------- | -------------------- | ------------------------------------------------------- |
|
|
| `VERITAS_DATA_DIR` | Project root | Storage root used when `DATA_DIR` is unset |
|
|
| `DATA_DIR` | `/app/data` (Docker) | Preferred storage root; takes precedence |
|
|
| `TELEMETRY_RETENTION_DAYS` | `30` | Days to keep telemetry event files |
|
|
| `TELEMETRY_COMPRESS_DAYS` | `7` | Days after which telemetry files are gzip-compressed |
|
|
|
|
### Frontend (build-time)
|
|
|
|
| Variable | Default | Description |
|
|
| -------------------- | ------- | ----------------------------------------------------------------------------------- |
|
|
| `VITE_BASE_PATH` | `/` | Sub-path prefix for the frontend (e.g., `/kanban/`). Set at build time, not runtime |
|
|
| `VITE_ALLOWED_HOSTS` | — | Comma-separated hostnames allowed by Vite dev server (dev only). `*` allows all |
|
|
|
|
### Integration
|
|
|
|
| Variable | Default | Description |
|
|
| ------------------------ | ------------------------ | ----------------------------------------------- |
|
|
| `CLAWDBOT_GATEWAY` | `http://127.0.0.1:18789` | OpenClaw gateway URL for AI agent orchestration |
|
|
| `VERITAS_WEBHOOK_URL` | — | Push task/chat events to an external service |
|
|
| `VERITAS_WEBHOOK_SECRET` | — | HMAC-SHA256 secret for webhook payload signing |
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Cannot GET / (UI not loading)
|
|
|
|
**Cause:** `NODE_ENV=development` is set in Docker. In dev mode, Express is API-only — it does not serve the frontend.
|
|
|
|
**Fix:** Remove `NODE_ENV=development` from your Docker environment. The Dockerfile defaults to `production`.
|
|
|
|
---
|
|
|
|
### CORS error in browser console
|
|
|
|
**Cause:** Your frontend origin is not in `CORS_ORIGINS`.
|
|
|
|
**Fix:** Add the exact origin (scheme + hostname + port) to `CORS_ORIGINS`:
|
|
|
|
```env
|
|
CORS_ORIGINS=https://kanban.example.com,http://192.168.1.100:3001
|
|
```
|
|
|
|
No trailing slashes. The origin must match exactly what the browser sends in the `Origin` header.
|
|
|
|
---
|
|
|
|
### WebSocket connection fails or disconnects immediately
|
|
|
|
1. Check that your reverse proxy forwards WebSocket upgrade headers:
|
|
- **nginx:** Needs `proxy_set_header Upgrade $http_upgrade; Connection "upgrade";` in the `/ws` location block.
|
|
- **Caddy:** Handles WebSocket automatically — no config needed.
|
|
2. Check proxy timeout: WebSocket connections are long-lived. Set `proxy_read_timeout 86400s` in nginx.
|
|
3. Verify `CORS_ORIGINS` includes the WebSocket origin.
|
|
|
|
---
|
|
|
|
### Assets 404 after sub-path deployment
|
|
|
|
**Cause:** Frontend was built without `VITE_BASE_PATH`.
|
|
|
|
**Fix:** Rebuild with the correct base path:
|
|
|
|
```bash
|
|
VITE_BASE_PATH=/kanban/ pnpm --filter @veritas-kanban/web build
|
|
```
|
|
|
|
Or with Docker:
|
|
|
|
```bash
|
|
docker build --build-arg VITE_BASE_PATH=/kanban/ -t veritas-kanban .
|
|
```
|
|
|
|
---
|
|
|
|
### Rate limiting errors (ERR_ERL_UNEXPECTED_X_FORWARDED_FOR)
|
|
|
|
**Cause:** Your reverse proxy sends `X-Forwarded-For` but `TRUST_PROXY` is not set.
|
|
|
|
**Fix:**
|
|
|
|
```env
|
|
TRUST_PROXY=1
|
|
```
|
|
|
|
---
|
|
|
|
### Tailscale: "ERR_TOO_MANY_REDIRECTS" or assets not loading
|
|
|
|
**Cause:** `VITE_BASE_PATH` not set when using Tailscale Serve with sub-path routing.
|
|
|
|
**Fix:** Rebuild with `VITE_BASE_PATH=/kanban/` (see [Tailscale Serve](#tailscale-serve) above).
|
|
|
|
---
|
|
|
|
### Weak admin key warning at startup
|
|
|
|
The server rejects or warns on `VERITAS_ADMIN_KEY` shorter than 32 characters. Generate a proper key:
|
|
|
|
```bash
|
|
openssl rand -hex 32
|
|
```
|
|
|
|
---
|
|
|
|
### Sessions reset after container restart
|
|
|
|
**Cause:** `VERITAS_JWT_SECRET` is not set, so a new secret is generated each startup.
|
|
|
|
**Fix:** Set a persistent JWT secret:
|
|
|
|
```bash
|
|
openssl rand -hex 64
|
|
# Add to docker-compose.yml environment or server/.env
|
|
VERITAS_JWT_SECRET=<output>
|
|
```
|
|
|
|
---
|
|
|
|
### Check server health
|
|
|
|
```bash
|
|
curl http://localhost:3001/health
|
|
# → {"status":"ok","timestamp":"..."}
|
|
```
|
|
|
|
Auth diagnostics (requires admin key):
|
|
|
|
```bash
|
|
curl -H "X-API-Key: your-admin-key" http://localhost:3001/api/auth/diagnostics
|
|
```
|
|
|
|
---
|
|
|
|
_For general deployment (Docker, bare metal, systemd, reverse proxy) see also [docs/DEPLOYMENT.md](../DEPLOYMENT.md)._
|