rigscope 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -0
- package/.next/app-path-routes-manifest.json +13 -0
- package/.next/build-manifest.json +20 -0
- package/.next/export-marker.json +6 -0
- package/.next/images-manifest.json +68 -0
- package/.next/next-minimal-server.js.nft.json +1 -0
- package/.next/next-server.js.nft.json +1 -0
- package/.next/package.json +1 -0
- package/.next/prerender-manifest.json +297 -0
- package/.next/react-loadable-manifest.json +1 -0
- package/.next/required-server-files.json +369 -0
- package/.next/routes-manifest.json +114 -0
- package/.next/server/app/_global-error/page.js +31 -0
- package/.next/server/app/_global-error/page.js.nft.json +1 -0
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -0
- package/.next/server/app/_global-error.html +1 -0
- package/.next/server/app/_global-error.meta +35 -0
- package/.next/server/app/_global-error.rsc +18 -0
- package/.next/server/app/_global-error.segments/_full.segment.rsc +18 -0
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +24 -0
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +3 -0
- package/.next/server/app/_not-found/page.js +1 -0
- package/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- package/.next/server/app/_not-found.html +1 -0
- package/.next/server/app/_not-found.meta +35 -0
- package/.next/server/app/_not-found.rsc +24 -0
- package/.next/server/app/_not-found.segments/_full.segment.rsc +24 -0
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +30 -0
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +4 -0
- package/.next/server/app/administration/page.js +1 -0
- package/.next/server/app/administration/page.js.nft.json +1 -0
- package/.next/server/app/administration/page_client-reference-manifest.js +1 -0
- package/.next/server/app/administration.html +1 -0
- package/.next/server/app/administration.meta +34 -0
- package/.next/server/app/administration.rsc +29 -0
- package/.next/server/app/administration.segments/_full.segment.rsc +29 -0
- package/.next/server/app/administration.segments/_tree.segment.rsc +4 -0
- package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +35 -0
- package/.next/server/app/alerts/page.js +1 -0
- package/.next/server/app/alerts/page.js.nft.json +1 -0
- package/.next/server/app/alerts/page_client-reference-manifest.js +1 -0
- package/.next/server/app/alerts.html +1 -0
- package/.next/server/app/alerts.meta +34 -0
- package/.next/server/app/alerts.rsc +28 -0
- package/.next/server/app/alerts.segments/_full.segment.rsc +28 -0
- package/.next/server/app/alerts.segments/_tree.segment.rsc +4 -0
- package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +34 -0
- package/.next/server/app/connections/page.js +1 -0
- package/.next/server/app/connections/page.js.nft.json +1 -0
- package/.next/server/app/connections/page_client-reference-manifest.js +1 -0
- package/.next/server/app/connections.html +1 -0
- package/.next/server/app/connections.meta +34 -0
- package/.next/server/app/connections.rsc +29 -0
- package/.next/server/app/connections.segments/_full.segment.rsc +29 -0
- package/.next/server/app/connections.segments/_tree.segment.rsc +4 -0
- package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +35 -0
- package/.next/server/app/history/page.js +2 -0
- package/.next/server/app/history/page.js.nft.json +1 -0
- package/.next/server/app/history/page_client-reference-manifest.js +1 -0
- package/.next/server/app/history.html +1 -0
- package/.next/server/app/history.meta +34 -0
- package/.next/server/app/history.rsc +33 -0
- package/.next/server/app/history.segments/_full.segment.rsc +33 -0
- package/.next/server/app/history.segments/_tree.segment.rsc +4 -0
- package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +37 -0
- package/.next/server/app/icon.svg/route.js +1 -0
- package/.next/server/app/icon.svg/route.js.nft.json +1 -0
- package/.next/server/app/icon.svg.body +1 -0
- package/.next/server/app/icon.svg.meta +1 -0
- package/.next/server/app/index.html +1 -0
- package/.next/server/app/index.meta +29 -0
- package/.next/server/app/index.rsc +26 -0
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +31 -0
- package/.next/server/app/index.segments/_full.segment.rsc +26 -0
- package/.next/server/app/index.segments/_tree.segment.rsc +3 -0
- package/.next/server/app/login/page.js +1 -0
- package/.next/server/app/login/page.js.nft.json +1 -0
- package/.next/server/app/login/page_client-reference-manifest.js +1 -0
- package/.next/server/app/login.html +1 -0
- package/.next/server/app/login.meta +34 -0
- package/.next/server/app/login.rsc +26 -0
- package/.next/server/app/login.segments/_full.segment.rsc +26 -0
- package/.next/server/app/login.segments/_tree.segment.rsc +4 -0
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +32 -0
- package/.next/server/app/machines/[machineId]/page.js +1 -0
- package/.next/server/app/machines/[machineId]/page.js.nft.json +1 -0
- package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -0
- package/.next/server/app/page.js +1 -0
- package/.next/server/app/page.js.nft.json +1 -0
- package/.next/server/app/page_client-reference-manifest.js +1 -0
- package/.next/server/app/processes/page.js +2 -0
- package/.next/server/app/processes/page.js.nft.json +1 -0
- package/.next/server/app/processes/page_client-reference-manifest.js +1 -0
- package/.next/server/app/processes.html +1 -0
- package/.next/server/app/processes.meta +34 -0
- package/.next/server/app/processes.rsc +30 -0
- package/.next/server/app/processes.segments/_full.segment.rsc +30 -0
- package/.next/server/app/processes.segments/_tree.segment.rsc +4 -0
- package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +36 -0
- package/.next/server/app-paths-manifest.json +13 -0
- package/.next/server/chunks/161.js +1 -0
- package/.next/server/chunks/168.js +1 -0
- package/.next/server/chunks/266.js +68 -0
- package/.next/server/chunks/430.js +116 -0
- package/.next/server/chunks/721.js +1 -0
- package/.next/server/chunks/751.js +1 -0
- package/.next/server/chunks/877.js +1 -0
- package/.next/server/chunks/894.js +2 -0
- package/.next/server/functions-config-manifest.json +4 -0
- package/.next/server/interception-route-rewrite-manifest.js +1 -0
- package/.next/server/middleware-build-manifest.js +1 -0
- package/.next/server/middleware-manifest.json +6 -0
- package/.next/server/middleware-react-loadable-manifest.js +1 -0
- package/.next/server/next-font-manifest.js +1 -0
- package/.next/server/next-font-manifest.json +1 -0
- package/.next/server/pages/404.html +1 -0
- package/.next/server/pages/500.html +1 -0
- package/.next/server/pages-manifest.json +4 -0
- package/.next/server/prefetch-hints.json +123 -0
- package/.next/server/preview-props.json +5 -0
- package/.next/server/server-reference-manifest.js +1 -0
- package/.next/server/server-reference-manifest.json +1 -0
- package/.next/server/webpack-runtime.js +1 -0
- package/.next/static/DZKWF9ur7H5wI0c7RAhdq/_buildManifest.js +1 -0
- package/.next/static/DZKWF9ur7H5wI0c7RAhdq/_ssgManifest.js +1 -0
- package/.next/static/chunks/103-c47318242b24e9ac.js +1 -0
- package/.next/static/chunks/157-0d36ebf66af048ac.js +1 -0
- package/.next/static/chunks/227-417939c480f50747.js +1 -0
- package/.next/static/chunks/408-18819c6490a5f8d3.js +1 -0
- package/.next/static/chunks/4bd1b696-c7408084347438ae.js +1 -0
- package/.next/static/chunks/794-ceeb732a1a81bc27.js +52 -0
- package/.next/static/chunks/939-2600d7c8e583d5ef.js +1 -0
- package/.next/static/chunks/app/_global-error/page-aba28f88d5781771.js +1 -0
- package/.next/static/chunks/app/_not-found/page-a2a510233e4836e2.js +1 -0
- package/.next/static/chunks/app/administration/page-4b16b19bb6a39c36.js +1 -0
- package/.next/static/chunks/app/alerts/page-fe0e877778c60af8.js +1 -0
- package/.next/static/chunks/app/connections/page-99cd54d6929b3f1f.js +1 -0
- package/.next/static/chunks/app/history/page-3fa5846eca26b7f7.js +3 -0
- package/.next/static/chunks/app/layout-4feff677c4071745.js +1 -0
- package/.next/static/chunks/app/login/page-c57e12dbe5146737.js +1 -0
- package/.next/static/chunks/app/machines/[machineId]/page-a5aa1d8676272f38.js +1 -0
- package/.next/static/chunks/app/page-1ae9fc94c897785b.js +1 -0
- package/.next/static/chunks/app/processes/page-e471ed23a1d5632f.js +3 -0
- package/.next/static/chunks/framework-ea4f97ca039bd777.js +1 -0
- package/.next/static/chunks/main-a458d32948147b84.js +5 -0
- package/.next/static/chunks/main-app-f92328c8950d2e53.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-aba28f88d5781771.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-aba28f88d5781771.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-43cbaa3aa1576eb4.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-aba28f88d5781771.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-aba28f88d5781771.js +1 -0
- package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/.next/static/chunks/webpack-9ca43f4ce1bb8938.js +1 -0
- package/.next/static/css/e41ccbccc3afaae5.css +1 -0
- package/ARCHITECTURE.md +279 -0
- package/CHANGELOG.md +25 -0
- package/DESIGN.md +299 -0
- package/LICENSE +21 -0
- package/README.md +157 -2
- package/SECURITY.md +12 -0
- package/THIRD_PARTY_NOTICES.md +8847 -0
- package/bin/rigscope.cjs +6 -0
- package/dist/cli.cjs +9075 -0
- package/dist/collector.cjs +5671 -0
- package/dist/hub.cjs +8355 -0
- package/dist/ops.cjs +4972 -0
- package/dist/system.cjs +4333 -0
- package/docs/bottlenecks.md +62 -0
- package/docs/development.md +46 -0
- package/docs/exports.md +39 -0
- package/docs/installation.md +477 -0
- package/docs/operations.md +515 -0
- package/docs/releasing.md +152 -0
- package/docs/scale-acceptance.json +412 -0
- package/docs/scale-validation.json +1898 -0
- package/docs/scale-validation.md +263 -0
- package/next.config.js +38 -0
- package/package.json +109 -4
- package/packaging/README.md +5 -0
- package/packaging/collector.env.example +24 -0
- package/packaging/hub.env.example +31 -0
- package/packaging/system/90-rigscope-rapl.rules +7 -0
- package/packaging/system/README.md +5 -0
- package/packaging/system/rigscope-collector.service.example +53 -0
- package/packaging/system/rigscope-hub.service.example +50 -0
- package/packaging/system/rigscope-rapl.sysusers +5 -0
- package/packaging/system/rigscope.sysusers +7 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Sustained scale validation
|
|
2
|
+
|
|
3
|
+
Status: implementation and local sustained measurement, 2026-11-04.
|
|
4
|
+
|
|
5
|
+
This document records the isolated sustained workload added after the earlier
|
|
6
|
+
`ops measure` burst. It is deliberately **not** a capacity guarantee. The
|
|
7
|
+
numbers below describe one host, one payload shape, one database and the
|
|
8
|
+
concurrency listed for each case. They do not prove that a larger fleet,
|
|
9
|
+
different hardware, slower storage or a different browser workload will behave
|
|
10
|
+
the same way.
|
|
11
|
+
|
|
12
|
+
## What was missing
|
|
13
|
+
|
|
14
|
+
The earlier `ops measure` command was a bounded synthetic burst capped at 50
|
|
15
|
+
synthetic machines; it queues a fixed number of records and by default runs
|
|
16
|
+
History query iterations sequentially unless --history-concurrency is supplied. It never opens a service socket, so it
|
|
17
|
+
cannot show concurrent browser requests, SSE delivery, slow consumers,
|
|
18
|
+
collector reconnects or replay. Those paths are now measured by a standalone
|
|
19
|
+
harness instead of being inferred from the burst runner.
|
|
20
|
+
|
|
21
|
+
`src/operations/measure.ts` was also extended:
|
|
22
|
+
|
|
23
|
+
- the synthetic machine bound is now 1,000 instead of 50,
|
|
24
|
+
- `historyConcurrency` initiates overlapping History request rounds and reports
|
|
25
|
+
their wall time, p95 and query count,
|
|
26
|
+
- the existing CLI arguments and report fields remain compatible.
|
|
27
|
+
|
|
28
|
+
## Harness and isolation
|
|
29
|
+
|
|
30
|
+
The harness is `scripts/scale-harness.ts`. It is a standalone source-checkout
|
|
31
|
+
tool, separate from the installed operational CLI. With development dependencies
|
|
32
|
+
installed, run it with `tsx`:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npx tsx scripts/scale-harness.ts --suite \
|
|
36
|
+
--json-out docs/scale-validation.json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The parent process:
|
|
40
|
+
|
|
41
|
+
1. creates a fresh `0700` temporary directory and a `0600` hub configuration,
|
|
42
|
+
2. writes a throwaway SQLite database path,
|
|
43
|
+
3. spawns the same script in `hub` child mode on an ephemeral `127.0.0.1`
|
|
44
|
+
port,
|
|
45
|
+
4. logs in real browser sessions through `/api/auth/login` (including the
|
|
46
|
+
normal scrypt work and login token bucket),
|
|
47
|
+
5. connects real collector WebSockets through `/api/collector/connect`,
|
|
48
|
+
6. reads authenticated `/api/live` SSE streams and `/api/history` responses,
|
|
49
|
+
7. stops the child, closes the database and removes the temporary directory
|
|
50
|
+
unless `--keep` is set.
|
|
51
|
+
|
|
52
|
+
The child is composed from the production modules: `HubState`,
|
|
53
|
+
`TelemetryStore`, `SampleIngestion`, `CollectorGateway`, `LiveFeed`,
|
|
54
|
+
`BrowserHttpAuth`, `BrowserSessionStore` and `MonitorHttp`. The database uses
|
|
55
|
+
WAL, `synchronous=FULL` and the real commit-before-ack path. The only synthetic
|
|
56
|
+
parts are the metric values and the cadence driver. No live hub, service
|
|
57
|
+
socket, production database or Tailscale endpoint is contacted.
|
|
58
|
+
|
|
59
|
+
The harness exposes a private `GET /__scale/stats` control endpoint on the
|
|
60
|
+
isolated child. It returns event-loop delay percentiles, event-loop
|
|
61
|
+
utilization, process CPU/RSS, database/WAL bytes, telemetry store counters,
|
|
62
|
+
connected collector count and SSE client count. The endpoint is not part of
|
|
63
|
+
the hub and is never reachable from a deployment.
|
|
64
|
+
|
|
65
|
+
## Workload
|
|
66
|
+
|
|
67
|
+
Each case uses representative metrics: eight CPU cores, two GPUs, two
|
|
68
|
+
filesystems, two disk devices, two network interfaces, plus the optional
|
|
69
|
+
bottleneck fields (temperature, package power, I/O wait, pressure, inode use,
|
|
70
|
+
disk latency and utilization). One record carries roughly 50 stored series
|
|
71
|
+
points before rollups.
|
|
72
|
+
|
|
73
|
+
| Case | Machines | Measured phase | Live cadence | History viewers | SSE viewers | Special workload |
|
|
74
|
+
| --- | ---: | ---: | ---: | ---: | ---: | --- |
|
|
75
|
+
| `small-5m` | 5 | 60 s | 1 s | 4 | 2 | small-fleet baseline |
|
|
76
|
+
| `fleet-75m` | 75 | 60 s | 1 s | 8 | 4 | beyond the old 50-machine cap |
|
|
77
|
+
| `fleet-75m-churn` | 75 | 60 s | 1 s | 8 | 4 | 15 collectors reconnect every 15 s with 20 replay records; SSE viewers reconnect with `Last-Event-ID` |
|
|
78
|
+
| `fleet-75m-slow-sse` | 75 | 60 s | 1 s | 8 | 6 | two SSE viewers pause reading for 15 s, measuring stale delivery and any buffer drop/reconnect |
|
|
79
|
+
| `fleet-75m-history` | 75 | 60 s | 1 s | 8 | 2 | broad History metric set every 300 ms per viewer |
|
|
80
|
+
|
|
81
|
+
Before each measured phase the harness connects all collectors and waits
|
|
82
|
+
1.5 seconds. The measured phase starts after that warmup. Records are counted
|
|
83
|
+
from collector startup, so the ingestion counters include the warmup and bounded drain tail; the
|
|
84
|
+
event-loop, CPU, RSS and database-growth windows are reset at the measured
|
|
85
|
+
phase boundary.
|
|
86
|
+
|
|
87
|
+
### Bounds
|
|
88
|
+
|
|
89
|
+
- machines: 1–1,000,
|
|
90
|
+
- measured phase: up to 1 hour,
|
|
91
|
+
- sample cadence: at least 100 ms (the protocol rate limit),
|
|
92
|
+
- records per live batch: 1–32,
|
|
93
|
+
- viewers: 1–50 authenticated sessions,
|
|
94
|
+
- SSE clients: 1–64,
|
|
95
|
+
- replay: at most 128 records per reconnect,
|
|
96
|
+
- estimated live plus replay records: at most 500,000 per case,
|
|
97
|
+
- database: 512 MiB main+WAL by default (`--database-budget-mb 0` disables the
|
|
98
|
+
cap for inspection only),
|
|
99
|
+
- temporary database/config directory: removed after the case unless
|
|
100
|
+
`--keep` is used.
|
|
101
|
+
|
|
102
|
+
## Host
|
|
103
|
+
|
|
104
|
+
The measurement host was:
|
|
105
|
+
|
|
106
|
+
- AMD EPYC 7402P 24-Core Processor, 48 logical CPUs,
|
|
107
|
+
- 121 GiB usable memory (130,351,214,592 bytes reported by the OS),
|
|
108
|
+
- Linux 7.3.0-9-generic, Ubuntu 26.10, x86_64,
|
|
109
|
+
- Node.js 26.13.0,
|
|
110
|
+
- local NVMe root filesystem (the harness database lived under `/tmp`),
|
|
111
|
+
- load average 0.92 / 0.76 / 0.84 around the run; unrelated user services were
|
|
112
|
+
active, so this is not a quiet benchmark host.
|
|
113
|
+
|
|
114
|
+
`monitorEventLoopDelay` used a 10 ms resolution. The reported p50 therefore
|
|
115
|
+
floors near 10 ms even when the process is mostly idle; p95/p99/max are the
|
|
116
|
+
meaningful tail values.
|
|
117
|
+
|
|
118
|
+
## Results
|
|
119
|
+
|
|
120
|
+
All five cases completed with zero rejected records, zero `retry` acknowledgments,
|
|
121
|
+
zero unacknowledged live records, zero write failures and no disk-budget
|
|
122
|
+
pressure. The event-loop window excludes the 1.5 s collector warmup. "History /
|
|
123
|
+
SSE" is authenticated History sessions / SSE clients. "Sample→commit" in the original report is capture-to-durable-ack latency: an
|
|
124
|
+
upper bound on commit lag that also includes the acknowledgment return path.
|
|
125
|
+
It is measured on the same host clock, not across uncalibrated remote clocks.
|
|
126
|
+
"Normal SSE lag" is the
|
|
127
|
+
age of the newest captured sample when an `update`/`snapshot` arrives at a
|
|
128
|
+
normal viewer; it includes the hub's 500 ms coalescing window. Hub CPU is the
|
|
129
|
+
hub child's process CPU as a percentage of one core, not the whole host.
|
|
130
|
+
|
|
131
|
+
| Case | Machines | History / SSE | Records accepted | Ack p95 | Sample→commit p95 | History p95 / max | Normal SSE lag p95 | Event-loop p99 / max | Hub CPU | RSS peak | DB+WAL growth |
|
|
132
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
133
|
+
| `small-5m` | 5 | 4 / 2 | 317 / 317 | 26.7 ms | 27 ms | 12.9 / 20.0 ms | 526 ms | 12.5 / 18.5 ms | 2.6% | 135.9 MiB | 4.63 MiB |
|
|
134
|
+
| `fleet-75m` | 75 | 8 / 4 | 4,789 / 4,789 | 55.6 ms | 56 ms | 20.9 / 35.2 ms | 439 ms | 30.4 / 44.6 ms | 12.4% | 188.7 MiB | 12.08 MiB |
|
|
135
|
+
| `fleet-75m-churn` | 75 | 8 / 4 | 4,845 / 4,845 | 52.2 ms | 52 ms | 23.4 / 35.0 ms | 438 ms | 29.0 / 42.4 ms | 14.2% | 203.2 MiB | 14.22 MiB |
|
|
136
|
+
| `fleet-75m-slow-sse` | 75 | 8 / 6 | 4,791 / 4,791 | 64.4 ms | 64 ms | 23.0 / 36.6 ms | 502 ms | 38.5 / 76.2 ms | 12.0% | 190.2 MiB | 12.95 MiB |
|
|
137
|
+
| `fleet-75m-history` | 75 | 8 / 2 | 4,785 / 4,785 | 70.7 ms | 71 ms | 50.3 / 60.5 ms | 453 ms | 48.0 / 72.7 ms | 31.2% | 229.7 MiB | 12.98 MiB |
|
|
138
|
+
|
|
139
|
+
Additional observations:
|
|
140
|
+
|
|
141
|
+
- **History authorization budget held.** The broad case ran 1,608
|
|
142
|
+
authenticated queries (8 sessions polling every 300 ms) with 1,608 `200`
|
|
143
|
+
responses, zero `429` responses and zero errors. Each response returned
|
|
144
|
+
about 25 series and 800 points; the 25,000-row/100 ms budget was not
|
|
145
|
+
truncated.
|
|
146
|
+
- **Collector reconnect/replay recovered.** The churn case performed 45
|
|
147
|
+
collector reconnects and replayed 900 historical records; all 900 were
|
|
148
|
+
accepted. First live acknowledgment after reconnect was 167.9 ms p95
|
|
149
|
+
(175.8 ms max). The durable queue finished empty.
|
|
150
|
+
- **SSE reconnect/reset recovered.** The churn case reconnected 48 SSE streams
|
|
151
|
+
and received 48 `reset` snapshots; all resets followed a reconnect. First
|
|
152
|
+
event after reconnect was 10.1 ms p95.
|
|
153
|
+
- **Slow consumers stayed connected but stale.** The two paused viewers
|
|
154
|
+
received no new bytes for 15 s and then read buffered snapshots. Their
|
|
155
|
+
delivery lag was 11.6 s p95 while normal viewers stayed at 502 ms p95. On
|
|
156
|
+
this loopback path no slow-viewer drop or reconnect was observed, so the
|
|
157
|
+
hub's 1 MiB writable-buffer guard did not fire; kernel socket buffering
|
|
158
|
+
likely absorbed the pause. The deliberate disconnect/reconnect path is
|
|
159
|
+
covered by the churn case instead.
|
|
160
|
+
- **Database/WAL stayed bounded.** The final main+WAL size was 14.8 MiB for
|
|
161
|
+
the 75-machine baseline, 16.9 MiB with churn, and 15.7 MiB with broad
|
|
162
|
+
History. The 512 MiB measurement budget was never approached, no checkpoint
|
|
163
|
+
pressure appeared, and the store queue was empty at the end of every case.
|
|
164
|
+
- **Small fleet stayed cheap.** Five machines, four History viewers and two SSE
|
|
165
|
+
viewers used 2.6% of one core and stayed at 12.5 ms event-loop p99.
|
|
166
|
+
|
|
167
|
+
The full machine-readable report, including per-case store counters, database
|
|
168
|
+
sizes, SSE byte counts, tier counts and host load averages, is
|
|
169
|
+
[scale-validation.json](scale-validation.json). The generated JSON is the raw
|
|
170
|
+
artifact; this page is the summary.
|
|
171
|
+
|
|
172
|
+
## Interpretation
|
|
173
|
+
|
|
174
|
+
These results support the following limited statements on this host:
|
|
175
|
+
|
|
176
|
+
- A 75-machine, 1-second-cadence synthetic fleet can be ingested with
|
|
177
|
+
commit-before-ack p95 under 100 ms while eight authenticated History viewers
|
|
178
|
+
and four full-fleet SSE viewers run concurrently.
|
|
179
|
+
- Collector reconnect plus 20-record replay per affected machine recovered
|
|
180
|
+
within a few hundred milliseconds in this run, without rejected or unacked
|
|
181
|
+
records.
|
|
182
|
+
- SSE reconnect with `Last-Event-ID` produced a fresh `reset` snapshot within
|
|
183
|
+
about 10 ms on loopback, and the next update was fresh again.
|
|
184
|
+
- Broad History polling near the per-session token budget raised the
|
|
185
|
+
event-loop tail to about 48 ms p99 and used about 31% of one core, but
|
|
186
|
+
produced no 429s and no truncated responses at this data size.
|
|
187
|
+
|
|
188
|
+
They do **not** support claims that:
|
|
189
|
+
|
|
190
|
+
- 75 machines is a capacity limit or a safe upper bound,
|
|
191
|
+
- a larger fleet, more viewers, slower storage or a different payload will meet
|
|
192
|
+
the same latencies,
|
|
193
|
+
- the hub is multi-core, clustered or horizontally scalable,
|
|
194
|
+
- slow consumers are always disconnected before they fall behind,
|
|
195
|
+
- the default 100 concurrent collector socket budget should be raised without
|
|
196
|
+
a deployment-specific measurement.
|
|
197
|
+
|
|
198
|
+
The 75-machine cases used the default 100-connection budget. A deployment that
|
|
199
|
+
raises `HUB_MAX_COLLECTOR_CONNECTIONS` must re-run the harness with its own
|
|
200
|
+
payloads, storage and viewer counts.
|
|
201
|
+
|
|
202
|
+
## Reproduction
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
# full five-case suite (about five minutes)
|
|
206
|
+
npx tsx scripts/scale-harness.ts --suite \
|
|
207
|
+
--json-out docs/scale-validation.json
|
|
208
|
+
|
|
209
|
+
# one case with overrides
|
|
210
|
+
npx tsx scripts/scale-harness.ts --case fleet-75m \
|
|
211
|
+
--machines 75 --viewers 8 --sse-clients 4 --duration-ms 60000 \
|
|
212
|
+
--json-out /tmp/rigscope-scale-75m.json
|
|
213
|
+
|
|
214
|
+
# retain the throwaway database and configuration for inspection
|
|
215
|
+
npx tsx scripts/scale-harness.ts --case small-5m --keep
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The short `tests/scale-harness.test.ts` smoke runs the same child with two
|
|
219
|
+
machines, collector reconnect/replay and an SSE reset. It is not a capacity
|
|
220
|
+
test; it proves the harness paths work in CI.
|
|
221
|
+
|
|
222
|
+
## Limits
|
|
223
|
+
|
|
224
|
+
- The hub child and all load generators share one host. The hub event loop uses
|
|
225
|
+
one thread; the load processes can run on other cores. This is
|
|
226
|
+
not a distributed-load test and the generator competes with the hub.
|
|
227
|
+
- History queries are synchronous SQLite work on the hub event loop.
|
|
228
|
+
"Concurrent viewers" means overlapping authenticated HTTP requests, not
|
|
229
|
+
parallel SQLite readers.
|
|
230
|
+
- The payload is representative but not captured from a real fleet. Different
|
|
231
|
+
process counts, GPUs, filesystems or optional fields change the stored row
|
|
232
|
+
count.
|
|
233
|
+
- The host may run unrelated services. Load averages and CPU are recorded in
|
|
234
|
+
the JSON artifact, but they are not a controlled benchmark environment.
|
|
235
|
+
- Passing these cases does not remove the concurrent collector socket budget
|
|
236
|
+
(default 100, configurable with `HUB_MAX_COLLECTOR_CONNECTIONS`) or the
|
|
237
|
+
history row/elapsed/frame/storage budgets.
|
|
238
|
+
- Authentication and machine scopes use the production modules with loopback
|
|
239
|
+
origin rules. This harness does not exercise the deployed HTTPS/private-socket
|
|
240
|
+
boundary; its separate runtime and actual-path checks remain distinct.
|
|
241
|
+
- A result is evidence for this host and workload only. It is not a claim of
|
|
242
|
+
unlimited machine, viewer or History capacity.
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
## Root integration acceptance
|
|
246
|
+
|
|
247
|
+
After integration, a separate 75-machine, eight-second churn run used eight
|
|
248
|
+
History sessions and four SSE consumers. Its authorization canary required
|
|
249
|
+
anonymous SSE 401, an allowed scoped History query 200, a forbidden machine query
|
|
250
|
+
403, and correctly filtered initial and reconnect/reset SSE snapshots. All passed.
|
|
251
|
+
The run accepted 930 live records plus 600 replay records, recorded 30 collector
|
|
252
|
+
reconnects, finished with zero unacked live records and zero write failures.
|
|
253
|
+
`scale-acceptance.json` records this shorter integration check. It is not another
|
|
254
|
+
60-second capacity measurement; the five sustained results above remain the
|
|
255
|
+
worker's original raw artifact from before the authorization canary was added.
|
|
256
|
+
|
|
257
|
+
The original `sampleToCommitLagMs` field names are retained for compatibility.
|
|
258
|
+
Those numbers are capture-to-durable-ack upper bounds on commit latency, including
|
|
259
|
+
the return path; they are not a server-side SQL commit timestamp. Queue/backpressure
|
|
260
|
+
counters and end-of-run queue emptiness are recorded, but the original report does
|
|
261
|
+
not sample an exact queue high-water mark. Slow viewers did not reach the server's
|
|
262
|
+
buffer-disconnect threshold in these runs, so that threshold is not a measured
|
|
263
|
+
fleet-capacity limit.
|
package/next.config.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { PHASE_DEVELOPMENT_SERVER } = require("next/constants");
|
|
2
|
+
const { availableParallelism } = require("node:os");
|
|
3
|
+
|
|
4
|
+
const workers = Number(process.env.RIGSCOPE_BUILD_WORKERS ?? Math.min(4, availableParallelism()));
|
|
5
|
+
if (!Number.isInteger(workers) || workers < 1 || workers > 64) {
|
|
6
|
+
throw new Error(
|
|
7
|
+
`RIGSCOPE_BUILD_WORKERS must be an integer from 1 to 64 (received ${JSON.stringify(process.env.RIGSCOPE_BUILD_WORKERS)}). ` +
|
|
8
|
+
"Set it to the number of build workers, or leave it unset to auto-select up to four.",
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** @param {string} phase @returns {import("next").NextConfig} */
|
|
13
|
+
const nextConfig = (phase) => ({
|
|
14
|
+
// Keep dev writes separate from the packaged hub's production artifact.
|
|
15
|
+
// Use a small static-generation worker pool for this six-page scaffold.
|
|
16
|
+
distDir: phase === PHASE_DEVELOPMENT_SERVER ? ".next-dev" : ".next",
|
|
17
|
+
experimental: { cpus: workers },
|
|
18
|
+
// Remote development goes through an outer TLS terminator and a Unix
|
|
19
|
+
// socket, so the browser's Origin hostname is the configured public origin,
|
|
20
|
+
// not localhost. Keep an explicit hostname allowlist for /_next assets and
|
|
21
|
+
// HMR (Next also adds the createNextApp hostname). Configuring this list
|
|
22
|
+
// makes Next's dev guard block unlisted origins instead of only warning.
|
|
23
|
+
// The hub validates the full exact origin before delegation.
|
|
24
|
+
allowedDevOrigins: phase === PHASE_DEVELOPMENT_SERVER ? publicDevOrigin() : undefined,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function publicDevOrigin() {
|
|
28
|
+
const value = process.env.HUB_PUBLIC_ORIGIN;
|
|
29
|
+
if (value === undefined || value === "") return undefined;
|
|
30
|
+
try {
|
|
31
|
+
const origin = new URL(value);
|
|
32
|
+
return origin.protocol === "https:" ? [origin.hostname] : undefined;
|
|
33
|
+
} catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = nextConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,111 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rigscope",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"rigscopeConfigVersion": 2,
|
|
5
|
+
"private": false,
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=26.0.0"
|
|
8
|
+
},
|
|
9
|
+
"os": [
|
|
10
|
+
"linux"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "NODE_ENV=development tsx src/hub/main.ts",
|
|
14
|
+
"build": "npm run build:ui && npm run build:runtime",
|
|
15
|
+
"build:runtime": "node scripts/build-runtime.cjs",
|
|
16
|
+
"start": "node dist/hub.cjs",
|
|
17
|
+
"collector:mock": "COLLECTOR_MODE=mock tsx collector/main.ts",
|
|
18
|
+
"collector:enroll": "tsx collector/main.ts --enroll",
|
|
19
|
+
"collector:start": "node dist/collector.cjs",
|
|
20
|
+
"ops": "node dist/ops.cjs",
|
|
21
|
+
"package:release": "npm run build && node scripts/package-release.cjs",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "node scripts/test.cjs",
|
|
24
|
+
"test:runtime": "npm run build:runtime && node scripts/test-runtime.cjs",
|
|
25
|
+
"test:dev": "npm run build:runtime && node scripts/test.cjs --suite development",
|
|
26
|
+
"validate:https": "node scripts/validate-https.cjs",
|
|
27
|
+
"build:ui": "next build --webpack",
|
|
28
|
+
"rigscope": "node bin/rigscope.cjs",
|
|
29
|
+
"verify:build": "node scripts/verify-build.cjs",
|
|
30
|
+
"verify:package": "node scripts/verify-package.cjs",
|
|
31
|
+
"verify:installed": "node scripts/verify-installed-package.cjs",
|
|
32
|
+
"prepack": "node scripts/prepack.cjs",
|
|
33
|
+
"package:npm": "npm pack",
|
|
34
|
+
"notices": "node scripts/third-party-notices.cjs"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"lucide-react": "^0.475.0",
|
|
38
|
+
"next": "^16.4.5",
|
|
39
|
+
"next-themes": "^0.4.6",
|
|
40
|
+
"react": "^19.1.0",
|
|
41
|
+
"react-dom": "^19.1.0",
|
|
42
|
+
"ws": "^8.22.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@tailwindcss/postcss": "^4.1.0",
|
|
46
|
+
"@types/node": "^22.13.0",
|
|
47
|
+
"@types/react": "^19.1.0",
|
|
48
|
+
"@types/react-dom": "^19.1.0",
|
|
49
|
+
"@types/ws": "^8.18.2",
|
|
50
|
+
"esbuild": "^0.28.2",
|
|
51
|
+
"postcss": "^8.5.23",
|
|
52
|
+
"tailwindcss": "^4.1.0",
|
|
53
|
+
"tsx": "^4.23.15",
|
|
54
|
+
"typescript": "^5.7.0"
|
|
55
|
+
},
|
|
56
|
+
"description": "Your machines, in view. A self-hosted Linux system and process monitor.",
|
|
57
|
+
"bin": {
|
|
58
|
+
"rigscope": "bin/rigscope.cjs"
|
|
59
|
+
},
|
|
60
|
+
"files": [
|
|
61
|
+
"bin/",
|
|
62
|
+
"dist/",
|
|
63
|
+
".next/BUILD_ID",
|
|
64
|
+
".next/*.json",
|
|
65
|
+
".next/server/",
|
|
66
|
+
".next/static/",
|
|
67
|
+
"next.config.js",
|
|
68
|
+
"README.md",
|
|
69
|
+
"CHANGELOG.md",
|
|
70
|
+
"docs/installation.md",
|
|
71
|
+
"docs/operations.md",
|
|
72
|
+
"docs/bottlenecks.md",
|
|
73
|
+
"docs/exports.md",
|
|
74
|
+
"docs/development.md",
|
|
75
|
+
"docs/releasing.md",
|
|
76
|
+
"docs/scale-validation.md",
|
|
77
|
+
"docs/scale-validation.json",
|
|
78
|
+
"docs/scale-acceptance.json",
|
|
79
|
+
"ARCHITECTURE.md",
|
|
80
|
+
"DESIGN.md",
|
|
81
|
+
"LICENSE",
|
|
82
|
+
"SECURITY.md",
|
|
83
|
+
"THIRD_PARTY_NOTICES.md",
|
|
84
|
+
"packaging/"
|
|
85
|
+
],
|
|
86
|
+
"license": "MIT",
|
|
87
|
+
"author": {
|
|
88
|
+
"name": "Triet (@Triet1402)",
|
|
89
|
+
"url": "https://github.com/Triet1402"
|
|
90
|
+
},
|
|
91
|
+
"repository": {
|
|
92
|
+
"type": "git",
|
|
93
|
+
"url": "git+https://github.com/Triet1402/rigscope.git"
|
|
94
|
+
},
|
|
95
|
+
"homepage": "https://github.com/Triet1402/rigscope#readme",
|
|
96
|
+
"bugs": {
|
|
97
|
+
"url": "https://github.com/Triet1402/rigscope/issues"
|
|
98
|
+
},
|
|
99
|
+
"keywords": [
|
|
100
|
+
"linux",
|
|
101
|
+
"monitoring",
|
|
102
|
+
"system-monitor",
|
|
103
|
+
"process-monitor",
|
|
104
|
+
"self-hosted",
|
|
105
|
+
"gpu"
|
|
106
|
+
],
|
|
107
|
+
"publishConfig": {
|
|
108
|
+
"access": "public",
|
|
109
|
+
"registry": "https://registry.npmjs.org/"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Packaging reference files
|
|
2
|
+
|
|
3
|
+
The `.env.example` files contain public examples, never deployment credentials. Copy configuration to private files (0600) outside the source tree and generate fresh credentials. No configuration starts a production deployment by itself.
|
|
4
|
+
|
|
5
|
+
Use [installation](../docs/installation.md) for managed user/system installs, [operations](../docs/operations.md) for manifest archives and recovery, and [releasing](../docs/releasing.md) for package validation. System reference units are in [system](system/README.md). Generated units in the installer source are authoritative.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Collector environment example
|
|
2
|
+
#
|
|
3
|
+
# Copy what you need into the private collector.env created by the installer
|
|
4
|
+
# (mode 0600). The generated file starts with every value commented out: with
|
|
5
|
+
# no destinations the collector fails closed and delivers nothing.
|
|
6
|
+
#
|
|
7
|
+
# PUBLIC DEMO VALUES ONLY. demo-token-local is a deliberately public local test
|
|
8
|
+
# string, not a credential for any real deployment.
|
|
9
|
+
|
|
10
|
+
# COLLECTOR_MODE=real
|
|
11
|
+
# COLLECTOR_STATE_FILE=/home/operator/.local/state/rigscope/collector/collector.json
|
|
12
|
+
# COLLECTOR_INTERVAL_MS=1000
|
|
13
|
+
# COLLECTOR_HOSTNAME=rig-01
|
|
14
|
+
|
|
15
|
+
# Enroll once against the hub, then remove the one-use grant from the hub.
|
|
16
|
+
# COLLECTOR_ENROLL_URL=ws://127.0.0.1:8081/api/collector/connect
|
|
17
|
+
# COLLECTOR_ENROLL_TOKEN=<one-use grant from the hub>
|
|
18
|
+
|
|
19
|
+
# Or supply destinations explicitly (enrollment writes these into private state
|
|
20
|
+
# for real installations). Full websocket URL including the connect path:
|
|
21
|
+
# COLLECTOR_HUBS=[{"url":"ws://127.0.0.1:8081/api/collector/connect","token":"demo-token-local"}]
|
|
22
|
+
|
|
23
|
+
# Per-destination sharing policy can withhold sensitive families. Defaults are
|
|
24
|
+
# CPU/memory only; process details remain request-scoped and are never spooled.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Hub environment example
|
|
2
|
+
#
|
|
3
|
+
# Copy the values you need into the private hub.env created by the installer
|
|
4
|
+
# (mode 0600), not into a repository file. The generated hub.env starts with
|
|
5
|
+
# every value commented out: no browser users and no collector credentials
|
|
6
|
+
# means protected browser data fails closed.
|
|
7
|
+
#
|
|
8
|
+
# PUBLIC DEMO VALUES ONLY below. Never use demo-token-local for a real host,
|
|
9
|
+
# never reuse a demo browser password, and never put credentials in URLs.
|
|
10
|
+
|
|
11
|
+
# Loopback-only development listener.
|
|
12
|
+
# HUB_HOST=127.0.0.1
|
|
13
|
+
# HUB_PORT=8081
|
|
14
|
+
|
|
15
|
+
# Remote mode is selected only when both are set. The socket must live under a
|
|
16
|
+
# private directory (the installer's state directory is writable by the unit).
|
|
17
|
+
# HUB_PUBLIC_ORIGIN=https://monitor.example.invalid
|
|
18
|
+
# HUB_SOCKET_PATH=/home/operator/.local/state/rigscope/run/hub.sock
|
|
19
|
+
|
|
20
|
+
# Browser access fails closed without this. Generate the real hash locally with
|
|
21
|
+
# scripts/hash-browser-password.cjs; never paste a plaintext password here.
|
|
22
|
+
# HUB_BROWSER_USERS=[{"username":"operator","passwordHash":"<private hash>","machineIds":"*"}]
|
|
23
|
+
|
|
24
|
+
# Static collector credentials (public demo shape/token).
|
|
25
|
+
# HUB_COLLECTORS=[{"collectorId":"demo-collector","machineId":"demo-machine","token":"demo-token-local"}]
|
|
26
|
+
|
|
27
|
+
# Enrollment/registry state. The installer sets HUB_DATABASE_PATH in the unit;
|
|
28
|
+
# keep enrollment state outside the repository, and HUB_STATE_FILE also serves
|
|
29
|
+
# as the recovery import source for previously enrolled collectors.
|
|
30
|
+
# HUB_STATE_FILE=/home/operator/.local/state/rigscope/hub/enrollment.json
|
|
31
|
+
# HUB_ENROLLMENT_GRANTS=[{"token":"<one-use grant>","expiresAt":"<ISO timestamp>"}]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Managed by Rigscope: read-only package energy/range counters for the collector group.
|
|
2
|
+
# Top-level package devices only; subdomains and constraint files are not touched.
|
|
3
|
+
SUBSYSTEM=="powercap", ACTION=="add|change", KERNEL=="intel-rapl:[0-9]*", KERNEL!="intel-rapl:*:*", ATTR{name}=="package*", \
|
|
4
|
+
RUN+="/usr/bin/chgrp rigscope-rapl /sys%p/energy_uj", \
|
|
5
|
+
RUN+="/usr/bin/chmod 0440 /sys%p/energy_uj", \
|
|
6
|
+
RUN+="/usr/bin/chgrp rigscope-rapl /sys%p/max_energy_range_uj", \
|
|
7
|
+
RUN+="/usr/bin/chmod 0440 /sys%p/max_energy_range_uj"
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# System installation references
|
|
2
|
+
|
|
3
|
+
These files illustrate dedicated `rigscope-hub` and `rigscope-collector` accounts, systemd units and read-only package-energy access. The renderer in `src/cli/system.ts` is authoritative; do not install examples blindly.
|
|
4
|
+
|
|
5
|
+
See [system installation](../../docs/installation.md#system-installation) for planning, `--apply`, independent component controls and RAPL permission limits. The collector needs GPU device access and does not use `PrivateDevices=yes`; the hub does. Both run without root privileges, with private state and bounded writable paths.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Managed by `rigscope system`. Enable explicitly after enrollment.
|
|
2
|
+
# Independent component: enabling or restarting this unit never touches the hub.
|
|
3
|
+
# The collector is nologin and has no capability grants.
|
|
4
|
+
# Package power reads use the private group rule installed separately.
|
|
5
|
+
# Node receives no capabilities, no root, and no device-control access.
|
|
6
|
+
[Unit]
|
|
7
|
+
Description=Rigscope collector (system scope)
|
|
8
|
+
Documentation=file:/opt/rigscope/current/README.md
|
|
9
|
+
After=network-online.target
|
|
10
|
+
Wants=network-online.target
|
|
11
|
+
StartLimitIntervalSec=60
|
|
12
|
+
StartLimitBurst=5
|
|
13
|
+
|
|
14
|
+
[Service]
|
|
15
|
+
Type=simple
|
|
16
|
+
User=rigscope-collector
|
|
17
|
+
Group=rigscope-collector
|
|
18
|
+
WorkingDirectory=/opt/rigscope/current
|
|
19
|
+
EnvironmentFile=/etc/rigscope/collector.env
|
|
20
|
+
Environment=NODE_ENV=production
|
|
21
|
+
Restart=on-failure
|
|
22
|
+
RestartSec=2s
|
|
23
|
+
KillSignal=SIGTERM
|
|
24
|
+
UMask=0077
|
|
25
|
+
NoNewPrivileges=yes
|
|
26
|
+
PrivateTmp=yes
|
|
27
|
+
ProtectSystem=strict
|
|
28
|
+
ProtectHome=yes
|
|
29
|
+
ProtectKernelTunables=yes
|
|
30
|
+
ProtectKernelModules=yes
|
|
31
|
+
ProtectKernelLogs=yes
|
|
32
|
+
ProtectControlGroups=yes
|
|
33
|
+
RestrictRealtime=yes
|
|
34
|
+
RestrictSUIDSGID=yes
|
|
35
|
+
RestrictNamespaces=yes
|
|
36
|
+
LockPersonality=yes
|
|
37
|
+
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
38
|
+
CapabilityBoundingSet=
|
|
39
|
+
AmbientCapabilities=
|
|
40
|
+
ExecStart="/opt/rigscope/node/node" "/opt/rigscope/current/dist/collector.cjs" --await-connection
|
|
41
|
+
SupplementaryGroups=rigscope-rapl
|
|
42
|
+
Environment="COLLECTOR_MODE=real"
|
|
43
|
+
Environment="COLLECTOR_STATE_FILE=/var/lib/rigscope/collector/state.json"
|
|
44
|
+
Environment="COLLECTOR_HEALTH_FILE=/var/lib/rigscope/collector/health.json"
|
|
45
|
+
ReadWritePaths="/var/lib/rigscope/collector"
|
|
46
|
+
ReadOnlyPaths="/opt/rigscope/current"
|
|
47
|
+
TimeoutStopSec=10s
|
|
48
|
+
StandardOutput=journal
|
|
49
|
+
StandardError=journal
|
|
50
|
+
SyslogIdentifier=rigscope-collector
|
|
51
|
+
|
|
52
|
+
[Install]
|
|
53
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Managed by `rigscope system`. Enable explicitly; do not edit in place.
|
|
2
|
+
# Independent component: enabling or restarting this unit never touches the collector.
|
|
3
|
+
[Unit]
|
|
4
|
+
Description=Rigscope hub (system scope)
|
|
5
|
+
Documentation=file:/opt/rigscope/current/README.md
|
|
6
|
+
After=network-online.target
|
|
7
|
+
Wants=network-online.target
|
|
8
|
+
StartLimitIntervalSec=60
|
|
9
|
+
StartLimitBurst=5
|
|
10
|
+
|
|
11
|
+
[Service]
|
|
12
|
+
Type=simple
|
|
13
|
+
User=rigscope-hub
|
|
14
|
+
Group=rigscope-hub
|
|
15
|
+
WorkingDirectory=/opt/rigscope/current
|
|
16
|
+
EnvironmentFile=/etc/rigscope/hub.env
|
|
17
|
+
Environment=NODE_ENV=production
|
|
18
|
+
Restart=on-failure
|
|
19
|
+
RestartSec=2s
|
|
20
|
+
KillSignal=SIGTERM
|
|
21
|
+
UMask=0077
|
|
22
|
+
NoNewPrivileges=yes
|
|
23
|
+
PrivateTmp=yes
|
|
24
|
+
ProtectSystem=strict
|
|
25
|
+
ProtectHome=yes
|
|
26
|
+
ProtectKernelTunables=yes
|
|
27
|
+
ProtectKernelModules=yes
|
|
28
|
+
ProtectKernelLogs=yes
|
|
29
|
+
ProtectControlGroups=yes
|
|
30
|
+
RestrictRealtime=yes
|
|
31
|
+
RestrictSUIDSGID=yes
|
|
32
|
+
RestrictNamespaces=yes
|
|
33
|
+
LockPersonality=yes
|
|
34
|
+
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
35
|
+
CapabilityBoundingSet=
|
|
36
|
+
AmbientCapabilities=
|
|
37
|
+
ExecStart="/opt/rigscope/node/node" "/opt/rigscope/current/dist/hub.cjs"
|
|
38
|
+
Environment="HUB_DATABASE_PATH=/var/lib/rigscope/hub/telemetry.sqlite"
|
|
39
|
+
Environment="HUB_STATE_FILE=/var/lib/rigscope/hub/enrollment.json"
|
|
40
|
+
Environment="HUB_SOCKET_PATH=/var/lib/rigscope/hub/run/hub.sock"
|
|
41
|
+
ReadWritePaths="/var/lib/rigscope/hub"
|
|
42
|
+
ReadOnlyPaths="/opt/rigscope/current"
|
|
43
|
+
PrivateDevices=yes
|
|
44
|
+
TimeoutStopSec=15s
|
|
45
|
+
StandardOutput=journal
|
|
46
|
+
StandardError=journal
|
|
47
|
+
SyslogIdentifier=rigscope-hub
|
|
48
|
+
|
|
49
|
+
[Install]
|
|
50
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Reference only. Generated by `rigscope system install --apply`.
|
|
2
|
+
# One complete package: both dedicated, unprivileged, non-login accounts exist
|
|
3
|
+
# even when one component is disabled, so it can be enabled later.
|
|
4
|
+
g rigscope-hub - -
|
|
5
|
+
u rigscope-hub - "Rigscope hub" /nonexistent /usr/sbin/nologin
|
|
6
|
+
g rigscope-collector - -
|
|
7
|
+
u rigscope-collector - "Rigscope collector" /nonexistent /usr/sbin/nologin
|