Strom is a distributed UPS monitoring and management system built around a controller/adopt model.
Small Raspberry Pi nodes run NUT near the hardware, automatically detect USB UPS devices, expose them on the network, serve a per-node web dashboard and status API, and advertise themselves for discovery. A central controller discovers those nodes, adopts them, collects metrics, and eventually bridges the fleet into Home Assistant.
This repository now ships the Phase 1 node agent, the Phase 2 flashable image pipeline, and a substantial Phase 3 controller foundation.
Today that means:
- Raspberry Pi nodes auto-detect USB UPS hardware, generate NUT configuration, advertise themselves over mDNS, and expose a branded local dashboard with live telemetry and UPS control actions.
- The image pipeline builds a flashable Raspberry Pi OS Lite image for node deployment.
- Node images use a read-mostly OverlayFS root to reduce SD card wear while mounting a dedicated persistent
strom-statevolume at/var/lib/strom, so local admin credentials, controller trust material, and stable UPS names survive normal reboots and power loss. - Node Settings can optionally enable password SSH for the local
adminaccount. It uses the dashboard password, grantssudoaccess, and is disabled by default in favor of SSH keys. - Nodes now support operator-driven factory reset through
strom-agent resetor a boot-partitionstrom-factory-resetmarker file for offline recovery. - The controller can discover nodes, persist them in SQLite, adopt pending nodes, forget stale node records, store controller-managed node display/location metadata, establish node-local trust material, poll adopted-node NUT variables into SQLite, expose recent UPS telemetry plus per-UPS detail/history APIs and trusted controller-side UPS commands, evaluate webhook alert rules, serve a branded React controller GUI, publish Home Assistant MQTT discovery/state payloads when configured with a broker, and re-serve adopted UPSes through an aggregate NUT listener on
:3493that can be enabled or disabled from controller settings. Adopted nodes can be returned to pending state withstrom-agent reseton the node. - The controller UPS detail surface now derives a battery runtime-decay trend and replacement estimate from stored historical
battery.runtimesamples gathered during healthy, high-charge periods.
What is still ahead:
-
Phase 3 hardware validation against the real-node exit criteria
-
Final Phase 4 validation against a real Home Assistant instance and broker
-
Later lifecycle and hardening work from the roadmap
-
ROADMAP.md defines the architecture, phases, and exit criteria.
-
docs/ contains the user-facing documentation set, including getting started, features, FAQ, and operational reference material.
-
.github/copilot-instructions.md captures project-specific coding guidance for Copilot sessions in this repo.
-
.github/prompts/ contains workspace slash-command prompts for each roadmap phase.
-
.github/skills/ contains project-specific Copilot skills for agent validation and Pi-node debugging workflows.
- Zero-configuration USB UPS discovery on Raspberry Pi nodes
- NUT-based network exposure with generated configuration
- Centralized discovery, adoption, monitoring, and control
- Home Assistant integration through a controller-side bridge
- A flashable Pi image for simple deployment
Note
Entries tagged planned are still future work and are not implemented in the workspace yet.
strom/
├── ROADMAP.md
├── .github/
│ ├── copilot-instructions.md # repo guidance for Copilot sessions
│ ├── prompts/ # slash-command prompts for roadmap phases
│ ├── skills/ # project-specific Copilot skills
│ └── workflows/
├── strom # top-level developer CLI entrypoint
├── agent/ # Go node agent (runs on the Pi)
│ ├── cmd/agent/
│ └── internal/
│ ├── hotplug/ # udev event watching
│ ├── nutconf/ # nut-scanner parsing + ups.conf generation
│ ├── discovery/ # mDNS advertisement
│ └── api/ # local HTTP API
├── controller/ # Go backend + React controller GUI (Phase 3)
│ ├── cmd/controller/ # controller entrypoint + embedded frontend assets
│ └── internal/ # browse, CA, registry, secure store
├── image/ # pi-gen based SD card image build
│ ├── stage-strom/ # custom pi-gen stage
│ └── config # pi-gen config
├── sim/ # planned: virtual UPS + node simulation
│ ├── dummy-ups/ # planned: NUT dummy-ups fixtures
│ └── docker-compose.yml # planned: simulated node topology
└── deploy/ # systemd units, udev rules, install scripts
Work is intended to follow the roadmap phase by phase rather than building the full system up front.
- Phase 0: scaffold the monorepo and CI
- Phase 1: ship the node agent MVP
- Phase 2: build a flashable image
- Phase 3: add the controller, adoption flow, and fleet UI
- Phase 4: add the Home Assistant bridge
Phase 3 implementation is now present in the repository: discovery, adoption, controller packaging, polling/history, alerting, and the React controller GUI. The remaining Phase 3 risk is hardware validation against the roadmap exit criteria.
When implementing code in this repository:
- Prefer Go standard library solutions unless a dependency is clearly justified
- Target Go 1.26+ and Raspberry Pi OS Lite (Debian bookworm) for the agent
- Keep generated configs and service artifacts deterministic and testable
- Write table-driven tests for anything that parses or renders text
If you are starting work from scratch:
- Read ROADMAP.md for the intended architecture and constraints.
- Use the matching slash-command prompt from .github/prompts/ when the task lines up with a roadmap phase.
- Implement only the requested phase unless a small prerequisite is needed to keep the repo buildable.
- Update ROADMAP.md in the same change when roadmap checklist items become fully complete.
- Use the skills in .github/skills/ for recurring validation or hardware-debug workflows instead of rewriting those procedures in every session.
This repository now produces versioned agent release artifacts, a flashable Raspberry Pi OS Lite image for Strom nodes, and a published multi-arch controller container image. Release tagging is automated for normal merges, while major/minor version choices and prerelease validation stay under maintainer control:
.github/release/version.tomlis the single source of truth for the active release train (major/minor) and RC build behavior. Only maintainers change it, via the manual "Version Train Bump" GitHub Actions workflow (workflow_dispatch), which opens a reviewed pull request rather than committing directly.- Every push to
main(other than docs-only changes, or a commit whose message contains[skip release]) runs.github/workflows/auto-release.yml, which computes the next patch tag for the configured train withstrom release next-version, creates and pushes an immutable annotated tag such asv0.1.1, and calls the existing.github/workflows/release.ymlto build and publish it.latestimage tags are only emitted for stable tags, never for-rcNprereleases. - Pull requests targeting
mainautomatically get lightweight release-candidate agent binaries as workflow artifacts via.github/workflows/rc.yml. Attach therelease-candidatelabel (or run the workflow manually) to also validate a build of the controller and agent container images; those RC images are never pushed anywhere. - Maintainers can still cut a tag by hand (for example, an out-of-band hotfix) by pushing a SemVer-style tag such as
v0.1.0or a prerelease likev0.1.0-rc1;release.ymlstill triggers directly on anyv*tag push. release.ymlruns tests, builds the agent forlinux/arm64andlinux/armv6, packages each archive with the install assets fromdeploy/, builds thestrom-node-<version>.img.xzimage through pi-gen, publishes those artifacts to the GitHub Release for that tag, and builds/pushes the multi-arch controller and agent container images toghcr.io/<owner>/strom-controllerandghcr.io/<owner>/strom-agent.
You can build the same release payload locally with:
uv run strom release agent --version v0.1.0
uv run strom image node --version v0.1.0
uv run strom image controller --version v0.1.0For local controller iteration, use:
uv run strom build controller
uv run strom dev controllerController DB backup/restore:
go run ./controller/cmd/controller backup --data-dir /data --output /tmp/controller-backup.db
go run ./controller/cmd/controller restore --data-dir /data --input /tmp/controller-backup.db --forceController-signed agent OTA push to an adopted node:
go run ./controller/cmd/controller ota --data-dir /data --node-id <node-id> --binary ./dist/strom-agent-linux-arm64 --version v0.4.0The controller signs the binary digest with its CA private key, then pushes the payload to the node's trusted TLS API. The node verifies the signature against the adopted controller CA certificate before replacing its local agent binary.
A standalone node with no adopting controller can also check for and install updates on its own: a daily timer polls GitHub releases for a signed manifest, and an operator can install an available update from the node dashboard or strom-agent update install <version>. Updates are verified against an Ed25519 signature before being staged, and a failed update automatically rolls back on the next restart. See docs/reference/agent.md for details and CONTRIBUTING.md for the release-signing-key procedure.
Image build prerequisites and the flash workflow are documented in image/README.md.
The user-facing documentation set lives in docs/. If you are looking for setup steps, product capabilities, or operational reference material, start there.
Local docs and repo tooling are managed with uv. Use uv run strom docs setup, uv run strom docs build, or uv run strom docs serve from the repo root after installing uv, and use uv run strom hooks install or uv run strom hooks run for repo hygiene checks.
For the newer project CLI flow, run strom from an activated virtual environment, or use uv run strom without activating one. Current commands include:
strom docs,strom docs serve,strom docs buildstrom sim up|down|ps|logsstrom sim smoke [--strict]strom sim scenario <name>(ci-smoke,on_battery,restore,node_loss)strom hooks install|runstrom build ...,strom dev ...,strom check ...,strom image ...,strom release ...
Examples:
uv run strom docs
uv run strom sim smoke --strict --replicas 2
uv run strom sim up --replicas 2
strom sim logs --service strom-controller --tail 200
strom hooks runFor the current user-facing path, start with:
- docs/getting-started.md for building, flashing, and validating a node image
- docs/features.md for what ships today versus what is still planned
- docs/faq.md for common operator questions
- docs/reference/image.md for image artifact, checksum, compatibility, and first-boot reference details
Contributor workflow, release policy, RC handling, and GitHub Actions limit guidance live in CONTRIBUTING.md. Community expectations and reporting guidance live in CODE_OF_CONDUCT.md. Security vulnerability reporting guidance lives in SECURITY.md.
This project is licensed under the terms in LICENSE.