rigscope 0.0.0-stage → 0.1.1

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 (224) 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/NE6PGEiJxUnKRceuzUWPc/_buildManifest.js +1 -0
  126. package/.next/static/NE6PGEiJxUnKRceuzUWPc/_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-dc6158e0bb450ef6.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-1475454737279ee0.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-916ee73de5eb067d.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/e45c0c7ef5ba0336.css +1 -0
  156. package/ARCHITECTURE.md +279 -0
  157. package/CHANGELOG.md +37 -0
  158. package/DESIGN.md +299 -0
  159. package/LICENSE +21 -0
  160. package/README.md +74 -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 +5679 -0
  166. package/dist/hub.cjs +8364 -0
  167. package/dist/ops.cjs +4976 -0
  168. package/dist/system.cjs +4333 -0
  169. package/docs/bottlenecks.md +62 -0
  170. package/docs/configuration.md +85 -0
  171. package/docs/development.md +46 -0
  172. package/docs/exports.md +39 -0
  173. package/docs/installation.md +306 -0
  174. package/docs/monitoring.md +112 -0
  175. package/docs/operations.md +550 -0
  176. package/docs/releasing.md +154 -0
  177. package/docs/scale-acceptance.json +412 -0
  178. package/docs/scale-validation.json +1898 -0
  179. package/docs/scale-validation.md +294 -0
  180. package/docs/stress-2026-11-04/180301-probe-m100-u1-s1-20000ms.json +412 -0
  181. package/docs/stress-2026-11-04/180335-probe-m200-u1-s1-20000ms.json +412 -0
  182. package/docs/stress-2026-11-04/180409-probe-m400-u1-s1-20000ms.json +412 -0
  183. package/docs/stress-2026-11-04/180443-probe-m800-u1-s1-20000ms.json +412 -0
  184. package/docs/stress-2026-11-04/180518-probe-m600-u1-s1-20000ms.json +412 -0
  185. package/docs/stress-2026-11-04/180553-probe-m500-u1-s1-20000ms.json +412 -0
  186. package/docs/stress-2026-11-04/180628-probe-m550-u1-s1-20000ms.json +412 -0
  187. package/docs/stress-2026-11-04/180716-probe-m800-u1-s0-20000ms.json +415 -0
  188. package/docs/stress-2026-11-04/180751-probe-m1600-u1-s0-20000ms.json +415 -0
  189. package/docs/stress-2026-11-04/180828-probe-m3200-u1-s0-20000ms.json +416 -0
  190. package/docs/stress-2026-11-04/180941-probe-m2400-u1-s0-20000ms.json +416 -0
  191. package/docs/stress-2026-11-04/181031-probe-m2000-u1-s0-20000ms.json +416 -0
  192. package/docs/stress-2026-11-04/181114-probe-m1800-u1-s0-20000ms.json +416 -0
  193. package/docs/stress-2026-11-04/181203-probe-m100-u64-s64-20000ms.json +418 -0
  194. package/docs/stress-2026-11-04/181304-probe-m200-u64-s64-20000ms.json +418 -0
  195. package/docs/stress-2026-11-04/181405-probe-m400-u64-s64-20000ms.json +418 -0
  196. package/docs/stress-2026-11-04/181507-probe-m500-u64-s64-20000ms.json +418 -0
  197. package/docs/stress-2026-11-04/181609-probe-m450-u64-s64-20000ms.json +418 -0
  198. package/docs/stress-2026-11-04/181845-probe-m2-u2-s2-3000ms.json +419 -0
  199. package/docs/stress-2026-11-04/181931-probe-m500-u64-s64-30000ms.json +420 -0
  200. package/docs/stress-2026-11-04/182108-probe-m1800-u1-s0-120000ms.json +418 -0
  201. package/docs/stress-2026-11-04/182354-probe-m1200-u1-s0-120000ms.json +418 -0
  202. package/docs/stress-2026-11-04/182625-probe-m900-u1-s0-120000ms.json +418 -0
  203. package/docs/stress-2026-11-04/182847-probe-m1050-u1-s0-120000ms.json +418 -0
  204. package/docs/stress-2026-11-04/183112-probe-m1125-u1-s0-120000ms.json +418 -0
  205. package/docs/stress-2026-11-04/193608-probe-m500-u64-s64-120000ms.json +420 -0
  206. package/docs/stress-2026-11-04/193909-probe-m529-u1-s1-15000ms.json +420 -0
  207. package/docs/stress-2026-11-04/193940-probe-m530-u1-s1-15000ms.json +420 -0
  208. package/docs/stress-2026-11-04/194011-probe-m100-u1-s65-10000ms.json +421 -0
  209. package/docs/stress-2026-11-04/README.md +205 -0
  210. package/docs/stress-2026-11-04/index.jsonl +29 -0
  211. package/docs/stress-2026-11-04/manifest.json +13 -0
  212. package/docs/stress-2026-11-04/results.html +22 -0
  213. package/docs/system-installation.md +195 -0
  214. package/next.config.js +38 -0
  215. package/package.json +117 -4
  216. package/packaging/README.md +5 -0
  217. package/packaging/collector.env.example +24 -0
  218. package/packaging/hub.env.example +31 -0
  219. package/packaging/system/90-rigscope-rapl.rules +7 -0
  220. package/packaging/system/README.md +5 -0
  221. package/packaging/system/rigscope-collector.service.example +53 -0
  222. package/packaging/system/rigscope-hub.service.example +50 -0
  223. package/packaging/system/rigscope-rapl.sysusers +5 -0
  224. package/packaging/system/rigscope.sysusers +7 -0
