Files
Containr/docs/guides/OPERATIONS.md
T
Tomas Dvorak e881747388 feat: personal access tokens with scoped bearer auth
Add cnp_ personal access tokens so the CLI, MCP server, and external
agents authenticate without a browser session — the agent-first spine
from ROADMAP2 phase A. Modeled on agent_auth_tokens: raw token shown
once, only SHA-256 hash stored.

- user_tokens table (goose migration + sqlc), name/prefix/scope/expiry,
  owner-scoped revoke, last_used_at stamping
- middleware: cnp_ bearer resolution, read scope gates mutations with
  403 SCOPE_INSUFFICIENT, non-admin scopes cap is_admin even for admin
  owners — a token never outranks its scope or its owner
- GET/POST/DELETE /api/v1/user/tokens + OpenAPI + generated types
- Settings UI: create with scope/expiry pickers, one-time token display
  with copy, list with last-used/expiry, confirm-revoke
- docs: OPERATIONS.md section, README feature line

Verified end-to-end against live Postgres: create/list/revoke, read
scope blocks PUT (403), write PAT on admin owner blocked from admin
routes (403), admin scope works, revoked token returns 401.

Authored By: TDvorak <[email protected]>
2026-10-08 14:31:29 +02:00

120 lines
4.6 KiB
Markdown

# Operations
Day-2 features for running services: scheduled jobs, ad-hoc exec, managed
databases, preview environments, security scans, and high availability.
## Cron jobs
Service detail → **Cron**. Standard 5-field cron expressions (`robfig/cron`
syntax), per-job timezone, enable/disable, manual trigger, and execution
history with captured stdout/stderr.
- The scheduler ticks every minute and runs due jobs via `docker exec`
(`sh -c <command>`) inside the service's container.
- Executions are retained per-job (`retention` count) and visible in the UI.
- Requires a running container — a stopped service's due job records a
failure, it does not queue.
```
GET/POST /api/v1/cron-jobs PUT/DELETE /api/v1/cron-jobs/:id
POST /api/v1/cron-jobs/:id/trigger
GET /api/v1/cron-jobs/:id/executions
```
## Exec console
Service detail → **Console**. One-off `sh -c` commands against the running
container: 30s ceiling, 64KB output cap, exit code + captured output shown
per run. Debugging aid — not an interactive shell.
```
POST /api/v1/services/:id/exec {command}
```
## Managed databases
**/databases** page — provision PostgreSQL/Redis/MySQL/MariaDB/MongoDB/
ClickHouse/Dragonfly as managed containers on the `containr` network.
- Start/stop/restart actions; connection URL with copy.
- **Backups**: manual snapshot now, or set a cron expression
(`backup_schedule`) — due backups run inside the same scheduler tick as
cron jobs. Archives live in the `containr-db-backups` volume and are
downloadable (`GET /databases/:id/backups/:bid/download`) and restorable.
- **Bind to service**: injects `connection_url` into a service's variables
as a secret (`DATABASE_URL` by default). Redeploy the service afterward —
variables are read at deploy time.
## Preview environments
Service detail → **Previews**. Create a preview per branch/PR with a TTL,
promote it to production, or delete it.
Current limitation: previews are bookkeeping records — they do not build,
deploy, or route a real container. The generated `*.preview.containr.local`
URL is a placeholder. Real preview runtime is tracked on the roadmap.
## Security scans
**/security** page — pick a project, run a scan (dependency / configuration
/ comprehensive, optionally scoped to one service).
The scanner is heuristic config analysis, not Trivy/Grype: it flags
unpinned image tags, `http://` source/public URLs, `npm install` without
`npm ci`, `pip install` without hashes, `curl|sh` build steps, missing
health checks, and similar smells. Findings get severity badges and can be
resolved or ignored; the page also shows a security score, open/resolved
counts, scan history, and compliance status.
```
POST /api/v1/security/scans {project_id, service_id?, scan_type}
GET /api/v1/projects/:id/vulnerabilities
PUT /api/v1/vulnerabilities/:id {status: open|resolved|ignored}
GET /api/v1/projects/:id/security/metrics
GET /api/v1/projects/:id/security/history
```
## High availability
**/ha** page — manager enable/disable, node/health-check/alert status
cards, manual failover (with confirmation), per-service failover policies
(project → service picker, strategy, min healthy nodes, max failures),
active alerts with resolve, and health check results.
HA state is in-memory like autoscaling — policies and alerts reset on
backend restart.
## Node agents
**/usage** page → *Connect node*: issue a per-node token (`cagt_…`, shown
once), copy the prefilled install command, revoke tokens when done. Agents
register over `POST /api/agents/register` and heartbeat via
`POST /api/agents/heartbeat`.
## Personal access tokens
**Settings → Personal Access Tokens**: issue `cnp_…` bearer tokens for the
CLI, MCP server, and scripts — the same privileges as your account, bounded
by scope. The raw token is shown once at creation; only its SHA-256 hash is
stored.
Scopes:
- `read` — `GET`/`HEAD`/`OPTIONS` only; mutations return `403 SCOPE_INSUFFICIENT`.
- `write` — everything except admin routes.
- `admin` — full access; requires a platform admin account, and the token is
still capped at the owner's privileges.
A token can never outrank its owner: non-admin scopes report
`is_admin = false` even on admin accounts.
```
GET /api/v1/user/tokens list (metadata only, never hashes)
POST /api/v1/user/tokens {name, scope?, expires_in_days?} → raw token once
DELETE /api/v1/user/tokens/:id revoke (owner-scoped)
```
Use as `Authorization: Bearer cnp_…`, or `containr auth login cnp_…`.
Revocation takes effect on the next request; `last_used_at` updates on each
authenticated call.