Skip to content

Add Network UPS Tools settings#3317

Open
BeeJoe wants to merge 4 commits into
Start9Labs:masterfrom
BeeJoe:feat/NUT
Open

Add Network UPS Tools settings#3317
BeeJoe wants to merge 4 commits into
Start9Labs:masterfrom
BeeJoe:feat/NUT

Conversation

@BeeJoe

@BeeJoe BeeJoe commented Jun 14, 2026

Copy link
Copy Markdown

Summary

Adds Network UPS Tools (NUT) support to StartOS settings.

  • Adds backend NUT configuration, status retrieval, and sync logic.
  • Supports disabled, directly connected UPS, and network UPS client modes.
  • Adds settings UI under System settings with UPS status details and error notification behavior.
  • Adds API bindings, mock API support, i18n strings, and required NUT image dependencies.

Related docs

Also see the related PR on the Start9Labs/start-docs repository.

Validation

  • npm --prefix web run check:ui
  • npm --prefix web run check:i18n
  • Pre-commit hook passed check:ui, check:shared, and check:i18n
  • git diff --check

@BeeJoe BeeJoe marked this pull request as ready for review June 14, 2026 02:01
@MattDHill MattDHill requested a review from dr-bonez June 15, 2026 14:41
@MattDHill MattDHill added Enhancement New feature or request UI Issues pertaining to any of the embassy-served web applications startd Issues pertaining to startd itself labels Jun 15, 2026
@MattDHill MattDHill added this to the 0.4.1 milestone Jun 15, 2026
@dr-bonez

Copy link
Copy Markdown
Member

Overall this is fairly well implemented, but may be a bit of an advanced feature for most users. A user may see this new tab and not understand why it's here or what to do with it. @MattDHill would like your input on the UX here.

@helix-nine

Copy link
Copy Markdown
Contributor

Heads up: this PR's NUT integration wires upsmon to run SHUTDOWNCMD "/sbin/shutdown -h +0" on low battery, but an externally-initiated shutdown -h is not currently graceful on StartOS — systemd SIGKILLs the LXC service containers before startd can coordinate a graceful teardown (see #3235). So a UPS-triggered shutdown would terminate services abruptly, which is exactly the data-corruption risk this feature is meant to avoid for Lightning/Bitcoin workloads.

This depends on #3319, which adds the systemd ordering + KillMode=mixed/TimeoutStopSec so shutdown -h lets startd stop containers gracefully first. #3317 should land on top of (or be merged after) #3319.

@MattDHill

Copy link
Copy Markdown
Member

Overall this is fairly well implemented, but may be a bit of an advanced feature for most users. A user may see this new tab and not understand why it's here or what to do with it. @MattDHill would like your input on the UX here.

Could/should this be a service?

@helix-nine

Copy link
Copy Markdown
Contributor

From the technical side — the part that constrains the packaging/UX call:

