Compare commits

..

No commits in common. "c0a1757cab2ed794e2a611ccc9d0e5dd12d9030c" and "c408693861f5aceca177628f3480d9ccd87c42fb" have entirely different histories.

6 changed files with 91 additions and 215 deletions

View file

@ -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";
};
};
}

View file

@ -206,6 +206,16 @@ 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,9 +5,7 @@
}:
let
cfg = osConfig.ringofstorms.dePlasma;
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;
inherit (lib) mkIf;
in
{
imports = [
@ -296,8 +294,6 @@ 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,28 +38,40 @@
},
"common": {
"locked": {
"path": "../../flakes/common",
"type": "path"
"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"
},
"original": {
"path": "../../flakes/common",
"type": "path"
},
"parent": []
"dir": "flakes/common",
"type": "git",
"url": "https://git.joshuabell.xyz/ringofstorms/dotfiles"
}
},
"de_plasma": {
"inputs": {
"plasma-manager": "plasma-manager"
},
"locked": {
"path": "../../flakes/de_plasma",
"type": "path"
"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"
},
"original": {
"path": "../../flakes/de_plasma",
"type": "path"
},
"parent": []
"dir": "flakes/de_plasma",
"type": "git",
"url": "https://git.joshuabell.xyz/ringofstorms/dotfiles"
}
},
"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 = [
../../hosts/_shared_assets/wallpapers/pixel_neon.png
../../hosts/_shared_assets/wallpapers/pixel_neon_v.png
../../_shared_assets/wallpapers/pixel_neon.png
../../_shared_assets/wallpapers/pixel_neon_v.png
];
};
})
@ -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

View file

@ -32,6 +32,8 @@
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).