2026-02-05 12:03:24 -08:00
|
|
|
{ pkgs, ... }: {
|
2023-08-22 16:35:21 -06:00
|
|
|
home-manager.users.imalison = {
|
|
|
|
|
imports = [
|
|
|
|
|
./emacs.nix
|
2026-02-12 23:45:49 -08:00
|
|
|
./dotfiles-links.nix
|
2023-08-22 16:35:21 -06:00
|
|
|
];
|
2026-02-05 12:03:24 -08:00
|
|
|
|
2026-02-20 01:07:31 -08:00
|
|
|
programs.git.enable = true;
|
2026-03-26 21:28:00 -07:00
|
|
|
programs.git.signing.format = "openpgp";
|
2026-02-20 01:07:31 -08:00
|
|
|
programs.gh = {
|
|
|
|
|
enable = true;
|
|
|
|
|
settings.git_protocol = "ssh";
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-05 18:37:37 -08:00
|
|
|
# Hyprland config starts this target on login (see `dotfiles/config/hypr/hyprland.conf`).
|
|
|
|
|
systemd.user.targets.hyprland-session = {
|
|
|
|
|
Unit = {
|
|
|
|
|
Description = "Hyprland session (custom)";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-13 00:26:08 -08:00
|
|
|
# Rootless podman stores images/layers under ~/.local/share/containers.
|
|
|
|
|
# NixOS' `virtualisation.podman.autoPrune` only affects the rootful store,
|
|
|
|
|
# so we prune the per-user store with a user timer.
|
|
|
|
|
systemd.user.services.podman-auto-prune = {
|
|
|
|
|
Unit = {
|
|
|
|
|
Description = "Podman auto prune (rootless)";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Service = {
|
|
|
|
|
Type = "oneshot";
|
|
|
|
|
ExecStart = "${pkgs.podman}/bin/podman system prune -a -f";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd.user.timers.podman-auto-prune = {
|
|
|
|
|
Unit = {
|
|
|
|
|
Description = "Podman auto prune (rootless)";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Timer = {
|
|
|
|
|
OnCalendar = "daily";
|
|
|
|
|
Persistent = true;
|
|
|
|
|
RandomizedDelaySec = "1h";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Install = {
|
|
|
|
|
WantedBy = [ "timers.target" ];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-05 18:37:37 -08:00
|
|
|
systemd.user.services.hyprpaper = let
|
2026-02-20 08:08:11 -08:00
|
|
|
wallpaperDir = "/var/lib/syncthing/sync/Wallpaper/use";
|
2026-02-05 18:37:37 -08:00
|
|
|
waitForWayland = pkgs.writeShellScript "wait-for-wayland" ''
|
|
|
|
|
# Hyprpaper needs a Wayland socket. systemd "Condition*" checks are
|
|
|
|
|
# brittle here (they skip the unit entirely, with no retry) and also
|
|
|
|
|
# don't support expanding $WAYLAND_DISPLAY in paths.
|
|
|
|
|
for _ in {1..50}; do
|
|
|
|
|
if [ -n "$WAYLAND_DISPLAY" ] && [ -n "$XDG_RUNTIME_DIR" ] && [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
${pkgs.coreutils}/bin/sleep 0.1
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
echo "Wayland socket not ready: WAYLAND_DISPLAY=$WAYLAND_DISPLAY XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
'';
|
|
|
|
|
setWallpaper = pkgs.writeShellScript "set-hyprpaper-wallpaper" ''
|
|
|
|
|
# hyprpaper 0.8.x doesn't seem to apply `wallpaper = ...` entries from
|
|
|
|
|
# its config on startup reliably. Explicitly set the wallpaper via IPC
|
|
|
|
|
# once the hyprpaper socket is ready.
|
|
|
|
|
#
|
|
|
|
|
# NOTE: hyprctl hyprpaper currently ignores `--instance` and uses
|
|
|
|
|
# $HYPRLAND_INSTANCE_SIGNATURE to find hyprpaper's socket, so we rely on
|
|
|
|
|
# the environment imported by Hyprland on login.
|
|
|
|
|
set -u
|
|
|
|
|
|
|
|
|
|
runtimeDir="''${XDG_RUNTIME_DIR:-}"
|
|
|
|
|
sig="''${HYPRLAND_INSTANCE_SIGNATURE:-}"
|
|
|
|
|
sockPath=""
|
|
|
|
|
if [ -n "$runtimeDir" ] && [ -n "$sig" ]; then
|
|
|
|
|
sockPath="$runtimeDir/hypr/$sig/.hyprpaper.sock"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Don't fail the service if env isn't available yet.
|
|
|
|
|
if [ -z "$sockPath" ]; then
|
|
|
|
|
echo "set-hyprpaper-wallpaper: missing XDG_RUNTIME_DIR/HYPRLAND_INSTANCE_SIGNATURE" >&2
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
for _ in {1..50}; do
|
|
|
|
|
if [ -S "$sockPath" ]; then
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
${pkgs.coreutils}/bin/sleep 0.1
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [ ! -S "$sockPath" ]; then
|
|
|
|
|
echo "set-hyprpaper-wallpaper: hyprpaper socket not ready at $sockPath" >&2
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ ! -d "${wallpaperDir}" ]; then
|
|
|
|
|
echo "set-hyprpaper-wallpaper: wallpaper directory missing: ${wallpaperDir}" >&2
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
wallpaper="$(${pkgs.findutils}/bin/find "${wallpaperDir}" -type f -regextype posix-extended -iregex '.*\.(png|jpe?g|webp)$' -print0 | ${pkgs.coreutils}/bin/shuf -z -n 1 | ${pkgs.coreutils}/bin/tr -d '\0')"
|
|
|
|
|
|
|
|
|
|
if [ -z "$wallpaper" ]; then
|
|
|
|
|
echo "set-hyprpaper-wallpaper: no wallpapers found in ${wallpaperDir}" >&2
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Apply to all current monitors.
|
|
|
|
|
mons=$(/run/current-system/sw/bin/hyprctl -j monitors 2>/dev/null | ${pkgs.jq}/bin/jq -r '.[].name' 2>/dev/null || true)
|
|
|
|
|
if [ -z "$mons" ]; then
|
|
|
|
|
# Fallback to parsing non-JSON output.
|
|
|
|
|
mons=$(/run/current-system/sw/bin/hyprctl monitors 2>/dev/null | ${pkgs.gnugrep}/bin/grep -oE '^Monitor [^ ]+' | ${pkgs.coreutils}/bin/cut -d' ' -f2 || true)
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
for mon in $mons; do
|
|
|
|
|
# The socket file can exist before hyprpaper is actually ready to
|
|
|
|
|
# accept connections, so retry briefly.
|
|
|
|
|
for _ in {1..50}; do
|
|
|
|
|
if /run/current-system/sw/bin/hyprctl hyprpaper wallpaper "$mon,$wallpaper" >/dev/null 2>&1; then
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
${pkgs.coreutils}/bin/sleep 0.1
|
|
|
|
|
done
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
exit 0
|
|
|
|
|
'';
|
|
|
|
|
hyprpaperConf = pkgs.writeText "hyprpaper.conf" ''
|
|
|
|
|
ipc = true
|
|
|
|
|
splash = false
|
|
|
|
|
'';
|
|
|
|
|
in {
|
|
|
|
|
Unit = {
|
|
|
|
|
Description = "Hyprpaper (managed by home-manager)";
|
|
|
|
|
PartOf = [ "hyprland-session.target" ];
|
|
|
|
|
After = [ "hyprland-session.target" ];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Service = {
|
|
|
|
|
ExecStartPre = waitForWayland;
|
|
|
|
|
ExecStart = "${pkgs.hyprpaper}/bin/hyprpaper -c ${hyprpaperConf}";
|
|
|
|
|
ExecStartPost = setWallpaper;
|
|
|
|
|
Restart = "on-failure";
|
|
|
|
|
RestartSec = 1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Install = {
|
|
|
|
|
WantedBy = [ "hyprland-session.target" ];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-13 04:35:47 -08:00
|
|
|
systemd.user.services.tailscale-systray = {
|
|
|
|
|
Unit = {
|
|
|
|
|
Description = "Tailscale systray";
|
|
|
|
|
After = [ "graphical-session.target" "tray.target" ];
|
|
|
|
|
PartOf = [ "graphical-session.target" ];
|
|
|
|
|
Requires = [ "tray.target" ];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Service = {
|
|
|
|
|
ExecStart = "${pkgs.tailscale}/bin/tailscale systray";
|
|
|
|
|
Restart = "on-failure";
|
|
|
|
|
RestartSec = 3;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Install = {
|
|
|
|
|
WantedBy = [ "graphical-session.target" ];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-05 12:03:24 -08:00
|
|
|
xdg.desktopEntries.google-chrome-devtools = {
|
|
|
|
|
name = "Google Chrome (DevTools)";
|
|
|
|
|
genericName = "Web Browser";
|
|
|
|
|
comment = "Access the Internet";
|
|
|
|
|
icon = "google-chrome";
|
|
|
|
|
terminal = false;
|
|
|
|
|
type = "Application";
|
|
|
|
|
categories = [ "Network" "WebBrowser" ];
|
|
|
|
|
mimeType = [
|
|
|
|
|
"application/pdf"
|
|
|
|
|
"application/rdf+xml"
|
|
|
|
|
"application/rss+xml"
|
|
|
|
|
"application/xhtml+xml"
|
|
|
|
|
"application/xhtml_xml"
|
|
|
|
|
"application/xml"
|
|
|
|
|
"image/gif"
|
|
|
|
|
"image/jpeg"
|
|
|
|
|
"image/png"
|
|
|
|
|
"image/webp"
|
|
|
|
|
"text/html"
|
|
|
|
|
"text/xml"
|
|
|
|
|
"x-scheme-handler/http"
|
|
|
|
|
"x-scheme-handler/https"
|
|
|
|
|
"x-scheme-handler/google-chrome"
|
|
|
|
|
];
|
2026-04-11 11:58:01 -07:00
|
|
|
exec = "${pkgs.google-chrome}/bin/google-chrome-stable --user-data-dir=/home/imalison/.cache/google-chrome-devtools --remote-debugging-port=46649 --remote-allow-origins=http://127.0.0.1,http://localhost %U";
|
2026-02-05 12:03:24 -08:00
|
|
|
actions = {
|
|
|
|
|
new-window = {
|
|
|
|
|
name = "New Window";
|
2026-04-11 11:58:01 -07:00
|
|
|
exec = "${pkgs.google-chrome}/bin/google-chrome-stable --user-data-dir=/home/imalison/.cache/google-chrome-devtools --remote-debugging-port=46649 --remote-allow-origins=http://127.0.0.1,http://localhost";
|
2026-02-05 12:03:24 -08:00
|
|
|
};
|
|
|
|
|
new-private-window = {
|
|
|
|
|
name = "New Incognito Window";
|
2026-04-11 11:58:01 -07:00
|
|
|
exec = "${pkgs.google-chrome}/bin/google-chrome-stable --user-data-dir=/home/imalison/.cache/google-chrome-devtools --remote-debugging-port=46649 --remote-allow-origins=http://127.0.0.1,http://localhost --incognito";
|
2026-02-05 12:03:24 -08:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
2023-08-22 16:35:21 -06:00
|
|
|
};
|
|
|
|
|
}
|