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.
Files changed (188) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-path-routes-manifest.json +13 -0
  3. package/.next/build-manifest.json +20 -0
  4. package/.next/export-marker.json +6 -0
  5. package/.next/images-manifest.json +68 -0
  6. package/.next/next-minimal-server.js.nft.json +1 -0
  7. package/.next/next-server.js.nft.json +1 -0
  8. package/.next/package.json +1 -0
  9. package/.next/prerender-manifest.json +297 -0
  10. package/.next/react-loadable-manifest.json +1 -0
  11. package/.next/required-server-files.json +369 -0
  12. package/.next/routes-manifest.json +114 -0
  13. package/.next/server/app/_global-error/page.js +31 -0
  14. package/.next/server/app/_global-error/page.js.nft.json +1 -0
  15. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -0
  16. package/.next/server/app/_global-error.html +1 -0
  17. package/.next/server/app/_global-error.meta +35 -0
  18. package/.next/server/app/_global-error.rsc +18 -0
  19. package/.next/server/app/_global-error.segments/_full.segment.rsc +18 -0
  20. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +24 -0
  21. package/.next/server/app/_global-error.segments/_tree.segment.rsc +3 -0
  22. package/.next/server/app/_not-found/page.js +1 -0
  23. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  24. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  25. package/.next/server/app/_not-found.html +1 -0
  26. package/.next/server/app/_not-found.meta +35 -0
  27. package/.next/server/app/_not-found.rsc +24 -0
  28. package/.next/server/app/_not-found.segments/_full.segment.rsc +24 -0
  29. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +30 -0
  30. package/.next/server/app/_not-found.segments/_tree.segment.rsc +4 -0
  31. package/.next/server/app/administration/page.js +1 -0
  32. package/.next/server/app/administration/page.js.nft.json +1 -0
  33. package/.next/server/app/administration/page_client-reference-manifest.js +1 -0
  34. package/.next/server/app/administration.html +1 -0
  35. package/.next/server/app/administration.meta +34 -0
  36. package/.next/server/app/administration.rsc +29 -0
  37. package/.next/server/app/administration.segments/_full.segment.rsc +29 -0
  38. package/.next/server/app/administration.segments/_tree.segment.rsc +4 -0
  39. package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +35 -0
  40. package/.next/server/app/alerts/page.js +1 -0
  41. package/.next/server/app/alerts/page.js.nft.json +1 -0
  42. package/.next/server/app/alerts/page_client-reference-manifest.js +1 -0
  43. package/.next/server/app/alerts.html +1 -0
  44. package/.next/server/app/alerts.meta +34 -0
  45. package/.next/server/app/alerts.rsc +28 -0
  46. package/.next/server/app/alerts.segments/_full.segment.rsc +28 -0
  47. package/.next/server/app/alerts.segments/_tree.segment.rsc +4 -0
  48. package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +34 -0
  49. package/.next/server/app/connections/page.js +1 -0
  50. package/.next/server/app/connections/page.js.nft.json +1 -0
  51. package/.next/server/app/connections/page_client-reference-manifest.js +1 -0
  52. package/.next/server/app/connections.html +1 -0
  53. package/.next/server/app/connections.meta +34 -0
  54. package/.next/server/app/connections.rsc +29 -0
  55. package/.next/server/app/connections.segments/_full.segment.rsc +29 -0
  56. package/.next/server/app/connections.segments/_tree.segment.rsc +4 -0
  57. package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +35 -0
  58. package/.next/server/app/history/page.js +2 -0
  59. package/.next/server/app/history/page.js.nft.json +1 -0
  60. package/.next/server/app/history/page_client-reference-manifest.js +1 -0
  61. package/.next/server/app/history.html +1 -0
  62. package/.next/server/app/history.meta +34 -0
  63. package/.next/server/app/history.rsc +33 -0
  64. package/.next/server/app/history.segments/_full.segment.rsc +33 -0
  65. package/.next/server/app/history.segments/_tree.segment.rsc +4 -0
  66. package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +37 -0
  67. package/.next/server/app/icon.svg/route.js +1 -0
  68. package/.next/server/app/icon.svg/route.js.nft.json +1 -0
  69. package/.next/server/app/icon.svg.body +1 -0
  70. package/.next/server/app/icon.svg.meta +1 -0
  71. package/.next/server/app/index.html +1 -0
  72. package/.next/server/app/index.meta +29 -0
  73. package/.next/server/app/index.rsc +26 -0
  74. package/.next/server/app/index.segments/__PAGE__.segment.rsc +31 -0
  75. package/.next/server/app/index.segments/_full.segment.rsc +26 -0
  76. package/.next/server/app/index.segments/_tree.segment.rsc +3 -0
  77. package/.next/server/app/login/page.js +1 -0
  78. package/.next/server/app/login/page.js.nft.json +1 -0
  79. package/.next/server/app/login/page_client-reference-manifest.js +1 -0
  80. package/.next/server/app/login.html +1 -0
  81. package/.next/server/app/login.meta +34 -0
  82. package/.next/server/app/login.rsc +26 -0
  83. package/.next/server/app/login.segments/_full.segment.rsc +26 -0
  84. package/.next/server/app/login.segments/_tree.segment.rsc +4 -0
  85. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +32 -0
  86. package/.next/server/app/machines/[machineId]/page.js +1 -0
  87. package/.next/server/app/machines/[machineId]/page.js.nft.json +1 -0
  88. package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -0
  89. package/.next/server/app/page.js +1 -0
  90. package/.next/server/app/page.js.nft.json +1 -0
  91. package/.next/server/app/page_client-reference-manifest.js +1 -0
  92. package/.next/server/app/processes/page.js +2 -0
  93. package/.next/server/app/processes/page.js.nft.json +1 -0
  94. package/.next/server/app/processes/page_client-reference-manifest.js +1 -0
  95. package/.next/server/app/processes.html +1 -0
  96. package/.next/server/app/processes.meta +34 -0
  97. package/.next/server/app/processes.rsc +30 -0
  98. package/.next/server/app/processes.segments/_full.segment.rsc +30 -0
  99. package/.next/server/app/processes.segments/_tree.segment.rsc +4 -0
  100. package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +36 -0
  101. package/.next/server/app-paths-manifest.json +13 -0
  102. package/.next/server/chunks/161.js +1 -0
  103. package/.next/server/chunks/168.js +1 -0
  104. package/.next/server/chunks/266.js +68 -0
  105. package/.next/server/chunks/430.js +116 -0
  106. package/.next/server/chunks/721.js +1 -0
  107. package/.next/server/chunks/751.js +1 -0
  108. package/.next/server/chunks/877.js +1 -0
  109. package/.next/server/chunks/894.js +2 -0
  110. package/.next/server/functions-config-manifest.json +4 -0
  111. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  112. package/.next/server/middleware-build-manifest.js +1 -0
  113. package/.next/server/middleware-manifest.json +6 -0
  114. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  115. package/.next/server/next-font-manifest.js +1 -0
  116. package/.next/server/next-font-manifest.json +1 -0
  117. package/.next/server/pages/404.html +1 -0
  118. package/.next/server/pages/500.html +1 -0
  119. package/.next/server/pages-manifest.json +4 -0
  120. package/.next/server/prefetch-hints.json +123 -0
  121. package/.next/server/preview-props.json +5 -0
  122. package/.next/server/server-reference-manifest.js +1 -0
  123. package/.next/server/server-reference-manifest.json +1 -0
  124. package/.next/server/webpack-runtime.js +1 -0
  125. package/.next/static/DZKWF9ur7H5wI0c7RAhdq/_buildManifest.js +1 -0
  126. package/.next/static/DZKWF9ur7H5wI0c7RAhdq/_ssgManifest.js +1 -0
  127. package/.next/static/chunks/103-c47318242b24e9ac.js +1 -0
  128. package/.next/static/chunks/157-0d36ebf66af048ac.js +1 -0
  129. package/.next/static/chunks/227-417939c480f50747.js +1 -0
  130. package/.next/static/chunks/408-18819c6490a5f8d3.js +1 -0
  131. package/.next/static/chunks/4bd1b696-c7408084347438ae.js +1 -0
  132. package/.next/static/chunks/794-ceeb732a1a81bc27.js +52 -0
  133. package/.next/static/chunks/939-2600d7c8e583d5ef.js +1 -0
  134. package/.next/static/chunks/app/_global-error/page-aba28f88d5781771.js +1 -0
  135. package/.next/static/chunks/app/_not-found/page-a2a510233e4836e2.js +1 -0
  136. package/.next/static/chunks/app/administration/page-4b16b19bb6a39c36.js +1 -0
  137. package/.next/static/chunks/app/alerts/page-fe0e877778c60af8.js +1 -0
  138. package/.next/static/chunks/app/connections/page-99cd54d6929b3f1f.js +1 -0
  139. package/.next/static/chunks/app/history/page-3fa5846eca26b7f7.js +3 -0
  140. package/.next/static/chunks/app/layout-4feff677c4071745.js +1 -0
  141. package/.next/static/chunks/app/login/page-c57e12dbe5146737.js +1 -0
  142. package/.next/static/chunks/app/machines/[machineId]/page-a5aa1d8676272f38.js +1 -0
  143. package/.next/static/chunks/app/page-1ae9fc94c897785b.js +1 -0
  144. package/.next/static/chunks/app/processes/page-e471ed23a1d5632f.js +3 -0
  145. package/.next/static/chunks/framework-ea4f97ca039bd777.js +1 -0
  146. package/.next/static/chunks/main-a458d32948147b84.js +5 -0
  147. package/.next/static/chunks/main-app-f92328c8950d2e53.js +1 -0
  148. package/.next/static/chunks/next/dist/client/components/builtin/app-error-aba28f88d5781771.js +1 -0
  149. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-aba28f88d5781771.js +1 -0
  150. package/.next/static/chunks/next/dist/client/components/builtin/global-error-43cbaa3aa1576eb4.js +1 -0
  151. package/.next/static/chunks/next/dist/client/components/builtin/not-found-aba28f88d5781771.js +1 -0
  152. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-aba28f88d5781771.js +1 -0
  153. package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  154. package/.next/static/chunks/webpack-9ca43f4ce1bb8938.js +1 -0
  155. package/.next/static/css/e41ccbccc3afaae5.css +1 -0
  156. package/ARCHITECTURE.md +279 -0
  157. package/CHANGELOG.md +25 -0
  158. package/DESIGN.md +299 -0
  159. package/LICENSE +21 -0
  160. package/README.md +157 -2
  161. package/SECURITY.md +12 -0
  162. package/THIRD_PARTY_NOTICES.md +8847 -0
  163. package/bin/rigscope.cjs +6 -0
  164. package/dist/cli.cjs +9075 -0
  165. package/dist/collector.cjs +5671 -0
  166. package/dist/hub.cjs +8355 -0
  167. package/dist/ops.cjs +4972 -0
  168. package/dist/system.cjs +4333 -0
  169. package/docs/bottlenecks.md +62 -0
  170. package/docs/development.md +46 -0
  171. package/docs/exports.md +39 -0
  172. package/docs/installation.md +477 -0
  173. package/docs/operations.md +515 -0
  174. package/docs/releasing.md +152 -0
  175. package/docs/scale-acceptance.json +412 -0
  176. package/docs/scale-validation.json +1898 -0
  177. package/docs/scale-validation.md +263 -0
  178. package/next.config.js +38 -0
  179. package/package.json +109 -4
  180. package/packaging/README.md +5 -0
  181. package/packaging/collector.env.example +24 -0
  182. package/packaging/hub.env.example +31 -0
  183. package/packaging/system/90-rigscope-rapl.rules +7 -0
  184. package/packaging/system/README.md +5 -0
  185. package/packaging/system/rigscope-collector.service.example +53 -0
  186. package/packaging/system/rigscope-hub.service.example +50 -0
  187. package/packaging/system/rigscope-rapl.sysusers +5 -0
  188. package/packaging/system/rigscope.sysusers +7 -0
