git init
This commit is contained in:
112
control-plane/README.md
Normal file
112
control-plane/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Control Plane
|
||||
|
||||
The control plane is a small Go API, PostgreSQL 16, and a React/TypeScript UI
|
||||
served by unprivileged nginx. The browser only reaches nginx/backend; agent
|
||||
credentials are mounted into the backend as files and never enter PostgreSQL or
|
||||
the browser.
|
||||
|
||||
## Docker deployment
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
mkdir -p secrets
|
||||
openssl rand -hex 32 > secrets/db_password
|
||||
openssl rand -base64 24 > secrets/admin_password
|
||||
openssl rand -hex 32 > secrets/integration_token
|
||||
openssl rand -base64 24 > secrets/grafana_admin_password
|
||||
openssl rand -hex 32 > secrets/grafana_secret_key
|
||||
# Securely copy the exact token from one agent for bootstrap:
|
||||
install -m 600 /secure/source/control-plane.token secrets/agent_token
|
||||
chmod 600 secrets/*
|
||||
docker compose config
|
||||
docker compose up -d --build
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Open `http://127.0.0.1:8080` through a TLS reverse proxy. With plain local HTTP
|
||||
for development only, set `COOKIE_SECURE=false`. The default host binding is
|
||||
loopback. PostgreSQL and backend ports are not published.
|
||||
|
||||
Set `BOOTSTRAP_SERVER_NAME` and `BOOTSTRAP_SERVER_ENDPOINT` before the first
|
||||
start, or enroll a server in the UI as Administrator. Endpoints must use a
|
||||
literal loopback/private IP and explicit port. `credential_ref` is a filename in
|
||||
`/run/secrets`, never token material. Add another Compose secret mount for each
|
||||
additional server token.
|
||||
|
||||
## Authentication and database
|
||||
|
||||
On an empty database, the backend reads `secrets/admin_password`, hashes it with
|
||||
Argon2id, and creates `INITIAL_ADMIN_USERNAME`. Change the bootstrap password
|
||||
after adding a user-management workflow or rotate it through an audited database
|
||||
administrative procedure. Sessions are stored server-side, expire after 12
|
||||
hours, use Secure/HttpOnly/SameSite cookies, and require a CSRF token on writes.
|
||||
|
||||
Roles are Viewer (read), Operator (Fail2Ban and service actions), and
|
||||
Administrator (server/firewall/policy administration). Every current mutation
|
||||
is audited with actor, target, result, time, and source IP. Secrets are excluded.
|
||||
|
||||
The backend creates schema on startup. One-minute metric samples are retained 30
|
||||
days (`METRICS_RETENTION=720h`) and agent logs seven days
|
||||
(`LOGS_RETENTION=168h`). PostgreSQL is the only durable history store.
|
||||
|
||||
## Grafana and Loki-compatible access
|
||||
|
||||
The backend is both a constrained Prometheus-compatible metrics source and a
|
||||
Loki-compatible read-only log source. A separate Loki server is not required:
|
||||
this is what preserves the required `Grafana -> backend -> agent` boundary.
|
||||
Both APIs require `Authorization: Bearer <integration_token>` and expose no
|
||||
agent endpoint or credential.
|
||||
|
||||
For the included provisioned instance:
|
||||
|
||||
```bash
|
||||
docker compose --profile observability up -d --build
|
||||
```
|
||||
|
||||
Open `http://127.0.0.1:3000`, use the `admin` account and the value in
|
||||
`secrets/grafana_admin_password`. The two datasources and `VPS Control Overview`
|
||||
dashboard are provisioned automatically.
|
||||
`grafana_secret_key` encrypts the datasource credential stored by Grafana and
|
||||
must be backed up; changing it invalidates encrypted secure data.
|
||||
|
||||
For an external Grafana, configure:
|
||||
|
||||
- Prometheus datasource URL: `https://vps-control.example/integrations/prometheus`
|
||||
- Loki datasource URL: `https://vps-control.example/integrations/loki`
|
||||
- Custom HTTP header on both: `Authorization: Bearer <integration_token>`
|
||||
|
||||
The frontend proxy publishes these protected routes. Restrict them to Grafana's
|
||||
source network at the outer TLS proxy when possible. Supported queries are a
|
||||
deliberately small subset: direct `vps_control_*` metric selectors and Loki
|
||||
stream matchers (`=` or `=~`) on `server_id`, `server_name`, `unit`, and
|
||||
`priority`.
|
||||
|
||||
## TLS, backup, and upgrades
|
||||
|
||||
Terminate TLS at Caddy, nginx, Traefik, or a managed load balancer and forward to
|
||||
the loopback frontend port. Preserve the original client IP only from a trusted
|
||||
proxy. Keep `COOKIE_SECURE=true` in production.
|
||||
|
||||
Back up with:
|
||||
|
||||
```bash
|
||||
docker compose exec -T postgres pg_dump -U vps_control -Fc vps_control > vps-control.dump
|
||||
```
|
||||
|
||||
Also back up `.env`, reverse-proxy/TLS configuration, and secret files through a
|
||||
secret manager. Test restore into a separate database. For upgrades, take a
|
||||
backup, pin image/source revisions, build, then `docker compose up -d`; check
|
||||
health and Audit. Migrations are forward-only in v1, so database restore is the
|
||||
rollback path.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
cd backend && go test ./... && go vet ./...
|
||||
cd ../frontend && npm ci && npm run lint && npm run typecheck
|
||||
npm test -- --run && npm run build
|
||||
```
|
||||
|
||||
The frontend Vite server proxies `/api` to `localhost:8080`. Use a development
|
||||
PostgreSQL and `COOKIE_SECURE=false`. Never point development at a production
|
||||
agent credential.
|
||||
Reference in New Issue
Block a user