The core value of UPS support is powering off the host on low battery, and a packaged service can't do that today:

  • The only shutdown an effect exposes to packages is Effects.shutdown(), which stops the calling service itself (service/effects/control.rs just sets that package's own status to stopped). There's no host-shutdown effect, and the LXC sandbox can't run /sbin/shutdown.
  • Server mode also needs host hardware access for the NUT driver (the USB/serial UPS + udev), which is more natural at the host level than in a sandboxed container.

So as-is, the host-poweroff behavior has to live in core. The path to "NUT as a service" would be to expose a privileged host-shutdown effect/permission that a NUT service calls on LOWBATT — which is essentially the server shutdown endpoint @dr-bonez and I are currently reworking for graceful teardown (see the #3319 thread). If that effect existed, client mode (and server mode with device access) could plausibly live in a package, with the privileged poweroff delegated to core — and it'd still depend on the graceful-shutdown fix so the triggered poweroff isn't abrupt.

Net: it could become a service if we add a host-shutdown effect, but the privileged poweroff (and the graceful-shutdown work behind it) stays in core either way. Whether the config/status surface should be a core tab or a package is your and @dr-bonez's call — flagging the constraints so it's grounded; happy to support either direction.

@dr-bonez

Copy link
Copy Markdown
Member

Not really. The purpose is being able to connect StartOS to a smart UPS such that it can shut down gracefully before the UPS battery runs out. Very OS-level feature, but not applicable to users that don't have the relevant hardware. And might raise questions in support about "what is this UPS thing and do I need it?"

@helix-nine

Copy link
Copy Markdown
Contributor

On the discoverability concern: one mechanism that squares "OS-level feature" with "don't confuse users who don't have a UPS" is hardware detection — nut-scanner / the driver layer can auto-detect a directly-connected USB UPS, so the tab (or a one-time prompt) could stay hidden until a UPS is actually detected. Invisible for the ~all users without the hardware, self-revealing for anyone who plugs one in.

The one caveat is client mode (monitoring a UPS hosted on another NUT server over the network) — there's no local device to detect, so that path would still need an explicit entry point, e.g. behind an "advanced" toggle rather than the default surface.

Final UX call is yours and @MattDHill's — just flagging that auto-detection is available as a gate if you want the feature to stay out of the way until it's relevant.

@BeeJoe

BeeJoe commented Jun 15, 2026

Copy link
Copy Markdown
Author

Would a different location or name (like "battery backup") or even a brief explanation make it more user-friendly?

@MattDHill

Copy link
Copy Markdown
Member

I think we should show it always and call it what it is, handling the explantion and instructions in documentation, as designed.

I'm generally against hidden/conditional features. If they are actual good/useful features, we want people to say "what's that?" and discover something useful. It also works against the person who explicitly wants the feature but doesn't know it exists. Finally, the client aspect of this makes muddles the clean conditionality.

The new frontend component needs some work. We'll want @waterplea to get in there and clean up a bit.

@MattDHill MattDHill requested a review from waterplea June 15, 2026 21:18
@helix-nine

Copy link
Copy Markdown
Contributor

Makes sense — agreed on always-visible and named for what it is, with the explanation in docs. Your point about it working against the user who wants the feature but doesn't know it exists is the convincing one, and the network-client mode does muddle any clean hardware gate. Withdrawing the auto-detection idea. Nothing further from me on the UX; the only remaining gate from the engineering side is the graceful-shutdown dependency (#3319) so the UPS-triggered shutdown -h is actually safe.

@waterplea

Copy link
Copy Markdown
Collaborator

I think we should show it always and call it what it is, handling the explantion and instructions in documentation, as designed.

I'm generally against hidden/conditional features. If they are actual good/useful features, we want people to say "what's that?" and discover something useful. It also works against the person who explicitly wants the feature but doesn't know it exists. Finally, the client aspect of this makes muddles the clean conditionality.

The new frontend component needs some work. We'll want @waterplea to get in there and clean up a bit.

I can jump on it after it's merged, is that ok?

@MattDHill

MattDHill commented Jun 17, 2026

Copy link
Copy Markdown
Member

No, but it's not urgent either. This isn't going into beta.10, expected today/tomorrow. It will wait for the next release.

@waterplea

Copy link
Copy Markdown
Collaborator

@MattDHill @BeeJoe I'm not sure how to contribute here, I've made my edits to the UI but looks like I do not have the right to push into @BeeJoe fork. Should I make my own fork with a PR to this branch? For now I only refactored the UI a little bit, I think the config interaction should be reworked a bit, maybe into modal so that it does not take up space all the time, what do you think? I also talked to @dr-bonez a little – I think that making disabled a config option is not that great UX, there should be a toggle on/off and then configuration. For that, it would need some reworking on the back to not discard config when we disable the feature.

@dr-bonez

Copy link
Copy Markdown
Member

@waterplea it looks like @BeeJoe has "allow edits from maintainers enabled on the PR so you should be able to push directly to his branch

helix-nine added a commit to BeeJoe/start-technologies that referenced this pull request Jun 29, 2026
Encode NUT enable/disable as a top-level boolean separate from the
server/client settings, so toggling off no longer discards configured
values. NutConfig becomes { enabled, settings: NutSettings? }; the
former Disabled enum variant is gone. Disabling stops monitoring and
retains settings; re-enabling re-applies them.

Addresses @waterplea's review feedback on Start9Labs#3317.
@helix-nine

Copy link
Copy Markdown
Contributor

Pushed a backend rework addressing @waterplea's point about not discarding config on disable (b1bc92628).

enable is now orthogonal to the settings rather than a mode:

NutConfig { enabled: bool, settings: Option<NutSettings> }
NutSettings = Server { … } | Client { … }   // the old Disabled variant is gone
  • Disabling sets enabled: false but keeps settings, so toggling off stops monitoring (MODE=none, units stopped) without losing the configured server/client values. Re-enabling re-applies them.
  • set-nut rejects enabled: true with no settings; clear-nut resets to { enabled: false, settings: null }.
  • Regenerated NutConfig/added NutSettings TS bindings.

I also updated the existing UI component to the new shape (an Enabled toggle + a server/client Mode union, status section gated on enabled && settings) just to keep the build green — that's deliberately minimal, so it's all yours to rework into the toggle-then-config / modal layout you described. cargo check --lib, check:ui, and check:i18n all pass.

Maintainer-edits is enabled on this PR, so you should be able to push your UI work straight to this branch — point your remote at BeeJoe/start-technologies and push to feat/NUT (not your own fork). Happy to adjust the data model if the UI rework wants a different shape.

MattDHill pushed a commit to BeeJoe/start-technologies that referenced this pull request Jun 30, 2026
Encode NUT enable/disable as a top-level boolean separate from the
server/client settings, so toggling off no longer discards configured
values. NutConfig becomes { enabled, settings: NutSettings? }; the
former Disabled enum variant is gone. Disabling stops monitoring and
retains settings; re-enabling re-applies them.

Addresses @waterplea's review feedback on Start9Labs#3317.
BeeJoe and others added 2 commits July 1, 2026 13:14
Encode NUT enable/disable as a top-level boolean separate from the
server/client settings, so toggling off no longer discards configured
values. NutConfig becomes { enabled, settings: NutSettings? }; the
former Disabled enum variant is gone. Disabling stops monitoring and
retains settings; re-enabling re-applies them.

Addresses @waterplea's review feedback on Start9Labs#3317.
@waterplea

Copy link
Copy Markdown
Collaborator

Rebased and pushed my changes

Resolve i18n dictionary ID collision: master claimed IDs 882-885 for the Port Range strings, so renumber the four colliding NUT strings (Network UPS Tools, Configure, Direct UPS, Client) to 909-912 across all five locale dictionaries. All other conflicts auto-merged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request startd Issues pertaining to startd itself UI Issues pertaining to any of the embassy-served web applications

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants