diff --git a/flakes/common/nix_modules/timezone_auto.nix b/flakes/common/nix_modules/timezone_auto.nix index dac85767..3d2ee7f7 100644 --- a/flakes/common/nix_modules/timezone_auto.nix +++ b/flakes/common/nix_modules/timezone_auto.nix @@ -1,207 +1,64 @@ -{ config, lib, pkgs, ... }: -let - cfg = config.services.automatic-timezoned; - persistFile = if cfg.persistDir == null then null else "${cfg.persistDir}/timezone"; - tzdata = pkgs.tzdata; -in +{ lib, pkgs, ... }: { - options.services.automatic-timezoned.persistDir = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - Absolute runtime directory used to persist the timezone for impermanence setups. + services.dbus.enable = lib.mkDefault true; + services.geoclue2.enable = true; - Important: this must be a normal filesystem path (a string like - "/persist/var/lib/timezone-persist"), not a Nix `path` value, otherwise it - can be coerced into a `/nix/store/...` path and become unwritable at runtime. + time.timeZone = null; + services.automatic-timezoned.enable = true; - When set, the timezone is saved to this directory and restored on boot, - allowing offline boots to use the last known timezone. - Set to null to disable persistence (default). - ''; + systemd.services.automatic-timezoned = { + after = [ "dbus.socket" "systemd-timedated.service" "geoclue.service" ]; + wants = [ "dbus.socket" "systemd-timedated.service" "geoclue.service" ]; + serviceConfig = { + ExecStartPre = "${lib.getExe' pkgs.coreutils "sleep"} 5"; + Restart = "on-failure"; + RestartSec = "10s"; + }; }; - config = { - assertions = [ - { - assertion = cfg.persistDir == null || lib.hasPrefix "/" cfg.persistDir; - message = "services.automatic-timezoned.persistDir must be an absolute path"; - } - ]; + systemd.services.automatic-timezoned-geoclue-agent = { + after = [ "dbus.socket" ]; + wants = [ "dbus.socket" ]; + }; - services.dbus.enable = lib.mkDefault true; - services.geoclue2.enable = true; + systemd.services.fix-localtime-symlink = { + description = "Fix /etc/localtime symlink to be absolute"; + wantedBy = [ "multi-user.target" ]; + after = [ "automatic-timezoned.service" ]; + wants = [ "automatic-timezoned.service" ]; - time.timeZone = null; - services.automatic-timezoned.enable = true; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = pkgs.writeShellScript "fix-localtime-symlink" '' + target=$(${pkgs.coreutils}/bin/readlink /etc/localtime 2>/dev/null || true) + if [ -z "$target" ]; then + exit 0 + fi - systemd.services.automatic-timezoned = { - after = [ "dbus.socket" "systemd-timedated.service" "geoclue.service" ] - ++ lib.optional (cfg.persistDir != null) "timezone-restore.service"; - wants = [ "dbus.socket" "systemd-timedated.service" "geoclue.service" ] - ++ lib.optional (cfg.persistDir != null) "timezone-restore.service"; - serviceConfig = { - ExecStartPre = "${lib.getExe' pkgs.coreutils "sleep"} 5"; - Restart = "on-failure"; - RestartSec = "10s"; - }; + if [[ "$target" == /* ]]; then + exit 0 + fi + + abs_target="/etc/$target" + if [ -e "$abs_target" ]; then + ${pkgs.coreutils}/bin/ln -sf "$abs_target" /etc/localtime + fi + ''; }; - systemd.services.automatic-timezoned-geoclue-agent = { - after = [ "dbus.socket" ]; - wants = [ "dbus.socket" ]; + unitConfig = { + ConditionPathIsSymbolicLink = "/etc/localtime"; }; + }; - # Ensure anything using timedate1 sees restored timezone first. - systemd.services.systemd-timedated = lib.mkIf (cfg.persistDir != null) { - after = [ "timezone-restore.service" ]; - wants = [ "timezone-restore.service" ]; - requires = [ "timezone-restore.service" ]; - }; + systemd.paths.fix-localtime-symlink = { + description = "Watch /etc/localtime for changes"; + wantedBy = [ "multi-user.target" ]; - # Restore timezone from persistent storage on boot (fallback for offline boots) - systemd.services.timezone-restore = lib.mkIf (cfg.persistDir != null) { - description = "Restore timezone from persistent storage"; - wantedBy = [ "sysinit.target" ]; - - # NixOS activation may recreate /etc/localtime based on config. - # Run after activation so the restored timezone "wins" on offline boots. - after = [ - "local-fs.target" - "systemd-remount-fs.service" - "nixos-activation.service" - ]; - wants = [ "nixos-activation.service" ]; - - before = [ - "time-sync.target" - "automatic-timezoned.service" - "systemd-timedated.service" - ]; - - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - RequiresMountsFor = [ cfg.persistDir ]; - ExecStart = pkgs.writeShellScript "timezone-restore" '' - set -euo pipefail - persist_file="${persistFile}" - - if [ ! -f "$persist_file" ]; then - echo "No persisted timezone found, skipping restore" - exit 0 - fi - - tz=$(${pkgs.coreutils}/bin/cat "$persist_file") - if [ -z "$tz" ]; then - echo "Persisted timezone file is empty, skipping restore" - exit 0 - fi - - tzfile="${tzdata}/share/zoneinfo/$tz" - if [ ! -f "$tzfile" ]; then - echo "Invalid timezone '$tz' in persist file, skipping restore" - exit 0 - fi - - echo "Restoring timezone: $tz" - ${pkgs.coreutils}/bin/ln -sf "$tzfile" /etc/localtime - - # Some NixOS setups may generate /etc/timezone as a symlink into the store. - # Replace it so we don't fail the whole restore. - ${pkgs.coreutils}/bin/rm -f /etc/timezone - ${pkgs.coreutils}/bin/printf '%s\n' "$tz" > /etc/timezone - ''; - }; - }; - - # Save timezone whenever it changes - systemd.services.timezone-persist = lib.mkIf (cfg.persistDir != null) { - description = "Persist timezone to storage"; - - serviceConfig = { - Type = "oneshot"; - RequiresMountsFor = [ cfg.persistDir ]; - ExecStart = pkgs.writeShellScript "timezone-persist" '' - set -euo pipefail - ${pkgs.coreutils}/bin/mkdir -p "${cfg.persistDir}" - - # Try to read timezone from /etc/timezone first, fall back to parsing symlink - if [ -f /etc/timezone ]; then - tz=$(${pkgs.coreutils}/bin/cat /etc/timezone | ${pkgs.coreutils}/bin/tr -d '[:space:]') - else - target=$(${pkgs.coreutils}/bin/readlink /etc/localtime 2>/dev/null || true) - if [ -z "$target" ]; then - echo "Cannot determine timezone, skipping persist" - exit 0 - fi - # Extract timezone name from path like /nix/store/.../share/zoneinfo/America/Chicago - tz=$(echo "$target" | ${pkgs.gnused}/bin/sed -n 's|.*/zoneinfo/||p') - fi - - if [ -z "$tz" ]; then - echo "Cannot determine timezone, skipping persist" - exit 0 - fi - - persist_file="${persistFile}" - - echo "Persisting timezone: $tz" - echo "$tz" > "$persist_file" - ''; - }; - }; - - # Watch /etc/localtime and /etc/timezone for changes and trigger persist - systemd.paths.timezone-persist = lib.mkIf (cfg.persistDir != null) { - description = "Watch timezone changes to persist"; - wantedBy = [ "multi-user.target" ]; - - pathConfig = { - PathChanged = [ "/etc/localtime" "/etc/timezone" ]; - Unit = "timezone-persist.service"; - }; - }; - - systemd.services.fix-localtime-symlink = { - description = "Fix /etc/localtime symlink to be absolute"; - wantedBy = [ "multi-user.target" ]; - after = [ "automatic-timezoned.service" ]; - wants = [ "automatic-timezoned.service" ]; - - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = pkgs.writeShellScript "fix-localtime-symlink" '' - target=$(${pkgs.coreutils}/bin/readlink /etc/localtime 2>/dev/null || true) - if [ -z "$target" ]; then - exit 0 - fi - - if [[ "$target" == /* ]]; then - exit 0 - fi - - abs_target="/etc/$target" - if [ -e "$abs_target" ]; then - ${pkgs.coreutils}/bin/ln -sf "$abs_target" /etc/localtime - fi - ''; - }; - - unitConfig = { - ConditionPathIsSymbolicLink = "/etc/localtime"; - }; - }; - - systemd.paths.fix-localtime-symlink = { - description = "Watch /etc/localtime for changes"; - wantedBy = [ "multi-user.target" ]; - - pathConfig = { - PathChanged = "/etc/localtime"; - Unit = "fix-localtime-symlink.service"; - }; + pathConfig = { + PathChanged = "/etc/localtime"; + Unit = "fix-localtime-symlink.service"; }; }; } diff --git a/hosts/juni/flake.nix b/hosts/juni/flake.nix index 751ac7d8..4ec8ced1 100644 --- a/hosts/juni/flake.nix +++ b/hosts/juni/flake.nix @@ -84,7 +84,6 @@ inputs.common.nixosModules.hardening inputs.common.nixosModules.nix_options inputs.common.nixosModules.timezone_auto - ({ services.automatic-timezoned.persistDir = "/persist/var/lib/timezone-persist"; }) inputs.common.nixosModules.tty_caps_esc inputs.common.nixosModules.zsh inputs.common.nixosModules.tailnet