System installation
System installation runs Rigscope under dedicated service accounts and starts at boot without user lingering. For the default per-user npm setup, use the installation guide.
Review the plan before applying changes. Commands that change the installation
require root and --apply; otherwise they only print the proposed changes.
The installed rigscope system command and dist/system.cjs use the same code.
| Task | Section |
|---|---|
| Review paths and permissions | Generated layout |
| Install or control services | Component lifecycle |
| Enroll with another hub | Connect to a hub |
| Grant package-energy access | CPU package power |
Scope and generated layout
/opt/rigscope/ root:root 0755
releases/<version>/ immutable root-owned release
current -> releases/<version>
previous -> releases/<previous> retained for rollback
node/node root-owned Node 26 when the selected binary was not root-owned
/etc/rigscope/
hub.env root:root 0600 (credentials, never printed)
collector.env root:root 0600
/var/lib/rigscope/
hub/telemetry.sqlite rigscope-hub 0600, private 0700 directory
hub/enrollment.json rigscope-hub 0600
hub/run/hub.sock private Unix socket (remote mode)
collector/state.json rigscope-collector 0600
collector/outbox/ durable collector outboxes
backups/ root:root 0700, pre-upgrade/pre-rollback SQLite copies
system-install.json root:root 0600, canonical component state/versions/backups/adoption
power-access.json root:root 0600, original counter modes for reversal
/etc/systemd/system/rigscope-hub.service root:root 0644, hub boot unit
/etc/systemd/system/rigscope-collector.service root:root 0644, collector boot unit
/etc/sysusers.d/rigscope.conf root:root 0644, both nologin accounts
/etc/sysusers.d/rigscope-rapl.conf root:root 0644, collector-only power membership
/etc/udev/rules.d/90-rigscope-rapl.rules root:root 0644Build the checkout before using dist/system.cjs; an installed package exposes the same commands through rigscope system. Reference copies are in packaging/system/. They are for review only; the
renderer in src/cli/system.ts is authoritative.
systemd-sysusers creates rigscope-hub and rigscope-collector as nologin
system accounts even when one component is disabled, so the other can be
enabled later without reinstalling. The hub and collector never run as root.
The units set NoNewPrivileges=yes, ProtectSystem=strict,
ProtectHome=yes, an empty CapabilityBoundingSet=/AmbientCapabilities=,
UMask=0077, explicit writable state paths and a read-only release tree. The
collector uses --await-connection to exit cleanly when no destinations are
configured, including after an interrupted enrollment. The
collector does not use PrivateDevices=yes because GPU telemetry still
needs real device nodes. The hub does.
System component lifecycle
All mutating commands require --apply and root. Without --apply, the exact
files and command sequence are printed and nothing is written. Every component
operation touches only its own unit; enabling or starting one component never
stops or restarts the other.
# Package first (from a built checkout).
node scripts/package-release.cjs --output /var/tmp/rigscope-<version>.tar.gz
# Review a release before granting sudo.
node dist/system.cjs plan \
--source /var/tmp/rigscope-<version>.tar.gz --role local --node /usr/bin/node
# Install one complete package. --role selects the initial enabled preset.
sudo node dist/system.cjs install \
--source /var/tmp/rigscope-<version>.tar.gz --role hub \
--node /usr/bin/node --install-deps --enable --apply
# Add the other component later; no reinstall and no new package are needed.
sudo node dist/system.cjs enable collector --apply
sudo node dist/system.cjs enable hub --apply
# Component-specific lifecycle when both exist.
sudo node dist/system.cjs disable collector --apply
sudo node dist/system.cjs restart hub --apply
sudo node dist/system.cjs restart collector --apply
sudo node dist/system.cjs restart --apply # enabled components only
# Component-specific status and logs.
node dist/system.cjs status
node dist/system.cjs status collector --json
node dist/system.cjs logs hub --followinstall stages a complete release, verifies both runtimes and UI artifacts,
copies a non-root-owned Node executable into the root-owned prefix, installs
production dependencies when --install-deps is given, writes private
configuration and both units, creates both accounts, then switches current.
Staging happens before any service is stopped. The state file has no format
version; its required components object records hub and collector enablement.
A pre-canonical system installation is reset, not migrated. system install and
system upgrade --apply stage and validate the replacement release, stop both
units completely, then remove the old configuration and history and install
the canonical layout fresh. Back up /var/lib/rigscope first if that history
must be kept; there is no rollback to the old format. Later upgrades that stay
canonical keep accounts, history, settings, collector identities and pending
samples, and rollback keeps its paired database backup.
- With
--enable,--role localinitially enables both components. - With
--enable,--role hubinitially enables only the hub. - With
--enable,--role collectorinitially enables only the collector. - Without
--enable, both components are installed but disabled. --startstarts only the requested preset and verifies those components.--without-powerskips the collector RAPL grant at that mutation.
enable hub creates the owner account once when HUB_BROWSER_USERS is empty
and prints the password only in that mutation. It is never stored in plaintext
or printed again. enable collector completes the collector account, unit,
identity and RAPL grant. With no destination it tries the colocated hub:
- A live configured hub gets a short-lived, single-use grant in its authoritative SQLite registry. The collector then enrolls through the normal hub API, which updates the live registry without stopping or restarting the hub.
- A stopped hub gets the identity written to the authoritative registry (or a
static
HUB_COLLECTORSbootstrap when the registry is not initialized yet); enabling the collector never starts the hub. - If there is no configured hub, the collector is enabled but stays dormant and
reports
Awaiting connection. Its unit uses--await-connectionto exit cleanly before collection until a destination is saved.
Configured remote destinations and credentials are never overwritten. Re-running
enable collector leaves its destination, token, identity and outboxes intact.
Connecting to an independent hub
For a hub-first test where the hub and collector are separate installations, create a connection grant in the hub's admin Collectors panel. Save the grant to a private file and connect the system collector:
# On the collector host; the grant file must be root-owned and 0600.
sudo install -m 0600 /dev/null /root/collector-grant
sudoedit /root/collector-grant # paste the single-use grant, save, quit
sudo node dist/system.cjs connect https://<hub-host> \
--token-file /root/collector-grant --apply
sudo rm /root/collector-grantconnect stops only the collector while it enrolls, calls the hub's normal
enrollment API, persists the issued credential under
/var/lib/rigscope/collector/state.json, and starts the collector if it is
enabled. The grant is never printed. Re-running connect on a configured
collector is refused rather than replacing a remote destination.
Status reports each component independently:
Components:
hub: installed, enabled, active, ready
collector: installed, enabled, active, ready, 1 destination(s)readiness is ready, stopped, unconfigured, awaiting-connection,
invalid-config, invalid-state or missing.
CPU package power
collector/sampling/hwmon.ts already reads top-level RAPL package counters,
sums multiple sockets, handles wrap/reset and returns unavailable rather than
zero. On hosts where energy_uj is root-only, system scope grants a narrow
read path instead of elevating Node.
node dist/system.cjs power status
sudo node dist/system.cjs power grant --apply
sudo node dist/system.cjs power revoke --applyMechanism:
/etc/sysusers.d/rigscope-rapl.confcreatesrigscope-rapland adds onlyrigscope-collector; the hub is not a member.- A
powercapudev rule matches only top-levelintel-rapl:Ndevices namedpackage*and changes onlyenergy_ujandmax_energy_range_ujto0440 root:rigscope-rapl. Subdomains, constraint files and power-limit writes are untouched. udevadm trigger --subsystem-match=powercap --action=changeapplies the rule immediately. Because the rule lives in/etc/udev/rules.d, device recreation and boot reapply it.- The collector unit adds
SupplementaryGroups=rigscope-rapl; it does not use file capabilities, ambient capabilities or a privileged Node helper. - Grant records each existing counter's original mode/uid/gid in
/var/lib/rigscope/power-access.json. Revoke removes the rule and group membership and restores those recorded values (defaults0400forenergy_ujand0444formax_energy_range_ujwhen no record exists). power statusreports driver modules, package domains, collector readability, persistence, world readability and every problem. A missing counter is reported unavailable, never as zero watts.
Verify real watts in History after granting access and restarting the collector;
do not treat a mocked value or a one-time manual chmod as installation
evidence. Unsupported hardware remains unavailable.