Skip to content

Configuration

Variable Default Purpose
JWT_SECRET insecure dev default Signing key for auth tokens. Must be set in production; the install script generates one.
HSI_SECRETS_KEY none 64 hex characters. Encrypts notification connector secrets (webhook URLs and headers, SMTP passwords). Generated by the install script and never rotated automatically: losing it means re-entering those secrets. Keep it with your configuration backups.
NATS_URL - NATS server URL for talking to the root-worker.
NATS_USER / NATS_PASS - NATS credentials.
BACKEND_PORT 9001 TCP port the backend listens on. The installer passes it to systemd. HTTPS is served on this port.
INSTALL_DIR - Installation root; anchors runtime paths (e.g. the bundled server.js).
DASHBOARD_PATH - Path to the built dashboard the backend serves. In dev, point it at apps/dashboard/dist.
WALLPAPER_DIR under INSTALL_DIR Where uploaded desktop wallpapers are stored.
HSI_TLS_DIR /var/lib/hsi/tls Directory of the HTTPS files (see TLS files).
NODE_ENV - Standard Node environment flag.
HSI_TELEMETRY_ENABLED true Set to false, 0, off or no to disable anonymous daily telemetry. Takes precedence over the switch in Settings > Telemetry.
HSI_TELEMETRY_CHANNEL detected Release channel reported with each heartbeat: stable, beta or dev. Detected by default: dev outside production (NODE_ENV not production, or a checkout without a version), beta for pre-release versions such as -rc.1, stable otherwise. The telemetry service does not record dev heartbeats.
HSI_TELEMETRY_URL https://hsi-telemetry.theo-labs.dev/v1/heartbeat HTTPS heartbeat endpoint used by the best-effort telemetry client.
HSI_VERSION package version Optional explicit version reported by packaged deployments.

If JWT_SECRET is unset the backend logs a warning and uses an insecure default; never do this in production.

Telemetry sends only the anonymous hardware and version fields documented by the telemetry service. Failures time out after three seconds and never block startup or normal HSI features. The random installation UUID and last-success timestamp are stored under INSTALL_DIR/data and survive upgrades.

The installer (scripts/install.sh, run via the README one-liner) accepts these environment variables:

Variable Default Purpose
VERSION latest release Install/pin a specific tag, e.g. VERSION=v1.28.1.
INSTALL_DIR /opt/hsi Installation directory.
APP_USER hsi System user the backend runs as.
BACKEND_PORT 9001 API port.
NATS_SERVER_VERSION v2.10.24 NATS binary version to download.
SKIP_NGINX 0 Skip the nginx site of older installations that still have one.
SKIP_SEED 0 Skip the database seed and the setup link.
HSI_LOG_LEVEL info Root worker log level (debug, info, warn, error), kept in /etc/hsi/worker.env across updates.
SKIP_DEPS_INSTALL 0 Only check host packages; fail instead of installing missing ones with apt-get.
HSI_ALLOW_UNSIGNED 0 Same as --allow-unsigned: install a release published before signing, with no signature check.

Re-running the installer detects an existing installation, preserves the database and all secrets, and restarts the services.

HTTPS files live in /var/lib/hsi/tls/ (override with HSI_TLS_DIR). See Secure access.

File Mode Content
ca.key 0600 root Private key of the local authority
ca.crt 0644 Local authority certificate (public)
server.crt 0644 Server certificate (local or imported)
server.key 0640 root:hsi Server private key
tls.json 0644 Mode, port, standard ports, trusted proxies, names the certificate was issued for

The installer checks these on every install and update. Missing required packages are installed with apt-get (set SKIP_DEPS_INSTALL=1 to only check them).

Package Needed for
curl, openssl Downloading releases, generating secrets
rsync, openssh-client Backup jobs
util-linux (runuser, lsblk, blkid) Running steps as the app user, disk inventory
mdadm RAID arrays
smartmontools S.M.A.R.T. health
lvm2 LVM volumes
parted, e2fsprogs, udev Partitioning and formatting

Optional packages enable a feature and are only reported when missing:

Package Feature
docker.io (or Docker CE) Apps and the App Store
samba SMB sharing

