Overview
roomy hub serve --listen 0.0.0.0:9478 --data-dir /var/lib/roomy-hub \
--agent-token-file /etc/roomy/hub.agent.token --admin-token-file /etc/roomy/hub.admin.token
roomy hub status | hosts | host NAME | remove NAME | events # talk to a hub (--url, --token)
roomy hub policy list | get NAME | push NAME FILE | remove NAME # centrally managed policies
roomy hub demo --data-dir ~/.roomy/hub-demo --hosts 12 # synthetic fleet to try the dashboard
roomy hub token # random token for the two files above
The hub is built like the monitoring you already run: hosts push a few kilobytes of plain JSON over HTTP on a timer, the hub writes them to disk, and the dashboard and metrics are computed from those files. There is no agent to install beyond roomy itself, no cloud component and no telemetry.
The screenshot is the built-in demo fleet (roomy hub demo), not a customer.
Running the hub
On a laptop, to have a look
roomy hub demo && roomy hub serve --data-dir ~/.roomy/hub-demo
open http://127.0.0.1:9478/
roomy hub serve with no flags listens on 127.0.0.1:9478 with no tokens, which is fine on localhost.
With systemd (from the packages)
roomy hub token | sudo tee /etc/roomy/hub.agent.token >/dev/null
roomy hub token | sudo tee /etc/roomy/hub.admin.token >/dev/null
sudo chmod 600 /etc/roomy/hub.*.token
sudoedit /etc/roomy/hub.env # ROOMY_HUB_LISTEN=0.0.0.0:9478, ROOMY_HUB_TITLE=…
sudo systemctl enable --now roomy-hub.service
roomy-hub.service runs as a DynamicUser with state in /var/lib/roomy-hub, settings in /etc/roomy/hub.env, and the two token files above.
In a container
docker run -d --name roomy-hub -p 9478:9478 -v roomy-hub:/data ghcr.io/tdermendjiev/roomy \
hub serve --listen 0.0.0.0:9478 --data-dir /data --agent-token … --admin-token …
Options
| Flag | Default | Meaning |
|---|---|---|
--listen H:P | 127.0.0.1:9478 | Address to bind. Also $ROOMY_HUB_LISTEN in the service. |
--data-dir DIR | ~/.roomy/hub | Where hosts, policies and events are kept. Also $ROOMY_HUB_DATA. |
--agent-token T / --agent-token-file F | none | Token hosts use to post reports and fetch policies. |
--admin-token T / --admin-token-file F | none | Token for the dashboard, the API and metrics. |
--stale-after 24h | 24h | When a host with unknown interval counts as stale. |
--license TOKEN | see Editions | Fleet license; sets the host allowance and the organisation name. |
--title NAME | Roomy Hub | Shown in the dashboard header. |
Tokens and security
Two bearer tokens, compared in constant time.
- The agent token can only post reports and fetch managed policies. A compromised host cannot read the inventory or change a policy.
- The admin token does everything else: the dashboard, the API, metrics, pushing policies, removing hosts.
Pass a token as Authorization: Bearer <token> or as ?token=. Without any token configured the API is open, which is fine on 127.0.0.1 and a warning anywhere else. The dashboard asks for the admin token once and keeps it in the browser's local storage.
roomy hub CLI handle an https:// hub URL through the system curl, so TLS to a reverse proxy works without Roomy linking a TLS stack.How a host reports
In the host's policy:
{ "hub": { "url": "http://hub.internal:9478", "tokenFile": "/etc/roomy/hub.token",
"tags": { "role": "agent", "team": "platform" }, "policy": "agent-host" } }
or, when writing the policy:
roomy fleet policy init --profile agent-host --hub http://hub.internal:9478 \
--hub-policy agent-host --tag role=agent --output /etc/roomy/policy.json
After every roomy fleet run (and every cycle of fleet serve) the host posts a HubHostReport: host, platform, Roomy and catalog version, policy name, edition, tags, what the scan found by tier and category, what the policy selected, what was removed, cumulative totals from the audit log, quarantine size, disk space, the audit chain head (seq + hash), and the largest findings (hub.topItems, default 25, warning tier included so the dashboard can show what the policy is not allowed to remove). A few kilobytes. Never file contents.
The audit log records hubReported, or error with the reason if the hub was unreachable. A hub outage never stops a run, and never makes one more aggressive.
fleet serve reports its interval so the hub can tell a quiet host from a dead one; timer-driven hosts set hub.intervalSeconds or inherit the hub's --stale-after.
Centrally managed policies
roomy hub policy push ci-runner policy.json # validate and store on the hub
roomy hub policy list
roomy hub policy get ci-runner
A host whose local policy sets hub.policy: "ci-runner" fetches it before every run and uses it instead of the local file, keeping only the local hub, license, scan.home and lockFile sections. If the hub cannot be reached the host runs its local policy and says so in the log. --local-policy on fleet run and fleet serve skips the pull.
So the local file on each host can be a two-line bootstrap, and a policy change is one push plus the next run. The Policies view shows each managed policy and which hosts run it.
Host status
| Status | Meaning |
|---|---|
| healthy | Reported within the window, last run had no failures, disk above 10% free. |
| attention | Reported, but: disposal failures in the last run, under 10% free, over the license allowance, or an expired fleet license. |
| stale | No report for 2.5× the host's interval (minimum 15 minutes), or --stale-after when the interval is unknown. |
Hosts that are over the license allowance still appear with their reports; only new hosts beyond it are refused with HTTP 402 and an event. Removing a host (roomy hub remove, or the dashboard) drops it from the inventory; it comes back on its next report.
Dashboard
GET / is a single self-contained page with no external assets, so it works air-gapped. It refreshes every 30 seconds.
- Overview: hosts by status, reclaimable by tier, freed in the last 24 h / 7 d / all-time, hosts under disk pressure, quarantine held; freed per day for 14 days; reclaimable by category; the hosts with the most to reclaim, by tier; the largest findings across the fleet; hosts that need attention; recent runs.
- Hosts: searchable, filterable, sortable inventory with tags, status reasons, tier bars, disk meter, a reclaimable sparkline and last-report age.
- Host: everything in the last report, reclaimable over time, freed per run, the largest findings with tier and idle time, why items were skipped, audit chain head, an operator note, and removal.
- Policies: hub-managed policies and which hosts use each policy name.
- Activity: the hub's own event log (reports, rejections, policy pushes, removals).
API
All JSON. Agent routes accept the agent or admin token; everything else needs the admin token.
| Route | Who | What |
|---|---|---|
POST /api/v1/report | agent | Ingest a HubHostReport; 402 when a new host exceeds the allowance |
GET /api/v1/policies, GET /api/v1/policies/{name} | agent | List / fetch a managed policy |
PUT /api/v1/policies/{name} (body: policy JSON), DELETE … | admin | Store (validated) / remove |
GET /api/v1/summary | admin | The HubSummary the overview is drawn from |
GET /api/v1/hosts | admin | Inventory rows with status (history trimmed, top items omitted) |
GET /api/v1/hosts/{name}, DELETE …, PUT …/note (text body) | admin | One host in full / remove / note |
GET /api/v1/events?n=50 | admin | Recent hub events |
GET /api/v1/license | admin | Edition, org, allowance, hosts over it |
GET /api/v1/info | public | Name, version, whether auth is required |
GET /metrics | admin | Fleet-wide Prometheus text |
GET /healthz | public | ok |
Shapes (HubHostReport, HubSummary, HubHostRecord, HubEvent) are versioned by schemaVersion and additive-only, like ScanReport.
curl -s -H "Authorization: Bearer $ADMIN" http://hub.internal:9478/api/v1/hosts | jq '.[] | {host, status, reclaimableBytes}'
Metrics
Scrape /metrics with the admin token (authorization: credentials in the Prometheus job). One scrape covers the whole fleet.
Fleet series
roomy_hub_hosts{status}, roomy_hub_license_hosts_max, roomy_hub_reclaimable_bytes{tier}, roomy_hub_reclaimable_bytes_by_category{category}, roomy_hub_disposed_bytes_total, roomy_hub_disposed_bytes_last_24h, roomy_hub_dispose_failures_total, roomy_hub_quarantine_bytes, roomy_hub_volume_available_bytes, roomy_hub_volume_total_bytes, roomy_hub_hosts_low_disk.
Per-host series
Each carries host: roomy_hub_host_info{platform,version,policy,edition,status}, roomy_hub_host_reclaimable_bytes{tier}, roomy_hub_host_disposed_bytes_total, roomy_hub_host_last_run_disposed_bytes, roomy_hub_host_last_run_failures, roomy_hub_host_last_run_success, roomy_hub_host_dry_run, roomy_hub_host_last_report_timestamp_seconds, roomy_hub_host_quarantine_bytes, roomy_hub_host_volume_available_bytes, roomy_hub_host_volume_total_bytes, roomy_hub_host_audit_seq.
Alerts worth having
roomy_hub_hosts{status="stale"} > 0 # a host stopped reporting
roomy_hub_host_volume_available_bytes / roomy_hub_host_volume_total_bytes < 0.1 # disk pressure despite cleanup
increase(roomy_hub_dispose_failures_total[1d]) > 0 # something could not be removed
delta(roomy_hub_host_audit_seq[1h]) < 0 # a host's audit log was reset
Data directory
/var/lib/roomy-hub/
hosts/<host>.json latest report + up to 1000 history points + note
policies/<name>.json managed policies, exactly as pushed
events.jsonl hub event log
Plain files: back up with rsync, inspect with jq, delete a host file to forget it. The hub reloads everything on start. Request bodies are capped at 8 MB.
Editions
Without a license the hub tracks up to 5 hosts, enough to try it with real machines. A fleet license sets the allowance to its maxHosts and shows the organisation in the header. The hub reads its license from --license, $ROOMY_LICENSE, /etc/roomy/license.key or ~/.roomy/license.key, and verifies it offline.
When the allowance is reached, a report from a host the hub has not seen is refused with HTTP 402 and logged as an event; known hosts keep reporting. An expired license therefore degrades to an "attention" marker, not an outage. Licensing details are in the Fleet reference; pricing is on the Fleet page.
CLI commands
All operator commands take --url and --token, or read $ROOMY_HUB_URL, $ROOMY_HUB_TOKEN and ~/.roomy/hub-admin.token. Add --json for machine output.
roomy hub status # fleet totals, hosts by status, license
roomy hub hosts [--status attention] # inventory table
roomy hub host agent-eu-03 [--note "Decommission next week"]
roomy hub remove agent-eu-03
roomy hub events -n 50
roomy hub policy list | get NAME | push NAME FILE | remove NAME
roomy hub demo [--hosts 24] [--seed 7] [--org "Example"] [--reset] # synthetic fleet in ~/.roomy/hub-demo
roomy hub token # print a random token
Home · Mac app · CLI · Fleet · Fleet & Hub overview · Privacy · EULA · Source on GitHub
