Files
vps-control/docs/architecture.md
DizelBadCoder c4dc19cd41
Some checks failed
CI / containers (push) Has been cancelled
CI / agent (push) Has been cancelled
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
git init
2026-09-19 19:31:36 +03:00

4.2 KiB

Architecture

Components and trust boundaries

The Rust agent is a stateless, pull-oriented HTTP API. It stores no metrics or log history and does no background polling. /proc, /sys, journald, and fixed read-only utilities are queried only when the control plane asks. The Go backend owns users, sessions, servers, metric/log samples, retention, and audit. React is served behind nginx and only calls the backend.

Grafana also talks only to the backend. The backend implements the constrained read portions of the Prometheus and Loki HTTP APIs, backed by PostgreSQL. There is intentionally no Grafana-to-agent or Loki-to-agent path and no agent token is ever provisioned into Grafana.

Agent transport is expected to be WireGuard plus a 256-bit application token, or TLS plus that token. The listen address is loopback by default. Tokens live in files outside configuration and are never returned by an API.

Agent API v1

All /v1 routes require Authorization: Bearer …; /healthz only returns liveness. JSON bodies are capped at 64 KiB. Read traffic is capped at 10 rps, writes at 2 rps, burst 20, with 32 concurrent requests and five-second timeouts.

Method Route Purpose
GET /v1/system/status CPU, load, memory, swap, filesystems, uptime
GET /v1/network/interfaces interfaces, addresses and counters
GET /v1/network/routes kernel routes
GET /v1/network/listeners listening TCP/UDP sockets
GET /v1/network/connections current TCP/UDP sockets
GET /v1/firewall/rules backend and read-only ruleset
GET /v1/fail2ban/status installation and jail summary
GET /v1/fail2ban/jails/:jail validated jail detail
POST /v1/fail2ban/ban validated IP and jail ban
POST /v1/fail2ban/unban validated IP and jail unban
GET /v1/wireguard/status interface and peer dump
GET /v1/services allow-listed services only
POST /v1/services/:name/restart allow-listed restart only
GET /v1/logs cursor-paginated journald rows from allow-listed units

No generic command route exists. Child processes receive a fixed executable and fixed subcommand plus individually validated argv values.

Data model

  • users(id, username, password_hash, role, disabled, timestamps)
  • sessions(id_hash, user_id, csrf_hash, expires_at, source_ip)
  • servers(id, name, hostname, agent_endpoint, credential_ref, tags, status, last_seen, timestamps)
  • metric_samples(server_id, sampled_at, cpu, memory, disk, rx, tx, connections, bans) with 30-day retention
  • agent_log_entries(server_id, journal_cursor, event_at, unit, priority, message, boot_id, pid) with configurable seven-day retention
  • audit_events(actor, server, action, target, before, after, result, source_ip, created_at); secrets are excluded

Credentials are indirect references to Docker secrets or environment variables, not database values. Database migrations are append-only and run on startup.

Observability compatibility APIs

/integrations/prometheus accepts direct selectors for the fixed vps_control_* metric set. /integrations/loki accepts bounded stream selectors over server_id, server_name, unit, and priority. Full PromQL, arbitrary LogQL pipelines, writes, remote-write, and Loki push APIs are deliberately not implemented. Queries are bearer-authenticated with an independent secret, rate-limited, range-limited to 31 days, and result-limited.

The data path is:

Grafana ──Prometheus/Loki read APIs──> backend ──semantic polling──> VPS agent
                                           │
                                      PostgreSQL

Privilege design

The agent has no ambient capabilities and runs as vps-agent. Linux usually permits the read-only endpoints without elevation. Fail2Ban mutations and service restarts need administrator-provided sudoers or polkit rules restricted to each approved verb/name. The shipped service does not install such policy automatically. Managed firewall mutation is out of v1 because giving the main daemon CAP_NET_ADMIN violates least privilege; its future helper protocol must provide prepare/apply/health/confirm/commit and persistent timed rollback.