feat(deploy): give the upgrade window its own timezone, not the system

An upgrade restarts the app and the Fujitsu dispenser runs an audible
init routine when it does. On sintra that was firing at 10:17 in the
morning, in the room, because `dates = "04:00"` is local time and every
machine inherits America/Guatemala from the shared base config.

The obvious fix is to set the system timezone per machine. This does the
narrower thing instead: systemd 252+ accepts a timezone suffix on a
calendar spec, so the timer follows Europe/Paris and its DST while the
system clock stays a fleet default nobody maintains per host. The only
other consumer of machine-local time is a technician reading the journal
at the machine, and for correlating against relay created_at stamps, UTC
is easier anyway.

The operator dashboard never needed this. It renders timestamps in the
reader's own browser locale, which is why the cassettes tab read
correctly while the journal did not.

Verified by resolving the config for all four hosts, and against
systemd-analyze on sintra itself: 04:00 Europe/Paris is 02:00 UTC in
summer, 04:00 America/Guatemala is 10:00 UTC.

A warning is in the comment because it nearly caught me: the same check
under `nix-shell -p systemd` silently computes EVERY named zone as UTC
while echoing the zone back in its normalized form. It looks accepted and
is wrong. Test on a real system.
This commit is contained in:
Padreug 2026-09-24 23:11:49 +02:00
commit c1ada736d2

View file

@ -67,6 +67,34 @@
batm3 = "USD"; batm3 = "USD";
}; };
# Nightly auto-upgrade window per machine model, as a systemd calendar
# spec. An upgrade restarts the app, and the Fujitsu dispenser runs an
# audible init routine when it does, so this wants to land in the middle
# of the machine's own night rather than its business hours.
#
# The timezone suffix (systemd 252+) is what makes that work WITHOUT
# setting the system clock: the timer follows the named zone and its DST,
# while time.timeZone stays a fleet-wide default nobody has to maintain
# per host. Verified on sintra with systemd-analyze — `04:00
# Europe/Paris` resolves to 02:00 UTC in summer, `04:00
# America/Guatemala` to 10:00 UTC.
#
# Do NOT check a spec like this under `nix-shell -p systemd`. The sandbox
# cannot resolve named zones and silently computes EVERY one of them as
# UTC, while still echoing the zone back in its "Normalized form" line.
# It looks accepted and is wrong. Test on a real system.
#
# Keyed on model like fiatCodeForModel above, and inheriting the same
# limitation: model is a hardware model, and it only doubles as host
# identity while there is one machine of each. A second sintra in another
# country needs this keyed on host instead, along with the fiat code and
# the app build that bakes it in.
#
# Unlisted models get 04:00 in whatever time.timeZone says, unchanged.
upgradeWindowForModel = {
sintra = "04:00 Europe/Paris";
};
lib = nixpkgs.lib; lib = nixpkgs.lib;
# Helper to create a live USB NixOS config for a specific machine model # Helper to create a live USB NixOS config for a specific machine model
@ -182,7 +210,9 @@
enable = true; enable = true;
flake = "git+ssh://forgejo@git.atitlan.io/aiolabs/bitspire.git?ref=dev#${machineModel}-installed"; flake = "git+ssh://forgejo@git.atitlan.io/aiolabs/bitspire.git?ref=dev#${machineModel}-installed";
flags = [ "--refresh" ]; flags = [ "--refresh" ];
dates = "04:00"; # daily at 4am # Daily at 4am in the machine's own zone; see
# upgradeWindowForModel above.
dates = upgradeWindowForModel.${machineModel} or "04:00";
allowReboot = false; allowReboot = false;
}; };