Compare commits

..

2 commits

Author SHA1 Message Date
RingOfStorms (Joshua Bell)
c0a1757cab Merge branch 'master' of ssh://git.joshuabell.xyz:3032/ringofstorms/dotfiles 2026-01-13 14:08:43 -06:00
RingOfStorms (Joshua Bell)
8be372e076 services.automatic-timezoned: Add persistDir and timezone restore 2026-01-13 14:08:42 -06:00
6 changed files with 215 additions and 91 deletions

View file

@ -1,5 +1,34 @@
{ lib, pkgs, ... }:
{ config, lib, pkgs, ... }:
let
cfg = config.services.automatic-timezoned;
persistFile = if cfg.persistDir == null then null else "${cfg.persistDir}/timezone";
tzdata = pkgs.tzdata;
in
{
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.
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.
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).
'';
};
config = {
assertions = [
{
assertion = cfg.persistDir == null || lib.hasPrefix "/" cfg.persistDir;
message = "services.automatic-timezoned.persistDir must be an absolute path";
}
];
services.dbus.enable = lib.mkDefault true;
services.geoclue2.enable = true;
@ -7,8 +36,10 @@
services.automatic-timezoned.enable = true;
systemd.services.automatic-timezoned = {
after = [ "dbus.socket" "systemd-timedated.service" "geoclue.service" ];
wants = [ "dbus.socket" "systemd-timedated.service" "geoclue.service" ];
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";
@ -21,6 +52,117 @@
wants = [ "dbus.socket" ];
};
# 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" ];
};
# 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" ];
@ -61,4 +203,5 @@
Unit = "fix-localtime-symlink.service";
};
};
};
}

View file

@ -206,16 +206,6 @@ in
'';
})
(mkIf ((length cfg.wallpapers) > 0) {
environment.etc."xdg/plasma-org.kde.plasma.desktop-appletsrc".text =
let
wallpaperPath = builtins.head cfg.wallpapers;
in
''
[Containments][1][Wallpaper][org.kde.image][General]
Image=file://${wallpaperPath}
'';
})
# GPU blocks
(mkIf cfg.gpu.amd.enable {

View file

@ -5,7 +5,9 @@
}:
let
cfg = osConfig.ringofstorms.dePlasma;
inherit (lib) mkIf;
inherit (lib) mkIf optionalAttrs;
# Get the first wallpaper from the list if available
wallpaper = if (builtins.length cfg.wallpapers) > 0 then builtins.head cfg.wallpapers else null;
in
{
imports = [
@ -294,6 +296,8 @@ in
lookAndFeel = "org.kde.breezedark.desktop";
theme = "breeze-dark";
cursor.theme = "breeze_cursors";
} // optionalAttrs (wallpaper != null) {
wallpaper = wallpaper;
};
configFile = {

36
hosts/juni/flake.lock generated
View file

@ -38,40 +38,28 @@
},
"common": {
"locked": {
"dir": "flakes/common",
"lastModified": 1768255305,
"narHash": "sha256-XcXl5M0WNYhCCqE9qc9Aj2/2Jb/T0NHZnu2ZuVBvlHw=",
"ref": "refs/heads/master",
"rev": "15769eda748f6fcc6fdab04f79f14ed9b1ffc548",
"revCount": 1125,
"type": "git",
"url": "https://git.joshuabell.xyz/ringofstorms/dotfiles"
"path": "../../flakes/common",
"type": "path"
},
"original": {
"dir": "flakes/common",
"type": "git",
"url": "https://git.joshuabell.xyz/ringofstorms/dotfiles"
}
"path": "../../flakes/common",
"type": "path"
},
"parent": []
},
"de_plasma": {
"inputs": {
"plasma-manager": "plasma-manager"
},
"locked": {
"dir": "flakes/de_plasma",
"lastModified": 1768255305,
"narHash": "sha256-XcXl5M0WNYhCCqE9qc9Aj2/2Jb/T0NHZnu2ZuVBvlHw=",
"ref": "refs/heads/master",
"rev": "15769eda748f6fcc6fdab04f79f14ed9b1ffc548",
"revCount": 1125,
"type": "git",
"url": "https://git.joshuabell.xyz/ringofstorms/dotfiles"
"path": "../../flakes/de_plasma",
"type": "path"
},
"original": {
"dir": "flakes/de_plasma",
"type": "git",
"url": "https://git.joshuabell.xyz/ringofstorms/dotfiles"
}
"path": "../../flakes/de_plasma",
"type": "path"
},
"parent": []
},
"flatpaks": {
"inputs": {

View file

@ -9,16 +9,16 @@
impermanence.url = "github:nix-community/impermanence";
# Use relative to get current version for testin
# common.url = "path:../../flakes/common";
common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/common";
common.url = "path:../../flakes/common";
# common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/common";
# secrets-bao.url = "path:../../flakes/secrets-bao";
secrets-bao.url = "path:../../flakes/secrets-bao";
# flatpaks.url = "path:../../flakes/flatpaks";
flatpaks.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/flatpaks";
# beszel.url = "path:../../flakes/beszel";
beszel.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/beszel";
# de_plasma.url = "path:../../flakes/de_plasma";
de_plasma.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/de_plasma";
de_plasma.url = "path:../../flakes/de_plasma";
# de_plasma.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/de_plasma";
opencode.url = "github:sst/opencode";
ros_neovim.url = "git+https://git.joshuabell.xyz/ringofstorms/nvim";
@ -63,8 +63,8 @@
gpu.intel.enable = true;
sddm.autologinUser = "josh";
wallpapers = [
../../_shared_assets/wallpapers/pixel_neon.png
../../_shared_assets/wallpapers/pixel_neon_v.png
../../hosts/_shared_assets/wallpapers/pixel_neon.png
../../hosts/_shared_assets/wallpapers/pixel_neon_v.png
];
};
})
@ -84,6 +84,7 @@
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

View file

@ -32,8 +32,6 @@
files = [
"/machine-key.json"
"/etc/machine-id"
"/etc/localtime"
"/etc/timezone"
"/etc/adjtime"
# NOTE: if you want mutable passwords across reboots, persist these,
# but you must do a one-time migration (see notes in chat).