@@ -0,0 +1,515 @@
1
+ # Production operations
2
+
3
+ This guide covers the supported single-host, two-process deployment:
4
+
5
+ - `dist/hub.cjs` — Node 26 custom server: listener, WebSocket collectors,
6
+ browser SSE, authentication and the production Next.js handler from `.next`.
7
+ - `dist/collector.cjs` — outbound-only collector with durable per-destination
8
+ outboxes.
9
+ - `dist/ops.cjs` — inspect, migrate, backup, restore, package, install,
10
+ activate, rollback and bounded synthetic measurement.
11
+
12
+ There is no Next.js standalone output and no `next start`. The hub runs the
13
+ custom server from the installed release directory; the production UI must be
14
+ present in `.next`.
15
+
16
+ All paths below are examples. Choose a prefix and state directory owned by the
17
+ service user. Paths containing spaces are supported by the generated units.
18
+
19
+ ## Managed CLI operations
20
+
21
+ For a per-user installation created by `rigscope install` (default state
22
+ `~/.local/state/rigscope`), the CLI owns the runtime, component units and release pairing:
23
+
24
+ ```sh
25
+ rigscope status
26
+ rigscope doctor [--json]
27
+ rigscope upgrade [--from PACKAGE] [--timeout SECONDS]
28
+ rigscope rollback [--backup PATH] [--timeout SECONDS]
29
+ rigscope recover-password [--user owner] [--password-file PATH] [--enable]
30
+ ```
31
+
32
+ `doctor` is read-only. It checks Node, private configuration and state
33
+ permissions, the installed and previous runtimes, the component units, the database
34
+ and schema, free space, hub port/Unix-socket state, role-appropriate listener
35
+ health, configured destinations with TLS certificate validation, and procfs or
36
+ RAPL readability for a collector. A hub without a collector is valid, and a
37
+ collector without a local hub database is valid. Output contains counts and
38
+ paths, never password hashes, collector tokens or environment values.
39
+
40
+ `upgrade` stages the package without touching the active runtime, stops the
41
+ running components, takes a consistent pre-upgrade hub database backup when one
42
+ exists, and activates the new runtime. It restarts only components that were
43
+ running before the upgrade and verifies them within `--timeout` seconds (default
44
+ 25). Verification follows the installation's enabled components:
45
+
46
+ - the active package reports the staged version;
47
+ - restarted component services are active;
48
+ - a restarted hub answers `status: "ok"` with the expected version;
49
+ - an existing hub database passes integrity and ledger checks and reaches the
50
+ staged runtime's supported schema;
51
+ - a restarted collector produces a fresh sampling health marker.
52
+
53
+ A failed start, migration or health wait restores the previous runtime and the
54
+ paired pre-upgrade database when the old runtime cannot read the new schema. The
55
+ replaced database is preserved first; credentials, collector identities and
56
+ outboxes are not rewritten. `release-state.json` in the state directory records
57
+ which runtime each backup belongs to. An inactive installation is staged and
58
+ activated without starting; live health is then reported as skipped.
59
+
60
+ `rollback` swaps to `runtime-previous` and validates it with the same bounded
61
+ health and database checks. If the current schema is compatible it keeps the
62
+ database unchanged, including post-upgrade data. If the previous runtime cannot
63
+ read it, rollback restores the paired pre-upgrade backup, refuses when no
64
+ compatible backup is recorded, and prints where the replaced newer database was
65
+ preserved. For installations created before release state existed, pass an
66
+ explicit compatible copy with `--backup PATH`. Rollback never silently deletes
67
+ the newer database and never runs old code against a newer schema.
68
+
69
+ `recover-password` is offline recovery for the durable managed user store. It
70
+ requires the hub service to be stopped, takes the hub database lease and
71
+ refuses a live or ambiguous lock. Enter the password at the private prompt, or
72
+ pass a private `0600` file with `--password-file`. It updates only that managed
73
+ user's hash in the database, clears `disabled` only with `--enable`, writes a
74
+ bounded `user.password-recovery` audit row and bumps a durable session-epoch
75
+ marker. It never edits `config.json` or `HUB_BROWSER_USERS`, so bootstrap
76
+ password changes do not overwrite managed users. Restarting the hub invalidates
77
+ all in-memory browser sessions; sign in again with the new password.
78
+
79
+ ## Low-level manifest archive workflow
80
+
81
+ The remaining archive deployment examples assume a source checkout with built
82
+ artifacts. They use a manifest `.tar.gz`, which is different from the npm `.tgz`
83
+ used by `rigscope install` and `rigscope upgrade`. For ordinary installations,
84
+ follow [installation](installation.md) and the managed commands above. The npm
85
+ package excludes source helper scripts and does not require users to build it.
86
+
87
+ ### Build and package
88
+
89
+ ```sh
90
+ npm ci
91
+ npm run build:ui # production .next
92
+ npm run build:runtime # dist/hub.cjs, dist/collector.cjs, dist/ops.cjs
93
+ node scripts/package-release.cjs --output /var/tmp/rigscope-release.tar.gz
94
+ ```
95
+
96
+ The archive contains the built runtime, `.next` without its cache, package and
97
+ lock files, the repository's `next.config.js`, `tsconfig.json`,
98
+ operational docs and the `packaging/` examples. It deliberately excludes source, tests, git data,
99
+ `node_modules`, private environment files, SQLite databases, keys and logs.
100
+ `manifest.json` lists every packaged file with size and SHA-256; the installer
101
+ re-verifies it before activation.
102
+
103
+ Production dependencies are installed inside each staged release:
104
+
105
+ ```sh
106
+ cd /home/operator/.local/opt/rigscope/releases/<version>
107
+ npm ci --omit=dev
108
+ ```
109
+
110
+ `install --install-deps` can run that command explicitly, but the installer
111
+ never runs npm implicitly for activation. If a package mirror is unavailable,
112
+ stage without activation and install from an approved mirror; do not weaken npm
113
+ security policy just to make installation pass. Isolated tests use a synthetic
114
+ dependency tree and do not perform remote installs.
115
+
116
+ ## Stage and activate
117
+
118
+ ```sh
119
+ node scripts/install-release.cjs \
120
+ --source /var/tmp/rigscope-release.tar.gz \
121
+ --prefix /home/operator/.local/opt/rigscope \
122
+ --state-dir /home/operator/.local/state/rigscope \
123
+ --units-dir /home/operator/.config/systemd/user
124
+ ```
125
+
126
+ The installer creates:
127
+
128
+ ```text
129
+ <prefix>/
130
+ releases/<version>/ immutable built release + manifest
131
+ releases/<version>/node_modules/
132
+ current -> releases/<version>
133
+ previous -> releases/<previous> (retained for rollback)
134
+ <state>/
135
+ hub/telemetry.sqlite hub database (0700 dir, 0600 file)
136
+ hub.env private EnvironmentFile (0600, no secrets printed)
137
+ collector.env private EnvironmentFile (0600)
138
+ collector/collector.json collector identity/enrollment (created by collector)
139
+ outbox/<sha256(url)[0:16]>/ per-destination collector outboxes
140
+ backups/ private pre-upgrade/pre-activation database copies
141
+ install-state.json versions, schema backups, no credentials
142
+ run/ private runtime directory for a Unix socket
143
+ <units-dir>/
144
+ rigscope-hub.service
145
+ rigscope-collector.service
146
+ ```
147
+
148
+ Staging does not switch `current` or start anything. After production
149
+ dependencies are installed and the old hub is stopped, activate explicitly:
150
+
151
+ ```sh
152
+ node scripts/install-release.cjs activate \
153
+ --prefix /home/operator/.local/opt/rigscope \
154
+ --state-dir /home/operator/.local/state/rigscope \
155
+ --version <version> --confirm-stopped
156
+ ```
157
+
158
+ Activation order is fixed:
159
+
160
+ 1. Validate the release manifest, hashes and production dependencies.
161
+ 2. Verify no hub lock exists and the operator confirmed the hub is stopped.
162
+ 3. Inspect the current database schema.
163
+ 4. If migration is needed: write a consistent online backup, then migrate, then
164
+ verify integrity and the target schema. A failed migration restores the
165
+ pre-migration backup and leaves `current` unchanged.
166
+ 5. Atomically switch `current`, retaining `previous` and a schema-backup record.
167
+ 6. Print (never execute) systemd commands.
168
+
169
+ If validation or migration fails, a good database is never silently replaced.
170
+ The pre-migration backup path is printed. First installation with no database
171
+ creates and migrates a new private database.
172
+
173
+ ## systemd user units
174
+
175
+ The generated units use an absolute Node 26 executable, absolute paths,
176
+ `WorkingDirectory=<prefix>/current`, a private `EnvironmentFile`, journald
177
+ logging, `Restart=on-failure`, `NoNewPrivileges=yes`, `PrivateTmp=yes`,
178
+ `UMask=0077`, `ProtectSystem=strict`, and explicit writable state/database/cache paths.
179
+
180
+ Startup is explicit and is not performed by the installer:
181
+
182
+ ```sh
183
+ systemctl --user daemon-reload
184
+ systemctl --user enable --now rigscope-hub.service rigscope-collector.service
185
+ ```
186
+
187
+ Automatic startup after logout/host reboot requires an explicit administrator
188
+ choice to enable user lingering for that account:
189
+
190
+ ```sh
191
+ sudo loginctl enable-linger operator
192
+ ```
193
+
194
+ Review the generated units and private env files first. Health and observation:
195
+
196
+ ```sh
197
+ curl --fail --silent http://127.0.0.1:8081/api/health
198
+ systemctl --user status rigscope-hub.service rigscope-collector.service
199
+ journalctl --user -u rigscope-hub.service -f
200
+ journalctl --user -u rigscope-collector.service -f
201
+ ```
202
+
203
+ Shutdown is bounded: the hub targets five seconds and the collector three
204
+ seconds; the generated `TimeoutStopSec` leaves a ten- and seven-second slack
205
+ respectively. Keep those times at or above the process deadlines.
206
+
207
+ ## Configuration
208
+
209
+ `RIGSCOPE_OPS_DEBUG` enables operation diagnostics; keep diagnostic output private. `RIGSCOPE_HUB_LOCK_FILE` overrides the hub lease path when needed; use the same path for the hub and operations commands.
210
+
211
+ The generated `hub.env` and `collector.env` contain only comments. Empty
212
+ configuration fails closed: no `HUB_BROWSER_USERS` means browser data is
213
+ unavailable, and no collector credentials/destinations means no fleet data.
214
+ Never copy the public demo credentials from the examples to a real host.
215
+
216
+ Loopback development:
217
+
218
+ ```sh
219
+ HUB_HOST=127.0.0.1
220
+ HUB_PORT=8081
221
+ HUB_DATABASE_PATH=/home/operator/.local/state/rigscope/hub/telemetry.sqlite
222
+ ```
223
+
224
+ Browser users and collectors belong in the private environment file. Generate
225
+ browser password hashes locally with `scripts/hash-browser-password.cjs`; never
226
+ store plaintext passwords or hashes in shared logs.
227
+
228
+ Remote mode uses a private Unix socket behind an external TLS terminator:
229
+
230
+ ```sh
231
+ HUB_PUBLIC_ORIGIN=https://monitor.example.invalid
232
+ HUB_SOCKET_PATH=/home/operator/.local/state/rigscope/run/hub.sock
233
+ ```
234
+
235
+ The parent directory must be private and owned by the service user. The
236
+ installer's state `run/` directory is already writable by the unit. Remote
237
+ health checks go through the socket with the exact Host:
238
+
239
+ ```sh
240
+ curl --fail --silent --unix-socket <socket> \
241
+ -H 'Host: monitor.example.invalid' http://localhost/api/health
242
+ ```
243
+
244
+ This example hostname and every `demo-token-*` value are public fixtures, not a
245
+ deployment. See `development.md` for the exact origin/Host/cookie
246
+ boundary. Never expose the loopback HTTP listener remotely without the private
247
+ socket/TLS boundary and authentication.
248
+
249
+ Collector enrollment remains separate from browser sessions:
250
+
251
+ ```sh
252
+ COLLECTOR_ENROLL_URL=ws://127.0.0.1:8081/api/collector/connect
253
+ COLLECTOR_ENROLL_TOKEN=<one-use grant>
254
+ COLLECTOR_MODE=real
255
+ COLLECTOR_STATE_FILE=/home/operator/.local/state/rigscope/collector/collector.json
256
+ ```
257
+
258
+ Remove the one-use grant after enrollment and start or restart the collector. The
259
+ hub does not need a restart. Enrollment state
260
+ and outboxes live outside the repository.
261
+
262
+ For an isolated local check without touching the real deployment, the public demo
263
+ grant from the packaging examples can drive a loopback enrollment and collection
264
+ run. These are deliberate demo strings only, never a real deployment:
265
+
266
+ ```sh
267
+ HUB_STATE_FILE="$PWD/.rigscope-local/hub/enrollment.json" \
268
+ HUB_ENROLLMENT_GRANTS="$(node -e 'console.log(JSON.stringify([{token:"demo-token-enrollment-local",expiresAt:new Date(Date.now()+300000).toISOString()}]))')" \
269
+ npm start
270
+
271
+ # In another terminal:
272
+ COLLECTOR_STATE_FILE="$PWD/.rigscope-local/collector/state.json" \
273
+ COLLECTOR_ENROLL_URL='ws://127.0.0.1:8081/api/collector/connect' \
274
+ COLLECTOR_ENROLL_TOKEN='demo-token-enrollment-local' \
275
+ npm run collector:enroll
276
+
277
+ # Start real local collection with the saved credential:
278
+ COLLECTOR_STATE_FILE="$PWD/.rigscope-local/collector/state.json" npm run collector:start
279
+ ```
280
+
281
+ Enrollment posts `{collectorId}` to `/api/collector/enroll`; the bearer grant travels
282
+ only in the Authorization header. Grants must expire within fifteen minutes and are
283
+ consumed across restarts. Plaintext is allowed only for literal loopback addresses;
284
+ WSS enrollment uses HTTPS with ordinary certificate validation.
285
+
286
+ ## Database commands
287
+
288
+ All commands take explicit paths. They print no credentials, user hashes,
289
+ webhook URLs, tokens or record payloads.
290
+
291
+ ```sh
292
+ # Read-only schema/integrity/size/table summary.
293
+ node dist/ops.cjs inspect --database /path/telemetry.sqlite
294
+
295
+ # Consistent single-file online backup. Does NOT migrate the source.
296
+ node dist/ops.cjs backup \
297
+ --database /path/telemetry.sqlite \
298
+ --output /path/private-backups/telemetry-$(date +%Y%m%dT%H%M%S).sqlite
299
+
300
+ # Offline guarded migration. Existing databases require --confirm-stopped.
301
+ node dist/ops.cjs migrate \
302
+ --database /path/telemetry.sqlite \
303
+ --backup-dir /path/private-backups --confirm-stopped
304
+
305
+ # Guarded restore over an existing database: both flags are required.
306
+ node dist/ops.cjs restore \
307
+ --input /path/private-backups/telemetry-....sqlite \
308
+ --database /path/telemetry.sqlite \
309
+ --replace --confirm-stopped
310
+
311
+ # Offline managed-user recovery; password comes from a private 0600 file.
312
+ node dist/ops.cjs recover-admin \
313
+ --database /path/telemetry.sqlite \
314
+ --user owner --password-file /path/private-recovery-password --enable
315
+ ```
316
+
317
+ Safety rules enforced by `backup`/`restore`:
318
+
319
+ - The hub database is one SQLite file. The online backup copies all tables in
320
+ that file: telemetry, history rollups, receipts/gaps, machine latest and
321
+ inventory, the collector registry/grants/audit, admin users/audit, and alert
322
+ rules/incidents/jobs/webhook state. There is no partial table selection.
323
+ - Backup opens the source read-only, runs `integrity_check`, stages the copy,
324
+ converts it to a self-contained DELETE-journal file, verifies the staged copy,
325
+ sets mode 0600 and atomically renames it. The source schema is never migrated.
326
+ - Restore validates the input integrity and migration ledger, refuses a schema
327
+ newer than this operations build, refuses to overwrite the target without
328
+ `--replace --confirm-stopped`, and refuses if a hub lock is present.
329
+ - It stages and verifies a copy first. For an existing target it checkpoints the
330
+ old database, takes a pre-restore backup, then removes only the old WAL/SHM
331
+ sidecars after proof that the hub is stopped, and atomically replaces the
332
+ main file. A failed validation never overwrites the target.
333
+ - The hub process lease is `<databasePath>.hub.lock` (JSON `{pid,instance}`).
334
+ Operations refuses while it exists, even if the PID looks stale, because only
335
+ the hub's own startup proof may recover a proven-dead lease. The hub removes
336
+ it on clean shutdown/exit; operators must inspect a lock and never delete it
337
+ blindly.
338
+ - Restoring an older-schema backup is allowed; the matching release migrates it
339
+ on the next hub start. Never start an old release against a newer schema.
340
+
341
+ Schema versions are derived from the build's migration ledger. The current
342
+ ledger includes telemetry, latest-session identity, admin, alerts and collector
343
+ registry migrations. A newer database must be opened by newer code; an old
344
+ release must never be pointed at a newer schema. `inspect` reports this state.
345
+
346
+ ## Consistent backup set
347
+
348
+ The database backup is not the whole installation. A restorable set is:
349
+
350
+ 1. Hub SQLite database: `telemetry.sqlite` (includes registry/admin/alerts).
351
+ Use `ops backup`; do not copy the main file alone while the hub is running.
352
+ 2. Enrollment/config state when configured: `HUB_STATE_FILE` (for
353
+ example `hub/enrollment.json`, the import source for previously enrolled
354
+ collectors) and the private hub environment file. These
355
+ contain credentials: protect and copy them only between private locations.
356
+ 3. Collector identity/enrollment: `collector/collector.json`.
357
+ 4. Collector outboxes: `outbox/<destination-hash>/outbox.sqlite` per hub URL,
358
+ which may contain unacknowledged telemetry. Replay is at-least-once; the hub
359
+ deduplicates by collector/run/sequence.
360
+ 5. Optional operational metadata: `install-state.json`, units and the built
361
+ release. Keep the release matching the database schema for rollback.
362
+
363
+ For a consistent set, prefer stopping both services cleanly, then run the online
364
+ database backup and copy the other private files. For an online backup, the SQLite
365
+ copy is internally consistent, but JSON identity/outbox files should be copied in
366
+ a quiescent moment; outbox copies may lag and replay duplicates are harmless.
367
+
368
+ Restore the database with `ops restore`; copy non-database state back separately
369
+ with the same ownership and modes. Never silently copy another installation's private keys, browser credentials or unrelated files.
370
+
371
+ ## Upgrade and rollback
372
+
373
+ For an upgrade:
374
+
375
+ 1. Package and stage the new release; install `npm ci --omit=dev` there.
376
+ 2. Stop the old hub/collector and verify the lock is gone.
377
+ 3. Run `install ... --activate --confirm-stopped` (or `activate`).
378
+ 4. The installer backs up, migrates and verifies before switching `current`.
379
+ 5. Start/enable services explicitly and check health. If the services were
380
+ already installed:
381
+ `systemctl --user restart rigscope-hub.service rigscope-collector.service`.
382
+
383
+ Rollback:
384
+
385
+ ```sh
386
+ node dist/ops.cjs rollback \
387
+ --prefix /home/operator/.local/opt/rigscope \
388
+ --state-dir /home/operator/.local/state/rigscope \
389
+ --confirm-stopped
390
+ ```
391
+
392
+ Rollback is refused while the hub lock exists. If the database schema is newer
393
+ than the previous release's manifest, rollback restores the recorded matching
394
+ pre-upgrade backup (verified against that release's schema) before switching
395
+ `current`. If no matching backup exists, it refuses rather than claiming an old
396
+ binary can read a newer schema. The replaced database is itself backed up first.
397
+
398
+ ### Upgrade ordering
399
+
400
+ Within a user installation or this manifest archive layout, hub and collector
401
+ share one release: upgrade the installation together. Stop the hub and
402
+ collector, activate the new release (which migrates and verifies the database),
403
+ then start both services from the same release. Avoid running an old collector
404
+ against a newer hub or vice versa for longer than the restart itself. Do not roll
405
+ a hub back below the schema its database already uses; `rollback` enforces that
406
+ boundary, and collector rollback keeps its private state and outboxes. Process
407
+ detail remains request-scoped and is never persisted or included in the backup
408
+ set. Separate machines and hubs are upgraded independently. System installations
409
+ use the component controls described in [installation](installation.md#system-component-lifecycle).
410
+
411
+ On upgrade from the previous collector endpoint, saved `/api/collector/v1/connect`
412
+ URLs normalize to `/api/collector/connect` without changing identity, credentials,
413
+ sharing policy or pending enrollment recovery proofs. The collector recovers the
414
+ old URL's outbox before connecting. If both queue directories exist, it imports
415
+ missing record identities in a transaction before removing the old directory.
416
+ Stored metrics and gaps with the previous `protocolVersion: 2` envelope are
417
+ decoded into the current wire format at the outbox boundary; record identities
418
+ and previously offered gap payloads are preserved. Wire parsers remain strict.
419
+
420
+ ## Measurement
421
+
422
+ `ops measure` is a bounded synthetic runner, not a capacity guarantee:
423
+
424
+ ```sh
425
+ node dist/ops.cjs measure --machines 2 --batches 30 --records-per-batch 5 \
426
+ --gpus 2 --viewers 2 --json
427
+ ```
428
+
429
+ It writes synthetic records through the real `TelemetryStore` into a
430
+ temporary private database and reports accepted/rejected/busy counts, ingestion
431
+ latency, event-loop delay/utilization, RSS, database/WAL size, store queue and
432
+ flush counters, and one history-query latency. The output is labelled synthetic.
433
+ Defaults are intentionally small; upper bounds are 1,000 machines, 2,000 batches,
434
+ 32 records per batch and 100,000 records total. It never opens a service socket
435
+ or contacts a hub. `--keep` retains the temporary directory for inspection. The
436
+ library accepts `historyConcurrency` for overlapping History request rounds;
437
+ the CLI exposes it as `--history-concurrency N` (default 1).
438
+
439
+ Current integration notes: storage commits batches of at most 32 records and the
440
+ history query has a 25,000-row/100 ms budget; a 512-record synthetic benchmark
441
+ showed roughly 407 ms transactions. Measure on the actual deployment before
442
+ choosing cadence, retention or viewer counts. No latency or capacity guarantee
443
+ is implied by these development numbers.
444
+
445
+ For sustained concurrent viewers, collector reconnects/replay, slow SSE
446
+ consumers and a fleet beyond 50 machines, use the isolated harness described in
447
+ [scale-validation.md](scale-validation.md). It spawns a temporary loopback hub
448
+ and never contacts the live service.
449
+
450
+ ## Verification limits
451
+
452
+ Default repository tests exercise backup/restore, newer-schema refusal, corrupted input,
453
+ lock refusal, installer staging/activation/rollback and failed-upgrade recovery
454
+ in isolated temporary directories. CLI fixture tests additionally cover doctor
455
+ redaction and role separation, inactive and live upgrade validation, paired
456
+ rollback restore/refusal, compatible rollback without data replacement, and
457
+ offline password recovery with lock refusal and audit. They do not start
458
+ services, enable units, touch Tailscale, use real credentials, modify developer
459
+ state, or perform npm network installs; fixture dependency trees stand in for
460
+ production `npm ci`.
461
+ Production visual/render verification and integrated runtime tests are separate.
462
+ The opt-in real-service checks in [releasing](releasing.md#verify-the-exact-archive)
463
+ do install disposable systemd user services and production npm dependencies;
464
+ they remove their test units afterward and use separate state and launchers.
465
+
466
+ Pass `--node /absolute/path/to/node` only when the unit must use a Node 26
467
+ executable different from the one running the installer. The path must be an
468
+ absolute regular file; use the release's `node_modules` installed with
469
+ `npm ci --omit=dev`, never a development-only tree.
470
+
471
+ ## Storage budgets and pressure recovery
472
+
473
+ Each Collector Hub connection owns policy filtering, cadence, inventory and its
474
+ durable outbox submission. On restart, recovered metrics are sanitized with the
475
+ current sharing policy before the connection starts; capture-time cadence is not
476
+ reapplied to retained records. Endpoint upgrade recovery and aggregate budgets
477
+ continue to use the existing outboxes. Hub startup restores stored telemetry,
478
+ cadence, inventory and metadata through Sample ingestion while retaining a
479
+ disconnected state until an authenticated Collector reconnects.
480
+
481
+ The default hub main-file plus WAL budget is 2 GiB, shared by every SQLite table.
482
+ The main file has a page-count limit; WAL reserves up to 128 MiB inside that budget.
483
+ Telemetry writes return busy on pressure or SQLITE_FULL, with no receipt/ack for a
484
+ failed transaction. Retention reclaims old raw/one-minute points in bounded chunks
485
+ while preserving the newest hour; hourly rollups retain their configured age window.
486
+ A full newest-hour working set must fit the deployment's budget. Outboxes retry
487
+ while the operator frees space or provisions a larger supported configuration.
488
+
489
+ Collector defaults are 64 MiB logical JSON and 80 MiB physical main+WAL per hub,
490
+ 256 MiB logical and 640 MiB physical across eight destinations. Physical pressure
491
+ evicts only pending metrics, records dropped intervals, and protects in-flight
492
+ records. Failed gap writes are held in bounded memory and retried after recovery;
493
+ an abrupt power loss can still lose that in-memory notice.
494
+
495
+ New databases use incremental page reclamation. Existing databases created without
496
+ auto-vacuum may keep allocated pages after retention deletes rows. Startup reports
497
+ pressure and limits growth; it never runs an unbounded VACUUM. Back up the database,
498
+ stop the hub and inspect file/WAL sizes before offline maintenance. These are
499
+ application/SQLite budgets rather than an operating-system filesystem quota. An
500
+ atomic transaction can briefly increase WAL before its post-commit checkpoint;
501
+ blocked checkpoints leave pressure visible and prevent continued ingestion. Keep
502
+ free disk space for backups, SQLite sidecars and release artifacts.
503
+
504
+ Collector state and enrollment file locks now contain an owner PID and instance.
505
+ A proven-dead owner is recovered with inode checks. Legacy empty `.lock` files have
506
+ no owner proof and intentionally require one manual inspection: verify no collector
507
+ or enrollment writer is running before removing that specific abandoned file.
508
+
509
+
510
+ Generated user units omit `ProtectKernelModules=yes`: on the target user manager,
511
+ that directive fails before Node starts with `218/CAPABILITIES`. Normal user
512
+ privileges and `NoNewPrivileges=yes` already prevent gaining module-loading
513
+ privileges. PrivateTmp, ProtectSystem=strict, explicit writable paths and the other
514
+ validated user-unit hardening remain enabled. Do not copy root-only hardening into
515
+ user units without starting and stopping an isolated unit on that host.
@@ -0,0 +1,152 @@
1
+ # Releasing Rigscope
2
+
3
+ The first public version is `0.1.0`. Source and issue links target
4
+ `https://github.com/Triet1402/rigscope`. Create that repository and push the
5
+ release source before npm publication. The package includes MIT licensing,
6
+ dependency notices, the security policy and [release notes](../CHANGELOG.md).
7
+
8
+ ## Recommended release order
9
+
10
+ 1. Finish the version, public docs, release notes and repository metadata. Commit
11
+ those changes. Keep the installation examples in the package ready for the
12
+ version being published.
13
+ 2. Push the source to GitHub and run the manual CI workflow on that revision.
14
+ This repository deliberately has no push, pull-request or scheduled CI
15
+ triggers. A local result is not a GitHub Actions result.
16
+ 3. Build one npm archive from the final revision. Retain it, its SHA-256 checksum
17
+ and verification results. Test and publish that archive without rebuilding it.
18
+ 4. Run the publish dry run, check the npm account, and publish the verified archive.
19
+ 5. Verify the registry version and install it into a disposable test installation.
20
+ Check sign-in, fresh telemetry and the installed command.
21
+ 6. Tag the exact source revision as `v0.1.0`, push the tag, and create a GitHub
22
+ Release with the release notes, verified archive and checksum. Announce it
23
+ after the registry installation works.
24
+
25
+ For subsequent releases, update the package and lockfile versions together
26
+ (for example, `npm version patch --no-git-tag-version`), update the release notes,
27
+ and repeat the same sequence. A published name/version cannot be overwritten,
28
+ so a correction after publication needs a new version. Prereleases should use
29
+ an explicit tag such as `next`; ordinary `rigscope upgrade` follows `latest`.
30
+
31
+ ## Build the final archive
32
+
33
+ Run from the source checkout with Linux and Node.js 26+:
34
+
35
+ ```sh
36
+ npm ci
37
+ npm run typecheck
38
+ mkdir -p out/release
39
+ npm pack --pack-destination out/release
40
+ ```
41
+
42
+ `npm pack` runs `prepack`, which regenerates dependency notices, builds fresh
43
+ runtime/UI artifacts and verifies them. If notice generation changes a tracked
44
+ file, include that change in the release commit before producing the final
45
+ archive. Reuse the archive for every check below. Replace `0.1.0` in these
46
+ commands when releasing another version.
47
+
48
+ ## Verify the exact archive
49
+
50
+ ```sh
51
+ node scripts/verify-package.cjs ./out/release/rigscope-0.1.0.tgz
52
+ npm run verify:installed -- --archive ./out/release/rigscope-0.1.0.tgz
53
+ npm test
54
+ node scripts/test-runtime.cjs
55
+ node scripts/test.cjs --suite development
56
+ ```
57
+
58
+ Package inspection verifies the built CLI/runtime/UI and excludes source tests,
59
+ Git data, private environment files and caches. Inspect the file list for
60
+ unexpected state, databases, credentials or logs. Installed-package verification
61
+ uses production dependencies in a temporary directory, audits the resolved
62
+ dependency tree and checks the CLI version plus authenticated sign-in/History.
63
+
64
+ The following opt-in checks require a working Linux systemd user manager. They
65
+ create disposable installations with separate state, launchers, ports and unit
66
+ names, then remove their services. They do not select an existing installation.
67
+
68
+ ```sh
69
+ RIGSCOPE_TEST_ARCHIVE="$PWD/out/release/rigscope-0.1.0.tgz" \
70
+ node --import tsx --test tests/cli-install.integration.ts
71
+ node scripts/verify-operations.cjs ./out/release/rigscope-0.1.0.tgz
72
+ ```
73
+
74
+ These cover installation, pairing, independent component controls, bootstrap
75
+ removal, retained-data reinstall, upgrade/rollback, failed-start recovery and
76
+ offline password recovery. Runtime tests additionally cover authenticated HTTPS,
77
+ SSE, collector connections, persistence and reconnect behavior in isolated setups.
78
+
79
+ On the reference host with 48 logical cores, two RTX 3090s and an ASPEED adapter,
80
+ run the hardware-specific test explicitly:
81
+
82
+ ```sh
83
+ RIGSCOPE_REAL_HW=1 node --import tsx --test tests/sampling-real.test.ts
84
+ ```
85
+
86
+ That test reads the actual host and is skipped by default. Its exact inventory
87
+ assertions are specific to that rig; it is not a portable hardware test.
88
+
89
+ Record the final checksum after verification:
90
+
91
+ ```sh
92
+ cd out/release
93
+ sha256sum rigscope-0.1.0.tgz > SHA256SUMS
94
+ sha256sum --check SHA256SUMS
95
+ ```
96
+
97
+ Keep the source revision, archive checksum, check results and any skips together.
98
+ Record boot enablement separately from an actual reboot. Physical laptop testing
99
+ and synthetic scale workloads are separate evidence; see
100
+ [hardware support](installation.md#hardware) and [scale validation](scale-validation.md).
101
+
102
+ ## Publish dry run and publication
103
+
104
+ Run these commands from the source checkout. Sign in interactively if needed,
105
+ then verify the account and preview the exact archive:
106
+
107
+ ```sh
108
+ npm login --registry=https://registry.npmjs.org
109
+ npm whoami --registry=https://registry.npmjs.org
110
+ npm publish ./out/release/rigscope-0.1.0.tgz --access public --tag latest \
111
+ --registry=https://registry.npmjs.org --dry-run
112
+ ```
113
+
114
+ The dry run does not publish, reserve the name, or prove that registry
115
+ authorization and two-factor authentication will succeed. The repository
116
+ metadata does not establish npm package ownership.
117
+
118
+ When releasing, this command actually publishes the verified archive publicly:
119
+
120
+ ```sh
121
+ npm publish ./out/release/rigscope-0.1.0.tgz --access public --tag latest \
122
+ --registry=https://registry.npmjs.org
123
+ npm view rigscope@0.1.0 version dist.integrity --registry=https://registry.npmjs.org
124
+ ```
125
+
126
+ Then use `npx --yes rigscope@0.1.0 install` with a disposable `--state-dir`, an
127
+ unused `--port` and an isolated `XDG_BIN_HOME` for the registry installation
128
+ check. Uninstall that test installation afterward. Use the same isolation
129
+ settings for all test commands so the check does not select a real installation.
130
+
131
+ The public quick start uses `npx --yes rigscope@latest install --boot` and the
132
+ installed `rigscope upgrade`. Archive commands remain available before npm
133
+ publication and for explicit version selection. Users do not need a global npm
134
+ installation of Rigscope.
135
+
136
+ GitHub Actions trusted publishing can automate later releases. Configure it for
137
+ the actual repository and publishing workflow; the current CI workflow only
138
+ verifies and does not publish. See npm's
139
+ [publishing documentation](https://docs.npmjs.com/cli/commands/npm-publish/) and
140
+ [trusted publishing guide](https://docs.npmjs.com/trusted-publishers/).
141
+
142
+ ## Alternative manifest archives
143
+
144
+ `npm run package:release` creates an operations `.tar.gz` with a hashed manifest.
145
+ It is a separate format from the npm `.tgz`. The low-level installer validates
146
+ size/hash and required artifacts before activation. Production dependencies must
147
+ be installed in the staged release; activation never silently runs npm or service
148
+ commands. See [operations](operations.md#low-level-manifest-archive-workflow) and
149
+ [system installation](installation.md#system-installation).
150
+
151
+ Configuration, database schema and wire-protocol revisions are internal
152
+ compatibility checks, independent of the application package version.