curl and root access are needed before the installer can run: use curl … | sudo bash, or curl … | bash from a root shell when sudo is not installed.

Unit Role
hsi.target The whole stack; the only unit enabled at boot
hsi-nats NATS JetStream message broker
hsi-worker Privileged filesystem/disk worker (runs as root)
hsi-server Backend API + static dashboard server
Terminal window
systemctl restart hsi.target
systemctl status hsi-server hsi-worker hsi-nats

systemctl stop|start|restart hsi.target acts on the three services; each can still be managed on its own. An active target does not mean every component is healthy: check the services individually. The scheduled disk checks (hsi-maintenance.timer) and the update units (hsi-update-check, hsi-update-apply) are not part of the target.

The backend and the root worker write JSON lines to files, rotated by /etc/logrotate.d/hsi; NATS logs to the journal.

Terminal window
tail -f /var/log/hsi/app.log # backend (HTTP requests, job outcomes)
tail -f /var/log/hsi/root-worker.log # root worker (jobs, failed requests, commands)
journalctl -u hsi-nats -f

Both files use one JSON format, one line per event:

Field Meaning
time ISO 8601 timestamp
level debug, info, warn, error or fatal
component backend or worker
msg What happened
err Error details (message, stack) when there is one

Other fields depend on the event (jobId, subject, reqId, username…). To read them comfortably:

Terminal window
tail -f /var/log/hsi/root-worker.log | jq -r '"\(.time) \(.level) \(.msg) \(.error // .err.message // "")"'

Output that bypasses the loggers (a Node.js warning, or a Go runtime crash dump) can still appear as plain text.

Every async operation has a jobId that appears in both files. The worker logs each job it receives and its outcome, and every synchronous request that fails. Set HSI_LOG_LEVEL=debug in /etc/hsi/worker.env and restart hsi-worker to also log successful requests and every external command the worker runs (mdadm, parted, smartctl…) with its exit code, duration and trimmed output. Command failures are logged at debug as well: many probes fail routinely, and a failure that matters already shows up as a failed job or request. Passwords are only ever passed on stdin, which is never logged.

Administrators can open the log lines of a failed operation from its error notification (View logs). HSI reads them from the end of app.log and root-worker.log in HSI_LOG_DIR (default /var/log/hsi); lines already rotated away are not shown.

Settings > Notifications routes events to connectors (the in-app bell, a webhook such as Discord, Slack or ntfy, or email over SMTP). Rules match on the event source prefix and a minimum severity:

Source Events
storage.smart, storage.raid, storage.disk-usage Alert raised, alert cleared (including manual clears)
storage.volume A data volume mounted by HSI is missing, replaced by another filesystem (critical) or read-only (warning)
backup.plan A backup run failed
system.update An HSI update did not end on the requested version: reported 5 minutes after HSI restarts on the old version, or 1 hour after the request if HSI never restarted

External deliveries go through a queue stored in the database: they survive a backend restart, are retried twice (after 2 s and 10 s), and each connector sends at most its configured number of messages per minute (10 by default). Extra messages wait; none are dropped unless more than 500 are waiting. Recent deliveries and their errors are listed at the bottom of the page.

Webhook URLs, header values (except Content-Type) and SMTP passwords are encrypted with HSI_SECRETS_KEY and never shown again after saving. If the key changes, affected connectors are flagged and their secrets must be entered again.

HSI can only notify while it is running. A server that is powered off, frozen or unreachable cannot report its own outage: use an external monitor (for example an uptime service polling https://<server>:9001/health) to detect that. On an HTTPS install, plain http:// answers a 308 redirect: trust the authority in the monitor, or skip certificate checks there. Plain http:// works directly only in Off or Proxy mode.

  • 9001: backend API + dashboard, over HTTPS by default (configurable via BACKEND_PORT)
  • 443 and 80: optional standard ports (443 serves HTTPS, 80 redirects to it), or the nginx site of an older installation
  • NATS listens locally for the backend ↔ root-worker channel

A new installation has no account: the installer prints a one-time link to the setup assistant, which creates the administrator (see Install). sudo hsi-worker setup-token prints a new link; it only opens the assistant while no administrator exists. Older installations keep their accounts; an admin / admin account from before must still change its password at the next login.