can-hub-web — web admin panel

Browser panel for the same admin surface as can-hub-cli: peers, agents, clients, interfaces, live telemetry, kick, interface config, pins and ACLs — plus its own users, groups and audit log.

It is a separate daemon, not part of the hub: an ordinary admin client of the hub's unix socket on one side, an HTTP server (REST API + embedded React SPA) towards browsers on the other. The hub accepts the admin role only on local transports, so can-hub-web must run on the hub host.

Install

Built from source today (Rust >= 1.80 and Node 20+):

web/build-release.sh                      # UI + daemon with the SPA embedded
sudo install web/daemon/target/release/can-hub-web /usr/bin/
sudo install -m644 packaging/systemd/can-hub-web.service /lib/systemd/system/
sudo install -m644 packaging/web.conf /etc/can-hub/web.conf
sudo systemctl enable --now can-hub-web

DESTDIR=<stage> web/build-release.sh stages that exact layout for packaging. The service runs as the can-hub user (so it reaches /run/can-hub/hub.sock), after can-hub.service, with the database at /var/lib/can-hub/web.db.

Run

can-hub-web \
  --connect /run/can-hub/hub.sock \   # hub admin socket (default)
  --listen 127.0.0.1:8080 \           # HTTP bind (default loopback)
  --db /var/lib/can-hub/web.db \      # users/groups/sessions/audit
  [--secure-cookies]                  # session cookie Secure (behind TLS)

First run

With zero users the panel serves only a setup page that creates the first admin (placed in an admins group holding every permission). Headless alternative:

CANHUB_WEB_PASSWORD=secret can-hub-web --add-user alice --db /var/lib/can-hub/web.db

Users, groups, permissions

The daemon holds full admin power over the socket; who may do what in the browser is enforced in the web layer. Users (argon2id passwords) belong to groups; groups hold permissions by operation class — views.read, peers.kick, interfaces.config, pins.manage, acl.manage, users.manage — and a user's effective permission is the union across its groups. The last users.manage holder cannot be removed (anti-lockout). All of this lives in web.db; the hub's hub.db and the agent/client identities are untouched.

Sessions are server-side over an HttpOnly, SameSite=Strict cookie (12 h idle, 7 day absolute expiry); mutating requests carry a CSRF token (the UI handles it); failed logins are rate limited per client IP; every mutating operation lands in the audit log (Audit tab or /api/audit).

Telemetry

The hub only keeps cumulative counters. The daemon polls them over the admin socket, derives rates from successive samples, and fans them out to all subscribed browsers over one WebSocket pipeline — one poller regardless of how many dashboards are open.

Behind TLS

The daemon serves plain HTTP on loopback. Expose it through a TLS reverse proxy on the same host, forwarding the WebSocket upgrade, and pass --secure-cookies:

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

--trusted-proxy makes login rate limiting key on X-Forwarded-For when behind such a proxy.

REST API

The JSON API is first-class and scriptable; the contract is web/openapi.yaml (OpenAPI 3.1), validated by the end-to-end suite. Development workflow (Vite dev server, embed feature) in web/README.md.