rewrite as a daemon with shutdown on low battery

the oneshot+timer produced 8367 journal lines a day of systemd
start/stop noise. now Type=notify with an internal ticker, logging
transitions only.

hardware sits behind sensor.Sensor; internal/monitor is pure, so the
shutdown state machine is tested without a pi.

four guards, each tested, because a false poweroff of a box with no
physical access is worse than a missed one:
- a failed read resets the arming window rather than pausing it
- voltage must actually fall across the window, else it is a stuck
  sensor. this is the only defence against the ac line reading 0
  while mains is connected, so it is load-bearing
- a settle period stops a restart loop acting on early readings
- flapping ac cannot accumulate

verified on hardware 2026-08-09: after poweroff on battery the x1208
starts the pi again when mains returns, with no button press. that
was the risk that could have made this feature unsafe.

a failed tick rewrites the previous sample with sensor_healthy 0 and
the failure counter, so last_update still freezes for the staleness
alert while the failure stays visible.

module path moved to git.mial.net. debian units and the compose
overlay deleted; deployment lives in the infra repo.
This commit is contained in:
2026-08-09 02:01:19 +02:00
parent aae6cee886
commit e4c34784c8
23 changed files with 1774 additions and 321 deletions
+63
View File
@@ -0,0 +1,63 @@
{
description = "Prometheus exporter for the Geekworm X1208 UPS HAT";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
}:
# This flake exports the PACKAGE only. There is deliberately no
# nixosModule: the systemd unit carries host policy (device access,
# groups, polkit, hardening, whether shutdown is enabled at all) and lives
# in the infrastructure repo. Two sources of unit truth is the split-brain
# this avoids.
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
version = if self ? rev then "0.0.0+${builtins.substring 0 8 self.rev}" else "dev";
in
{
packages.default = self.packages.${system}.x1208-exporter;
packages.x1208-exporter = pkgs.buildGoModule {
pname = "x1208-exporter";
inherit version;
src = ./.;
vendorHash = "sha256-vPzEdZJ3kZ79zl17r2ssFtk3anE21ARy/RRbYqBoiEI=";
ldflags = [
"-s"
"-w"
"-X main.version=${version}"
];
# The hardware packages need a Pi; the pure ones do not. Only the
# pure packages carry tests, so the whole suite runs in the sandbox.
doCheck = true;
meta = {
description = "Prometheus textfile exporter for the Geekworm X1208 UPS HAT";
mainProgram = "x1208-exporter";
platforms = pkgs.lib.platforms.linux;
};
};
devShells.default = pkgs.mkShell {
packages = with pkgs; [
go
gopls
golangci-lint
];
};
formatter = pkgs.nixfmt-rfc-style;
}
);
}