feat(timezone): opt-in automatic, location-following timezone
nomarchy.system.autoTimezone (off by default): geoclue + automatic- timezoned drive /etc/localtime from your location, so travelling to another zone updates the Waybar clock on its own. Menu-driven, in-flake state (the night-light / keyboard philosophy): the flag is settings.autoTimezone in theme-state.json (git-tracked); a System-menu entry toggles it. Because it's a SYSTEM service — not a user unit it can start/stop instantly like night-light — the toggle (nomarchy-autotimezone, run in a terminal) writes the flag --no-switch then drives `sudo nixos-rebuild` (bakes the service + the time.timeZone override) plus `home-manager switch` (the Waybar-refresh watcher); both sides read the one flag. - modules/nixos/timezone.nix: parses settings.autoTimezone from nomarchy.system.stateFile (like Plymouth), gates geoclue2 + automatic-timezoned, and mkForce-nulls time.timeZone — automatic- timezoned sets it null at normal priority, which collides with the installer's static value (a hard eval error), so force wins and reverts cleanly when disabled. Ships the nomarchy-autotimezone toggle. - modules/home/timezone.nix: a tiny user service watching timedate1's change signal that reloads Waybar (SIGUSR2) on a real zone change, so the clock follows live (Waybar captures the zone at construction). Gated on the same flag via nomarchy.settings.autoTimezone. - rofi menu: "Auto timezone (on/off)" in the System submenu. - option + template example + ROADMAP entry; theme.nix seeds the flag. Eval-verified on (geoclue+daemon on, tz forced null over a static installer value, watcher present) and off (static tz intact, nothing extra); home + system both build green. Needs an on-hardware check (geoclue detection + the SIGUSR2 clock refresh aren't CI-testable). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
./yazi.nix # flagship TUI file manager, themed + plugins
|
||||
./osd.nix # swayosd volume/brightness OSD, themed
|
||||
./nightlight.nix # scheduled blue-light filter (hyprsunset), opt-in
|
||||
./timezone.nix # keep the Waybar clock in step with auto-timezone changes
|
||||
./updates.nix # passive update-awareness indicator + notification, opt-in
|
||||
./shell.nix # zsh + starship + bat/eza/zoxide, themed
|
||||
./keys.nix # gpg-agent (fronts SSH) + pinentry-qt
|
||||
|
||||
@@ -324,6 +324,14 @@ ${themeRows}
|
||||
&& exec nomarchy-nightlight toggle
|
||||
notify-send "Night light" "Not available (nomarchy.nightlight off?)."; exit 0 ;;
|
||||
|
||||
autotimezone)
|
||||
# Toggle automatic timezone detection (geoclue + automatic-timezoned).
|
||||
# A SYSTEM service, so flipping it writes the in-flake flag and runs a
|
||||
# system rebuild (sudo) + a home switch — in a terminal, like Snapshots.
|
||||
command -v nomarchy-autotimezone >/dev/null 2>&1 \
|
||||
|| { notify-send "Auto timezone" "Unavailable on this machine."; exit 0; }
|
||||
exec ${cfg.terminal} -e nomarchy-autotimezone toggle ;;
|
||||
|
||||
snapshot)
|
||||
# btrfs-assistant 2.2 segfaults on 26.05 (libbtrfsutil ABI), so launch
|
||||
# the keyboard-driven snapper browser instead: a terminal running it
|
||||
@@ -374,6 +382,10 @@ ${themeRows}
|
||||
then row "Night light (on)" weather-clear-night
|
||||
else row "Night light (off)" weather-clear-night
|
||||
fi
|
||||
if [ "$(nomarchy-theme-sync get settings.autoTimezone 2>/dev/null)" = true ]
|
||||
then row "Auto timezone (on)" preferences-system-time
|
||||
else row "Auto timezone (off)" preferences-system-time
|
||||
fi
|
||||
command -v nomarchy-snapshots >/dev/null 2>&1 && row "Snapshots" timeshift
|
||||
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
|
||||
row "Power profile" preferences-system-power
|
||||
@@ -388,6 +400,7 @@ ${themeRows}
|
||||
*Printers*) exec "$0" printers ;;
|
||||
*"Do Not Disturb"*) exec "$0" dnd ;;
|
||||
*"Night light"*) exec "$0" nightlight ;;
|
||||
*"Auto timezone"*) exec "$0" autotimezone ;;
|
||||
*Snapshots*) exec "$0" snapshot ;;
|
||||
*"Power profile"*) exec "$0" power-profile ;;
|
||||
esac ;;
|
||||
@@ -414,7 +427,7 @@ ${themeRows}
|
||||
esac ;;
|
||||
|
||||
*)
|
||||
echo "usage: nomarchy-menu [tools|system|power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|audio|printers|capture|keybinds|ask|dnd|nightlight|snapshot]" >&2
|
||||
echo "usage: nomarchy-menu [tools|system|power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|audio|printers|capture|keybinds|ask|dnd|nightlight|autotimezone|snapshot]" >&2
|
||||
exit 64 ;;
|
||||
esac
|
||||
'';
|
||||
|
||||
@@ -66,6 +66,10 @@ let
|
||||
settings = {
|
||||
nightlight = { installed = false; on = true; };
|
||||
keyboard.devices = { };
|
||||
# Automatic timezone detection (nomarchy.system.autoTimezone): a system
|
||||
# service, but the flag lives here so both sides read one source — the
|
||||
# home side gates the Waybar-refresh watcher (timezone.nix) on it.
|
||||
autoTimezone = false;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
44
modules/home/timezone.nix
Normal file
44
modules/home/timezone.nix
Normal file
@@ -0,0 +1,44 @@
|
||||
# Auto-timezone, home side: keep the Waybar clock in step with the system
|
||||
# timezone. Waybar's clock module captures the zone once at construction, so a
|
||||
# runtime timezone change (automatic-timezoned, nomarchy.system.autoTimezone)
|
||||
# would NOT show until a relogin. A tiny watcher subscribes to timedate1's
|
||||
# change signal and reloads Waybar (SIGUSR2 = the same reload theme-sync uses),
|
||||
# so the clock follows your location live. Also catches a manual
|
||||
# `timedatectl set-timezone`.
|
||||
#
|
||||
# Gated on the same in-flake flag the system side reads (settings.autoTimezone,
|
||||
# exposed via nomarchy.settings) — so it only runs when the feature is on. The
|
||||
# menu toggle (nomarchy-autotimezone) rebuilds both sides off that one flag.
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.nomarchy;
|
||||
enabled = cfg.waybar.enable && (cfg.settings.autoTimezone or false);
|
||||
|
||||
tzWatch = pkgs.writeShellScript "nomarchy-tz-watch" ''
|
||||
last=$(${pkgs.systemd}/bin/timedatectl show -p Timezone --value 2>/dev/null || true)
|
||||
${pkgs.dbus}/bin/dbus-monitor --system \
|
||||
"type='signal',interface='org.freedesktop.DBus.Properties',path='/org/freedesktop/timedate1',member='PropertiesChanged'" \
|
||||
2>/dev/null |
|
||||
while read -r _; do
|
||||
cur=$(${pkgs.systemd}/bin/timedatectl show -p Timezone --value 2>/dev/null || true)
|
||||
[ "$cur" = "$last" ] && continue
|
||||
last=$cur
|
||||
${pkgs.procps}/bin/pkill -SIGUSR2 -x waybar 2>/dev/null || true
|
||||
done
|
||||
'';
|
||||
in
|
||||
{
|
||||
systemd.user.services.nomarchy-tz-watch = lib.mkIf enabled {
|
||||
Unit = {
|
||||
Description = "Reload Waybar on timezone change (auto-timezone)";
|
||||
PartOf = [ "graphical-session.target" ];
|
||||
After = [ "graphical-session.target" ];
|
||||
};
|
||||
Service = {
|
||||
ExecStart = "${tzWatch}";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
Install.WantedBy = [ "graphical-session.target" ];
|
||||
};
|
||||
}
|
||||
@@ -22,7 +22,7 @@ let
|
||||
'';
|
||||
in
|
||||
{
|
||||
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ./hardware.nix ];
|
||||
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ./hardware.nix ./timezone.nix ];
|
||||
|
||||
config = {
|
||||
# The safe half of distro branding: distroName flows into
|
||||
|
||||
@@ -46,6 +46,16 @@
|
||||
audio.enable = lib.mkEnableOption "the Pipewire audio stack" // { default = true; };
|
||||
bluetooth.enable = lib.mkEnableOption "Bluetooth support with blueman" // { default = true; };
|
||||
|
||||
autoTimezone.enable = lib.mkEnableOption ''
|
||||
automatic timezone detection (geoclue + automatic-timezoned): the
|
||||
system timezone — and so the Waybar clock — follows your location, so
|
||||
travelling to another zone updates the time on its own. Off by default
|
||||
(it's a location service and needs the network); toggle it from the
|
||||
System menu, which lands the choice in the in-flake state file. Enabling
|
||||
it unsets the static time.timeZone for you (a runtime timezone needs
|
||||
/etc/localtime writable), so the menu toggle drives a system rebuild''
|
||||
// { default = false; };
|
||||
|
||||
snapper.enable = lib.mkEnableOption ''
|
||||
hourly/daily BTRFS timeline snapshots of / via snapper, plus the
|
||||
`nixos-rebuild-snap` pre-rebuild-snapshot helper. No-op unless the
|
||||
|
||||
87
modules/nixos/timezone.nix
Normal file
87
modules/nixos/timezone.nix
Normal file
@@ -0,0 +1,87 @@
|
||||
# Automatic timezone detection (opt-in) — the system timezone, and so the
|
||||
# Waybar clock, follows your location, so travelling to another zone updates
|
||||
# the time on its own. Geoclue feeds `automatic-timezoned`, which drives
|
||||
# /etc/localtime at runtime.
|
||||
#
|
||||
# In-flake state, menu-driven (the keyboard/night-light philosophy): the on/off
|
||||
# flag lives in the same theme-state.json under `settings.autoTimezone`
|
||||
# (git-tracked, reproducible), written by the System-menu toggle
|
||||
# (nomarchy-autotimezone). Because this is a SYSTEM service — not a user unit it
|
||||
# can start/stop instantly like night-light — the toggle drives a system rebuild
|
||||
# (plus a home switch for the Waybar-refresh watcher in timezone.nix home-side).
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.nomarchy.system;
|
||||
|
||||
# Read the same state file the rest of the system side uses (Plymouth too),
|
||||
# wired by lib.mkFlake. The flag defaults off when the file is absent/sparse.
|
||||
state =
|
||||
if cfg.stateFile != null
|
||||
then builtins.fromJSON (builtins.readFile cfg.stateFile)
|
||||
else { };
|
||||
stateEnabled = (state.settings or { }).autoTimezone or false;
|
||||
|
||||
sync = lib.getExe pkgs.nomarchy-theme-sync;
|
||||
|
||||
# Menu/CLI toggle. Runs as the normal user (it owns the flake checkout +
|
||||
# writes the state); sudos only the system switch, like sys-update. Writes
|
||||
# the in-flake flag, then rebuilds: the system rebuild bakes the service +
|
||||
# the time.timeZone override, the home switch installs/removes the Waybar
|
||||
# refresh watcher — both read the same flag we just wrote.
|
||||
nomarchy-autotimezone = pkgs.writeShellScriptBin "nomarchy-autotimezone" ''
|
||||
set -e
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
echo "nomarchy-autotimezone: run as your normal user (it sudos the rebuild itself)" >&2
|
||||
exit 1
|
||||
fi
|
||||
flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}"
|
||||
|
||||
cur=$(${sync} get settings.autoTimezone 2>/dev/null) || cur=false
|
||||
case "''${1:-toggle}" in
|
||||
on) new=true ;;
|
||||
off) new=false ;;
|
||||
toggle) case "$cur" in true|True) new=false ;; *) new=true ;; esac ;;
|
||||
status) echo "$cur"; exit 0 ;;
|
||||
*) echo "usage: nomarchy-autotimezone [toggle|on|off|status]" >&2; exit 64 ;;
|
||||
esac
|
||||
|
||||
${sync} --quiet set settings.autoTimezone "$new" --no-switch
|
||||
|
||||
if [ "$new" = true ]; then
|
||||
notify-send "Auto timezone" "Enabling — rebuilding the system…" 2>/dev/null || true
|
||||
else
|
||||
notify-send "Auto timezone" "Disabling — rebuilding the system…" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
sudo nixos-rebuild switch --flake "$flake#default"
|
||||
home-manager switch --flake "$flake"
|
||||
|
||||
if [ "$new" = true ]; then
|
||||
notify-send "Auto timezone on" "The clock now follows your location." 2>/dev/null || true
|
||||
else
|
||||
notify-send "Auto timezone off" "Back to the fixed timezone in system.nix." 2>/dev/null || true
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
# Shipped unconditionally so the menu can enable the feature even while
|
||||
# it's off. Track the in-flake flag; mkDefault so a hand-set
|
||||
# nomarchy.system.autoTimezone.enable in system.nix still wins.
|
||||
environment.systemPackages = [ nomarchy-autotimezone ];
|
||||
nomarchy.system.autoTimezone.enable = lib.mkDefault stateEnabled;
|
||||
}
|
||||
|
||||
(lib.mkIf cfg.autoTimezone.enable {
|
||||
services.geoclue2.enable = true;
|
||||
services.automatic-timezoned.enable = true;
|
||||
# A runtime timezone needs /etc/localtime writable. automatic-timezoned
|
||||
# sets time.timeZone = null itself, but the installer writes a static
|
||||
# value at normal priority, which would collide (a hard eval error) —
|
||||
# mkForce null overrides both and resolves cleanly.
|
||||
time.timeZone = lib.mkForce null;
|
||||
})
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user