@@ -0,0 +1,550 @@
1
+ # Operations
2
+
3
+ For an npm installation, use the managed commands below. The
4
+ [manifest archive workflow](#low-level-manifest-archive-workflow) is for operators
5
+ building and deploying from source.
6
+
7
+ | Task | Section |
8
+ | --- | --- |
9
+ | Check, upgrade or roll back an npm installation | [Managed CLI operations](#managed-cli-operations) |
10
+ | Reset a lost password | [Password recovery](#password-recovery) |
11
+ | Change localhost/HTTPS access | [Access changes](#access-changes) |
12
+ | Back up or restore a database | [Database commands](#database-commands) |
13
+ | Plan what to back up | [Consistent backup set](#consistent-backup-set) |
14
+ | Handle a full database or queue | [Storage pressure recovery](#storage-budgets-and-pressure-recovery) |
15
+
16
+ ## Managed CLI operations
17
+
18
+ These commands apply to an installation created by `rigscope install`. Its
19
+ state directory defaults to `~/.local/state/rigscope`.
20
+
21
+ ### Check health
22
+
23
+ ```sh
24
+ rigscope status
25
+ rigscope doctor
26
+ ```
27
+
28
+ `status` shows services and the dashboard URL. `doctor` checks configuration,
29
+ runtimes, database integrity, listeners, TLS destinations, disk space and sensor
30
+ access. It follows enabled components: a hub-only installation does not need a
31
+ collector. Checks are read-only and omit credentials. Use `doctor --json` for
32
+ structured output.
33
+
34
+ ### Upgrade
35
+
36
+ ```sh
37
+ rigscope upgrade
38
+ ```
39
+
40
+ Use `--from PACKAGE` for an exact npm version or local archive. The command
41
+ stages the package, stops running components, takes a consistent hub database
42
+ backup and activates the new runtime. It restarts only the components that were
43
+ running, then checks:
44
+
45
+ - the installed and running versions match the new package;
46
+ - restarted services are active;
47
+ - hub health is OK and its database passes integrity/schema checks;
48
+ - a restarted collector produces a fresh sampling marker.
49
+
50
+ The health timeout is 25 seconds; change it with `--timeout SECONDS`. A failed
51
+ start, migration or health check restores the previous runtime. It also restores
52
+ the paired database backup if the old runtime cannot read the new schema,
53
+ preserving the replaced database first.
54
+
55
+ Credentials, collector identities and pending samples are retained.
56
+ `release-state.json` records which backup belongs to each runtime. Upgrading a
57
+ stopped installation leaves it stopped and reports live checks as skipped.
58
+
59
+ ### Roll back
60
+
61
+ ```sh
62
+ rigscope rollback
63
+ ```
64
+
65
+ Rollback uses `runtime-previous` and runs the same health checks. If that runtime
66
+ can read the current database, post-upgrade data stays intact. Otherwise it
67
+ restores the paired pre-upgrade backup and saves the replaced database.
68
+
69
+ A missing compatible backup stops rollback. Older installations without a
70
+ recorded backup can supply a compatible copy with `--backup PATH`. Never point
71
+ old code at a newer database schema.
72
+
73
+ ## Password recovery
74
+
75
+ Stop the installation, enter a new password at the private prompt, then restart:
76
+
77
+ ```sh
78
+ rigscope stop
79
+ rigscope recover-password --user owner
80
+ rigscope start
81
+ ```
82
+
83
+ Recovery requires the hub to be stopped and refuses a live or uncertain database
84
+ lock. For unattended recovery, use `--password-file` with an owned `0600` file.
85
+ It updates the existing user's stored hash. `--enable` also clears that user's
86
+ disabled flag; recovery never creates a user or grants administrator access.
87
+
88
+ The operation records an audit event and invalidates sessions. It keeps
89
+ configuration, history and collector credentials. Sign in again after restart;
90
+ editing bootstrap hashes in `config.json` or `HUB_BROWSER_USERS` does not reset a
91
+ managed account.
92
+
93
+ ## Access changes
94
+
95
+ Follow the commands in [Remote access](installation.md#access-from-another-computer).
96
+ `configure` requires a stopped hub and, when changing its local collector's
97
+ destination, a stopped collector.
98
+
99
+ It validates the HTTPS origin and private socket, backs up the old settings, and
100
+ updates the matching local collector destination. It moves that destination's
101
+ whole queue directory, including SQLite WAL files. Credentials and other hubs
102
+ stay unchanged. An existing target queue stops the conversion to avoid merging
103
+ or discarding pending records. Accounts and the hub history database are retained.
104
+ Rollback targets must support the saved configuration and database schema.
105
+
106
+ ## Low-level manifest archive workflow
107
+
108
+ The remaining archive deployment examples assume a source checkout with built
109
+ artifacts. They use a manifest `.tar.gz`, which is different from the npm `.tgz`
110
+ used by `rigscope install` and `rigscope upgrade`. For ordinary installations,
111
+ follow [installation](installation.md) and the managed commands above. The npm
112
+ package excludes source helper scripts and does not require users to build it.
113
+
114
+ The built deployment runs `dist/hub.cjs` for the dashboard and collector
115
+ connections, and `dist/collector.cjs` for collection. `dist/ops.cjs` provides the
116
+ database and archive tools below. The hub serves the production Next.js output
117
+ from `.next`; it does not use `next start` or standalone output.
118
+
119
+ Choose a prefix and state directory owned by the service user. All paths below
120
+ are examples; generated units support paths containing spaces.
121
+
122
+ ### Build and package
123
+
124
+ ```sh
125
+ npm ci
126
+ npm run build:ui # production .next
127
+ npm run build:runtime # dist/hub.cjs, dist/collector.cjs, dist/ops.cjs
128
+ node scripts/package-release.cjs --output /var/tmp/rigscope-release.tar.gz
129
+ ```
130
+
131
+ The archive contains the built runtime, `.next` without its cache, package and
132
+ lock files, the repository's `next.config.js`, `tsconfig.json`,
133
+ operational docs and the `packaging/` examples. It deliberately excludes source, tests, git data,
134
+ `node_modules`, private environment files, SQLite databases, keys and logs.
135
+ `manifest.json` lists every packaged file with size and SHA-256; the installer
136
+ re-verifies it before activation.
137
+
138
+ Production dependencies are installed inside each staged release:
139
+
140
+ ```sh
141
+ cd /home/operator/.local/opt/rigscope/releases/<version>
142
+ npm ci --omit=dev
143
+ ```
144
+
145
+ `install --install-deps` can run that command explicitly, but the installer
146
+ never runs npm implicitly for activation. If a package mirror is unavailable,
147
+ stage without activation and install from an approved mirror; do not weaken npm
148
+ security policy just to make installation pass. Isolated tests use a synthetic
149
+ dependency tree and do not perform remote installs.
150
+
151
+ ## Stage and activate
152
+
153
+ ```sh
154
+ node scripts/install-release.cjs \
155
+ --source /var/tmp/rigscope-release.tar.gz \
156
+ --prefix /home/operator/.local/opt/rigscope \
157
+ --state-dir /home/operator/.local/state/rigscope \
158
+ --units-dir /home/operator/.config/systemd/user
159
+ ```
160
+
161
+ The installer creates:
162
+
163
+ ```text
164
+ <prefix>/
165
+ releases/<version>/ immutable built release + manifest
166
+ releases/<version>/node_modules/
167
+ current -> releases/<version>
168
+ previous -> releases/<previous> (retained for rollback)
169
+ <state>/
170
+ hub/telemetry.sqlite hub database (0700 dir, 0600 file)
171
+ hub.env private EnvironmentFile (0600, no secrets printed)
172
+ collector.env private EnvironmentFile (0600)
173
+ collector/collector.json collector identity/enrollment (created by collector)
174
+ outbox/<sha256(url)[0:16]>/ per-destination collector outboxes
175
+ backups/ private pre-upgrade/pre-activation database copies
176
+ install-state.json versions, schema backups, no credentials
177
+ run/ private runtime directory for a Unix socket
178
+ <units-dir>/
179
+ rigscope-hub.service
180
+ rigscope-collector.service
181
+ ```
182
+
183
+ Staging does not switch `current` or start anything. After production
184
+ dependencies are installed and the old hub is stopped, activate explicitly:
185
+
186
+ ```sh
187
+ node scripts/install-release.cjs activate \
188
+ --prefix /home/operator/.local/opt/rigscope \
189
+ --state-dir /home/operator/.local/state/rigscope \
190
+ --version <version> --confirm-stopped
191
+ ```
192
+
193
+ Activation order is fixed:
194
+
195
+ 1. Validate the release manifest, hashes and production dependencies.
196
+ 2. Verify no hub lock exists and the operator confirmed the hub is stopped.
197
+ 3. Inspect the current database schema.
198
+ 4. If migration is needed: write a consistent online backup, then migrate, then
199
+ verify integrity and the target schema. A failed migration restores the
200
+ pre-migration backup and leaves `current` unchanged.
201
+ 5. Atomically switch `current`, retaining `previous` and a schema-backup record.
202
+ 6. Print (never execute) systemd commands.
203
+
204
+ If validation or migration fails, a good database is never silently replaced.
205
+ The pre-migration backup path is printed. First installation with no database
206
+ creates and migrates a new private database.
207
+
208
+ ## systemd user units
209
+
210
+ The generated units use an absolute Node 26 executable, absolute paths,
211
+ `WorkingDirectory=<prefix>/current`, a private `EnvironmentFile`, journald
212
+ logging, `Restart=on-failure`, `NoNewPrivileges=yes`, `PrivateTmp=yes`,
213
+ `UMask=0077`, `ProtectSystem=strict`, and explicit writable state/database/cache paths.
214
+
215
+ Startup is explicit and is not performed by the installer:
216
+
217
+ ```sh
218
+ systemctl --user daemon-reload
219
+ systemctl --user enable --now rigscope-hub.service rigscope-collector.service
220
+ ```
221
+
222
+ Automatic startup after logout/host reboot requires an explicit administrator
223
+ choice to enable user lingering for that account:
224
+
225
+ ```sh
226
+ sudo loginctl enable-linger operator
227
+ ```
228
+
229
+ Review the generated units and private env files first. Health and observation:
230
+
231
+ ```sh
232
+ curl --fail --silent http://127.0.0.1:8081/api/health
233
+ systemctl --user status rigscope-hub.service rigscope-collector.service
234
+ journalctl --user -u rigscope-hub.service -f
235
+ journalctl --user -u rigscope-collector.service -f
236
+ ```
237
+
238
+ Shutdown is bounded: the hub targets five seconds and the collector three
239
+ seconds; the generated `TimeoutStopSec` leaves a ten- and seven-second slack
240
+ respectively. Keep those times at or above the process deadlines.
241
+
242
+ ## Configuration
243
+
244
+ `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.
245
+
246
+ The generated `hub.env` and `collector.env` contain only comments. Empty
247
+ configuration fails closed: no `HUB_BROWSER_USERS` means browser data is
248
+ unavailable, and no collector credentials/destinations means no fleet data.
249
+ Never copy the public demo credentials from the examples to a real host.
250
+
251
+ Loopback development:
252
+
253
+ ```sh
254
+ HUB_HOST=127.0.0.1
255
+ HUB_PORT=8081
256
+ HUB_DATABASE_PATH=/home/operator/.local/state/rigscope/hub/telemetry.sqlite
257
+ ```
258
+
259
+ Browser users and collectors belong in the private environment file. Generate
260
+ browser password hashes locally with `scripts/hash-browser-password.cjs`; never
261
+ store plaintext passwords or hashes in shared logs.
262
+
263
+ Remote mode uses a private Unix socket behind an external TLS terminator:
264
+
265
+ ```sh
266
+ HUB_PUBLIC_ORIGIN=https://monitor.example.invalid
267
+ HUB_SOCKET_PATH=/home/operator/.local/state/rigscope/run/hub.sock
268
+ ```
269
+
270
+ The parent directory must be private and owned by the service user. The
271
+ installer's state `run/` directory is already writable by the unit. Remote
272
+ health checks go through the socket with the exact Host:
273
+
274
+ ```sh
275
+ curl --fail --silent --unix-socket <socket> \
276
+ -H 'Host: monitor.example.invalid' http://localhost/api/health
277
+ ```
278
+
279
+ This example hostname and every `demo-token-*` value are public fixtures, not a
280
+ deployment. See `development.md` for the exact origin/Host/cookie
281
+ boundary. Never expose the loopback HTTP listener remotely without the private
282
+ socket/TLS boundary and authentication.
283
+
284
+ Collector enrollment remains separate from browser sessions:
285
+
286
+ ```sh
287
+ COLLECTOR_ENROLL_URL=ws://127.0.0.1:8081/api/collector/connect
288
+ COLLECTOR_ENROLL_TOKEN=<one-use grant>
289
+ COLLECTOR_MODE=real
290
+ COLLECTOR_STATE_FILE=/home/operator/.local/state/rigscope/collector/collector.json
291
+ ```
292
+
293
+ Remove the one-use grant after enrollment and start or restart the collector. The
294
+ hub does not need a restart. Enrollment state
295
+ and outboxes live outside the repository.
296
+
297
+ For an isolated local check without touching the real deployment, the public demo
298
+ grant from the packaging examples can drive a loopback enrollment and collection
299
+ run. These are deliberate demo strings only, never a real deployment:
300
+
301
+ ```sh
302
+ HUB_STATE_FILE="$PWD/.rigscope-local/hub/enrollment.json" \
303
+ HUB_ENROLLMENT_GRANTS="$(node -e 'console.log(JSON.stringify([{token:"demo-token-enrollment-local",expiresAt:new Date(Date.now()+300000).toISOString()}]))')" \
304
+ npm start
305
+
306
+ # In another terminal:
307
+ COLLECTOR_STATE_FILE="$PWD/.rigscope-local/collector/state.json" \
308
+ COLLECTOR_ENROLL_URL='ws://127.0.0.1:8081/api/collector/connect' \
309
+ COLLECTOR_ENROLL_TOKEN='demo-token-enrollment-local' \
310
+ npm run collector:enroll
311
+
312
+ # Start real local collection with the saved credential:
313
+ COLLECTOR_STATE_FILE="$PWD/.rigscope-local/collector/state.json" npm run collector:start
314
+ ```
315
+
316
+ Enrollment posts `{collectorId}` to `/api/collector/enroll`; the bearer grant travels
317
+ only in the Authorization header. Grants must expire within fifteen minutes and are
318
+ consumed across restarts. Plaintext is allowed only for literal loopback addresses;
319
+ WSS enrollment uses HTTPS with ordinary certificate validation.
320
+
321
+ ## Database commands
322
+
323
+ All commands take explicit paths. They print no credentials, user hashes,
324
+ webhook URLs, tokens or record payloads.
325
+
326
+ ```sh
327
+ # Read-only schema/integrity/size/table summary.
328
+ node dist/ops.cjs inspect --database /path/telemetry.sqlite
329
+
330
+ # Consistent single-file online backup. Does NOT migrate the source.
331
+ node dist/ops.cjs backup \
332
+ --database /path/telemetry.sqlite \
333
+ --output /path/private-backups/telemetry-$(date +%Y%m%dT%H%M%S).sqlite
334
+
335
+ # Offline guarded migration. Existing databases require --confirm-stopped.
336
+ node dist/ops.cjs migrate \
337
+ --database /path/telemetry.sqlite \
338
+ --backup-dir /path/private-backups --confirm-stopped
339
+
340
+ # Guarded restore over an existing database: both flags are required.
341
+ node dist/ops.cjs restore \
342
+ --input /path/private-backups/telemetry-....sqlite \
343
+ --database /path/telemetry.sqlite \
344
+ --replace --confirm-stopped
345
+
346
+ # Offline managed-user recovery; password comes from a private 0600 file.
347
+ node dist/ops.cjs recover-admin \
348
+ --database /path/telemetry.sqlite \
349
+ --user owner --password-file /path/private-recovery-password --enable
350
+ ```
351
+
352
+ Safety rules enforced by `backup`/`restore`:
353
+
354
+ - The hub database is one SQLite file. The online backup copies all tables in
355
+ that file: telemetry, history rollups, receipts/gaps, machine latest and
356
+ inventory, the collector registry/grants/audit, admin users/audit, and alert
357
+ rules/incidents/jobs/webhook state. There is no partial table selection.
358
+ - Backup opens the source read-only, runs `integrity_check`, stages the copy,
359
+ converts it to a self-contained DELETE-journal file, verifies the staged copy,
360
+ sets mode 0600 and atomically renames it. The source schema is never migrated.
361
+ - Restore validates the input integrity and migration ledger, refuses a schema
362
+ newer than this operations build, refuses to overwrite the target without
363
+ `--replace --confirm-stopped`, and refuses if a hub lock is present.
364
+ - It stages and verifies a copy first. For an existing target it checkpoints the
365
+ old database, takes a pre-restore backup, then removes only the old WAL/SHM
366
+ sidecars after proof that the hub is stopped, and atomically replaces the
367
+ main file. A failed validation never overwrites the target.
368
+ - The hub process lease is `<databasePath>.hub.lock` (JSON `{pid,instance}`).
369
+ Operations refuses while it exists, even if the PID looks stale, because only
370
+ the hub's own startup proof may recover a proven-dead lease. The hub removes
371
+ it on clean shutdown/exit; operators must inspect a lock and never delete it
372
+ blindly.
373
+ - Restoring an older-schema backup is allowed; the matching release migrates it
374
+ on the next hub start. Never start an old release against a newer schema.
375
+
376
+ Schema versions are derived from the build's migration ledger. The current
377
+ ledger includes telemetry, latest-session identity, admin, alerts and collector
378
+ registry migrations. A newer database must be opened by newer code; an old
379
+ release must never be pointed at a newer schema. `inspect` reports this state.
380
+
381
+ ## Consistent backup set
382
+
383
+ The database backup is not the whole installation. A restorable set is:
384
+
385
+ 1. Hub SQLite database: `telemetry.sqlite` (includes registry/admin/alerts).
386
+ Use `ops backup`; do not copy the main file alone while the hub is running.
387
+ 2. Enrollment/config state when configured: `HUB_STATE_FILE` (for
388
+ example `hub/enrollment.json`, the import source for previously enrolled
389
+ collectors) and the private hub environment file. These
390
+ contain credentials: protect and copy them only between private locations.
391
+ 3. Collector identity/enrollment: `collector/collector.json`.
392
+ 4. Collector outboxes: `outbox/<destination-hash>/outbox.sqlite` per hub URL,
393
+ which may contain unacknowledged telemetry. Replay is at-least-once; the hub
394
+ deduplicates by collector/run/sequence.
395
+ 5. Optional operational metadata: `install-state.json`, units and the built
396
+ release. Keep the release matching the database schema for rollback.
397
+
398
+ For a consistent set, prefer stopping both services cleanly, then run the online
399
+ database backup and copy the other private files. For an online backup, the SQLite
400
+ copy is internally consistent, but JSON identity/outbox files should be copied in
401
+ a quiescent moment; outbox copies may lag and replay duplicates are harmless.
402
+
403
+ Restore the database with `ops restore`; copy non-database state back separately
404
+ with the same ownership and modes. Never silently copy another installation's private keys, browser credentials or unrelated files.
405
+
406
+ ## Upgrade and rollback
407
+
408
+ For an upgrade:
409
+
410
+ 1. Package and stage the new release; install `npm ci --omit=dev` there.
411
+ 2. Stop the old hub/collector and verify the lock is gone.
412
+ 3. Run `install ... --activate --confirm-stopped` (or `activate`).
413
+ 4. The installer backs up, migrates and verifies before switching `current`.
414
+ 5. Start/enable services explicitly and check health. If the services were
415
+ already installed:
416
+ `systemctl --user restart rigscope-hub.service rigscope-collector.service`.
417
+
418
+ Rollback:
419
+
420
+ ```sh
421
+ node dist/ops.cjs rollback \
422
+ --prefix /home/operator/.local/opt/rigscope \
423
+ --state-dir /home/operator/.local/state/rigscope \
424
+ --confirm-stopped
425
+ ```
426
+
427
+ Rollback is refused while the hub lock exists. If the database schema is newer
428
+ than the previous release's manifest, rollback restores the recorded matching
429
+ pre-upgrade backup (verified against that release's schema) before switching
430
+ `current`. If no matching backup exists, it refuses rather than claiming an old
431
+ binary can read a newer schema. The replaced database is itself backed up first.
432
+
433
+ ### Upgrade ordering
434
+
435
+ Within a user installation or this manifest archive layout, hub and collector
436
+ share one release: upgrade the installation together. Stop the hub and
437
+ collector, activate the new release (which migrates and verifies the database),
438
+ then start both services from the same release. Avoid running an old collector
439
+ against a newer hub or vice versa for longer than the restart itself. Do not roll
440
+ a hub back below the schema its database already uses; `rollback` enforces that
441
+ boundary, and collector rollback keeps its private state and outboxes. Process
442
+ detail remains request-scoped and is never persisted or included in the backup
443
+ set. Separate machines and hubs are upgraded independently. System installations
444
+ use the component controls in [System installation](system-installation.md#system-component-lifecycle).
445
+
446
+ On upgrade from the previous collector endpoint, saved `/api/collector/v1/connect`
447
+ URLs normalize to `/api/collector/connect` without changing identity, credentials,
448
+ sharing policy or pending enrollment recovery proofs. The collector recovers the
449
+ old URL's outbox before connecting. If both queue directories exist, it imports
450
+ missing record identities in a transaction before removing the old directory.
451
+ Stored metrics and gaps with the previous `protocolVersion: 2` envelope are
452
+ decoded into the current wire format at the outbox boundary; record identities
453
+ and previously offered gap payloads are preserved. Wire parsers remain strict.
454
+
455
+ ## Measurement
456
+
457
+ `ops measure` is a bounded synthetic runner, not a capacity guarantee:
458
+
459
+ ```sh
460
+ node dist/ops.cjs measure --machines 2 --batches 30 --records-per-batch 5 \
461
+ --gpus 2 --viewers 2 --json
462
+ ```
463
+
464
+ It writes synthetic records through the real `TelemetryStore` into a
465
+ temporary private database and reports accepted/rejected/busy counts, ingestion
466
+ latency, event-loop delay/utilization, RSS, database/WAL size, store queue and
467
+ flush counters, and one history-query latency. The output is labelled synthetic.
468
+ Defaults are intentionally small; upper bounds are 1,000 machines, 2,000 batches,
469
+ 32 records per batch and 100,000 records total. It never opens a service socket
470
+ or contacts a hub. `--keep` retains the temporary directory for inspection. The
471
+ library accepts `historyConcurrency` for overlapping History request rounds;
472
+ the CLI exposes it as `--history-concurrency N` (default 1).
473
+
474
+ Current integration notes: storage commits batches of at most 32 records and the
475
+ history query has a 25,000-row/100 ms budget; a 512-record synthetic benchmark
476
+ showed roughly 407 ms transactions. Measure on the actual deployment before
477
+ choosing cadence, retention or viewer counts. No latency or capacity guarantee
478
+ is implied by these development numbers.
479
+
480
+ For sustained concurrent viewers, collector reconnects/replay, slow SSE
481
+ consumers and a fleet beyond 50 machines, use the isolated harness described in
482
+ [scale-validation.md](scale-validation.md). It spawns a temporary loopback hub
483
+ and never contacts the live service.
484
+
485
+ ## Verification limits
486
+
487
+ Default repository tests exercise backup/restore, newer-schema refusal, corrupted input,
488
+ lock refusal, installer staging/activation/rollback and failed-upgrade recovery
489
+ in isolated temporary directories. CLI fixture tests additionally cover doctor
490
+ redaction and role separation, inactive and live upgrade validation, paired
491
+ rollback restore/refusal, compatible rollback without data replacement, and
492
+ offline password recovery with lock refusal and audit. They do not start
493
+ services, enable units, touch Tailscale, use real credentials, modify developer
494
+ state, or perform npm network installs; fixture dependency trees stand in for
495
+ production `npm ci`.
496
+ Production visual/render verification and integrated runtime tests are separate.
497
+ The opt-in real-service checks in [releasing](releasing.md#verify-the-exact-archive)
498
+ do install disposable systemd user services and production npm dependencies;
499
+ they remove their test units afterward and use separate state and launchers.
500
+
501
+ Pass `--node /absolute/path/to/node` only when the unit must use a Node 26
502
+ executable different from the one running the installer. The path must be an
503
+ absolute regular file; use the release's `node_modules` installed with
504
+ `npm ci --omit=dev`, never a development-only tree.
505
+
506
+ ## Storage budgets and pressure recovery
507
+
508
+ Each Collector Hub connection owns policy filtering, cadence, inventory and its
509
+ durable outbox submission. On restart, recovered metrics are sanitized with the
510
+ current sharing policy before the connection starts; capture-time cadence is not
511
+ reapplied to retained records. Endpoint upgrade recovery and aggregate budgets
512
+ continue to use the existing outboxes. Hub startup restores stored telemetry,
513
+ cadence, inventory and metadata through Sample ingestion while retaining a
514
+ disconnected state until an authenticated Collector reconnects.
515
+
516
+ The default hub main-file plus WAL budget is 2 GiB, shared by every SQLite table.
517
+ The main file has a page-count limit; WAL reserves up to 128 MiB inside that budget.
518
+ Telemetry writes return busy on pressure or SQLITE_FULL, with no receipt/ack for a
519
+ failed transaction. Retention reclaims old raw/one-minute points in bounded chunks
520
+ while preserving the newest hour; hourly rollups retain their configured age window.
521
+ A full newest-hour working set must fit the deployment's budget. Outboxes retry
522
+ while the operator frees space or provisions a larger supported configuration.
523
+
524
+ Collector defaults are 64 MiB logical JSON and 80 MiB physical main+WAL per hub,
525
+ 256 MiB logical and 640 MiB physical across eight destinations. Physical pressure
526
+ evicts only pending metrics, records dropped intervals, and protects in-flight
527
+ records. Failed gap writes are held in bounded memory and retried after recovery;
528
+ an abrupt power loss can still lose that in-memory notice.
529
+
530
+ New databases use incremental page reclamation. Existing databases created without
531
+ auto-vacuum may keep allocated pages after retention deletes rows. Startup reports
532
+ pressure and limits growth; it never runs an unbounded VACUUM. Back up the database,
533
+ stop the hub and inspect file/WAL sizes before offline maintenance. These are
534
+ application/SQLite budgets rather than an operating-system filesystem quota. An
535
+ atomic transaction can briefly increase WAL before its post-commit checkpoint;
536
+ blocked checkpoints leave pressure visible and prevent continued ingestion. Keep
537
+ free disk space for backups, SQLite sidecars and release artifacts.
538
+
539
+ Collector state and enrollment file locks now contain an owner PID and instance.
540
+ A proven-dead owner is recovered with inode checks. Legacy empty `.lock` files have
541
+ no owner proof and intentionally require one manual inspection: verify no collector
542
+ or enrollment writer is running before removing that specific abandoned file.
543
+
544
+
545
+ Generated user units omit `ProtectKernelModules=yes`: on the target user manager,
546
+ that directive fails before Node starts with `218/CAPABILITIES`. Normal user
547
+ privileges and `NoNewPrivileges=yes` already prevent gaining module-loading
548
+ privileges. PrivateTmp, ProtectSystem=strict, explicit writable paths and the other
549
+ validated user-unit hardening remain enabled. Do not copy root-only hardening into
550
+ user units without starting and stopping an isolated unit on that host.