From 07b7e2bb052af08a63fde990eb36e1d9ce8a3f36 Mon Sep 17 00:00:00 2001 From: soraefir Date: Fri, 11 Sep 2026 14:25:45 +0200 Subject: [PATCH] Better graphical start --- modules/home/base/default.nix | 1 + modules/home/base/network-watchdog.nix | 52 ++++++++++++++ modules/home/gui/apps/mpv/default.nix | 33 +-------- modules/home/gui/apps/pipewire/default.nix | 2 + modules/home/gui/base/default.nix | 20 +++++- modules/home/wayland/apps/kanshi/default.nix | 2 + modules/home/wayland/hyprland/config.nix | 12 +--- modules/home/xorg/bspwm/config.nix | 2 - modules/nixos/gui/audio/default.nix | 3 +- packages/default.nix | 2 +- .../jellyfin-mpv-shim-watchdog/default.nix | 51 -------------- packages/network-watchdog/default.nix | 69 +++++++++++++++++++ systems/valinor/hardware.nix | 2 +- 13 files changed, 152 insertions(+), 99 deletions(-) create mode 100644 modules/home/base/network-watchdog.nix delete mode 100644 packages/jellyfin-mpv-shim-watchdog/default.nix create mode 100644 packages/network-watchdog/default.nix diff --git a/modules/home/base/default.nix b/modules/home/base/default.nix index 0bf9d2a..92e1f65 100644 --- a/modules/home/base/default.nix +++ b/modules/home/base/default.nix @@ -1,4 +1,5 @@ { lib, config, pkgs, ... }: { + imports = [ ./network-watchdog.nix ]; #environment.sessionVariables.SOPS_AGE_KEY_FILE = keyFilePath; systemd.user.startServices = lib.mkIf pkgs.stdenv.isLinux "sd-switch"; diff --git a/modules/home/base/network-watchdog.nix b/modules/home/base/network-watchdog.nix new file mode 100644 index 0000000..da28aa5 --- /dev/null +++ b/modules/home/base/network-watchdog.nix @@ -0,0 +1,52 @@ +{ lib, config, pkgs, ... }: +let + cfg = config.services.network-watchdog; + watchdog = "${pkgs.custom.network-watchdog}/bin/network-watchdog"; + units = map (name: "${name}.service") cfg.units; +in { + # User services that only make sense while online: GUI clients that hold a + # socket to a server (jellyfin-mpv-shim, nextcloud-client). Their upstream + # units have no network ordering (network-online.target is a system target, + # out of reach for user units), so they happily start while offline — + # silently dead — and nothing notices a connection dropped by a network + # blip. Gate each start on the network being up, restart on failure, and let + # a watchdog catch the "up but not connected" state that systemd can't see. + # See pkgs.custom.network-watchdog. + options.services.network-watchdog.units = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "jellyfin-mpv-shim" ]; + description = "User services (no .service suffix) to gate on the network and keep connected."; + }; + + config = lib.mkIf (cfg.units != [ ]) { + systemd.user.services = lib.genAttrs cfg.units (name: { + Service = { + ExecStartPre = "${watchdog} wait-online ${name}.service"; + TimeoutStartSec = "infinity"; # stay in start-pre until the network is back + Restart = lib.mkDefault "on-failure"; + RestartSec = lib.mkDefault 5; + }; + }) // { + network-watchdog = { + Unit = { + Description = "Restart network services that are up but not connected"; + After = units; + }; + Service = { + Type = "oneshot"; + ExecStart = "${watchdog} check ${lib.concatStringsSep " " units}"; + }; + }; + }; + + systemd.user.timers.network-watchdog = { + Unit.Description = "Periodic health check for network services"; + Timer = { + OnActiveSec = "60s"; + OnUnitActiveSec = "60s"; + }; + Install.WantedBy = [ "timers.target" ]; + }; + }; +} diff --git a/modules/home/gui/apps/mpv/default.nix b/modules/home/gui/apps/mpv/default.nix index aad5ab1..8968d43 100755 --- a/modules/home/gui/apps/mpv/default.nix +++ b/modules/home/gui/apps/mpv/default.nix @@ -1,7 +1,6 @@ { lib, config, pkgs, ... }: let configuredMpv = config.programs.mpv.finalPackage; - shimWatchdog = pkgs.custom.jellyfin-mpv-shim-watchdog; in{ config = lib.mkIf (config.syscfg.make.gui) { @@ -83,36 +82,6 @@ in{ programs.yt-dlp.enable = true; - # The upstream jellyfin-mpv-shim service has no Restart and no network - # ordering: it happily starts while offline (silently dead) and never - # recovers a connection dropped by a network blip. Gate its start on the - # network being up, and let a watchdog notice the "up but not connected" - # state that systemd can't see. See pkgs.custom.jellyfin-mpv-shim-watchdog. - systemd.user.services.jellyfin-mpv-shim.Service = { - ExecStartPre = "${shimWatchdog}/bin/jellyfin-mpv-shim-watchdog wait-online"; - TimeoutStartSec = "infinity"; # stay in start-pre until the network is back - Restart = "on-failure"; - RestartSec = 5; - }; - - systemd.user.services.jellyfin-mpv-shim-watchdog = { - Unit = { - Description = "Restart jellyfin-mpv-shim when it is up but not connected"; - After = [ "jellyfin-mpv-shim.service" ]; - }; - Service = { - Type = "oneshot"; - ExecStart = "${shimWatchdog}/bin/jellyfin-mpv-shim-watchdog check"; - }; - }; - - systemd.user.timers.jellyfin-mpv-shim-watchdog = { - Unit.Description = "Periodic health check for jellyfin-mpv-shim"; - Timer = { - OnActiveSec = "60s"; - OnUnitActiveSec = "60s"; - }; - Install.WantedBy = [ "timers.target" ]; - }; + services.network-watchdog.units = [ "jellyfin-mpv-shim" ]; }; } diff --git a/modules/home/gui/apps/pipewire/default.nix b/modules/home/gui/apps/pipewire/default.nix index febbd81..c02dcda 100755 --- a/modules/home/gui/apps/pipewire/default.nix +++ b/modules/home/gui/apps/pipewire/default.nix @@ -1,6 +1,8 @@ { lib, config, ... }: { config = lib.mkIf (config.syscfg.make.gui) { + services.easyeffects.enable = true; + xdg.configFile."pipewire/pipewire-pulse.conf.d/desktop.conf".text = '' context.modules = [ { name = libpipewire-module-loopback diff --git a/modules/home/gui/base/default.nix b/modules/home/gui/base/default.nix index db51071..1ca78a1 100644 --- a/modules/home/gui/base/default.nix +++ b/modules/home/gui/base/default.nix @@ -1,7 +1,25 @@ { lib, config, pkgs, ... }: { config = lib.mkIf (config.syscfg.make.gui) { - services.nextcloud-client.enable = true; + services.nextcloud-client = { + enable = true; + startInBackground = true; + }; + services.network-watchdog.units = [ "nextcloud-client" ]; + + systemd.user.services.keepassxc = lib.mkIf (config.usercfg.wm != "X11") { + Unit = { + Description = "KeePassXC"; + After = [ "graphical-session.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + Service = { + ExecStart = "${pkgs.keepassxc}/bin/keepassxc"; + Restart = "on-failure"; + RestartSec = 5; + }; + Install.WantedBy = [ "graphical-session.target" ]; + }; home.packages = with pkgs; [ thunar diff --git a/modules/home/wayland/apps/kanshi/default.nix b/modules/home/wayland/apps/kanshi/default.nix index 0fdaf16..8b9fb6c 100644 --- a/modules/home/wayland/apps/kanshi/default.nix +++ b/modules/home/wayland/apps/kanshi/default.nix @@ -20,6 +20,8 @@ let in { config = lib.mkIf (config.usercfg.wm == "Wayland") { + systemd.user.services.kanshi.Unit.After = lib.mkForce [ config.services.kanshi.systemdTarget "awww.service" ]; + services.kanshi = { enable = true; systemdTarget = "graphical-session.target"; diff --git a/modules/home/wayland/hyprland/config.nix b/modules/home/wayland/hyprland/config.nix index 7ffd466..b2c1c20 100644 --- a/modules/home/wayland/hyprland/config.nix +++ b/modules/home/wayland/hyprland/config.nix @@ -24,23 +24,15 @@ startupScript = pkgs.writeShellScriptBin "hyprland-start" '' - # The eww bar is not started here: eww.service opens it with the - # graphical session and kanshi places it per profile (see apps/eww). - ${pkgs.awww}/bin/awww-daemon & - ${pkgs.awww}/bin/awww restore & - sleep 2 - keepassxc & firefox & - easyeffects --gapplication-service & - - sleep 2 - nextcloud & # telegram-desktop & # discord & ''; in { config = lib.mkIf (config.usercfg.wm == "Wayland") { + services.awww.enable = true; + wayland.windowManager.hyprland = { enable = true; xwayland.enable = true; diff --git a/modules/home/xorg/bspwm/config.nix b/modules/home/xorg/bspwm/config.nix index 91e04b8..b27050e 100755 --- a/modules/home/xorg/bspwm/config.nix +++ b/modules/home/xorg/bspwm/config.nix @@ -108,7 +108,6 @@ bspc desktop -f 0 telegram-desktop & - nextcloud & #flameshot & sleep 2 @@ -117,7 +116,6 @@ ibus-daemon -drx - easyeffects --gapplication-service & bspc desktop -f 0 ''; diff --git a/modules/nixos/gui/audio/default.nix b/modules/nixos/gui/audio/default.nix index 70d5926..8117f4d 100644 --- a/modules/nixos/gui/audio/default.nix +++ b/modules/nixos/gui/audio/default.nix @@ -13,6 +13,7 @@ in { # wireplumber.enable = true; }; - environment.systemPackages = with pkgs; [ easyeffects alsa-utils ]; + programs.dconf.enable = true; + environment.systemPackages = with pkgs; [ alsa-utils ]; }; } diff --git a/packages/default.nix b/packages/default.nix index 0067a85..8b2f31a 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -8,5 +8,5 @@ eww-bar = pkgs.callPackage ./eww-bar { }; - jellyfin-mpv-shim-watchdog = pkgs.callPackage ./jellyfin-mpv-shim-watchdog { }; + network-watchdog = pkgs.callPackage ./network-watchdog { }; } diff --git a/packages/jellyfin-mpv-shim-watchdog/default.nix b/packages/jellyfin-mpv-shim-watchdog/default.nix deleted file mode 100644 index d0c866b..0000000 --- a/packages/jellyfin-mpv-shim-watchdog/default.nix +++ /dev/null @@ -1,51 +0,0 @@ -{ writeShellScriptBin -, coreutils -, findutils -, gnugrep -, iproute2 -, systemd -}: - -# jellyfin-mpv-shim keeps a persistent socket to the Jellyfin server. When the -# machine drops off the network that socket dies, but the process stays alive -# and the unit stays "active" — running yet deaf to cast requests, the "up but -# not working, restart it by hand" case. systemd can't see this: nothing -# crashes and the shim speaks no health protocol, so we probe the real thing — -# is its MainPID still holding a live (established, non-loopback) connection? -# -# check (default) one tick, run by a 60s timer: restart if online but -# not connected, rate-limited to one restart per 5 min -# wait-online block until a real address exists; used as ExecStartPre so the -# unit only starts once online - -writeShellScriptBin "jellyfin-mpv-shim-watchdog" '' - set -u - service="jellyfin-mpv-shim.service" - stamp="''${XDG_RUNTIME_DIR:-/run/user/$(${coreutils}/bin/id -u)}/jellyfin-mpv-shim-watchdog.stamp" - - # A global-scope address on a non-loopback interface — true for a LAN IP too, - # so this needs no server URL and works whether Jellyfin is local or remote. - network_up() { ${iproute2}/bin/ip -o addr show scope global up 2>/dev/null | ${gnugrep}/bin/grep -q .; } - - if [ "''${1:-check}" = wait-online ]; then - while ! network_up; do ${coreutils}/bin/sleep 5; done - exit 0 - fi - - # Healthy: MainPID owns an established socket to something other than loopback. - pid="$(${systemd}/bin/systemctl --user show -p MainPID --value "$service" 2>/dev/null)" - if [ "''${pid:-0}" != 0 ] && ${iproute2}/bin/ss -tnpH state established 2>/dev/null \ - | ${gnugrep}/bin/grep "pid=$pid," \ - | ${gnugrep}/bin/grep -qv -e '127\.0\.0\.1' -e '\[::1\]'; then - exit 0 - fi - - # Not connected: only worth restarting once the network is actually back... - network_up || exit 0 - # ...and not if we already restarted within the last 5 minutes. - [ -e "$stamp" ] && [ -n "$(${findutils}/bin/find "$stamp" -mmin -5 2>/dev/null)" ] && exit 0 - - : > "$stamp" - echo "jellyfin-mpv-shim-watchdog: online but no live connection -> restarting $service" - ${systemd}/bin/systemctl --user restart "$service" -'' diff --git a/packages/network-watchdog/default.nix b/packages/network-watchdog/default.nix new file mode 100644 index 0000000..f8f98a3 --- /dev/null +++ b/packages/network-watchdog/default.nix @@ -0,0 +1,69 @@ +{ writeShellScriptBin +, coreutils +, findutils +, gnugrep +, iproute2 +, systemd +}: + +# For user services that only make sense while online (jellyfin-mpv-shim, +# nextcloud-client, ...). They keep a persistent socket to their server; when +# the machine drops off the network that socket dies, but the process stays +# alive and the unit stays "active" — running yet deaf, the "up but not +# working, restart it by hand" case. systemd can't see this: nothing crashes +# and the programs speak no health protocol, so we probe the real thing — is +# the MainPID still holding a live (established, non-loopback) connection? +# +# wait-online UNIT block until a real address exists; used as ExecStartPre +# so UNIT only starts once online. Also stamps the start +# time so `check` leaves a freshly (re)started UNIT alone. +# check UNIT... one tick, run by a 60s timer: restart every UNIT that is +# active but not connected, at most once per 5 minutes. +# +# Wired up by the services.network-watchdog home-manager module. + +writeShellScriptBin "network-watchdog" '' + set -u + stampdir="''${XDG_RUNTIME_DIR:-/run/user/$(${coreutils}/bin/id -u)}/network-watchdog" + ${coreutils}/bin/mkdir -p "$stampdir" + + usage() { echo "usage: network-watchdog wait-online UNIT | check UNIT..." >&2; exit 64; } + + # A global-scope address on a non-loopback interface — true for a LAN IP too, + # so this needs no server URL and works whether the server is local or remote. + network_up() { ${iproute2}/bin/ip -o addr show scope global up 2>/dev/null | ${gnugrep}/bin/grep -q .; } + + # Healthy: MainPID owns an established socket to something other than loopback. + connected() { + pid="$(${systemd}/bin/systemctl --user show -p MainPID --value "$1" 2>/dev/null)" + [ "''${pid:-0}" != 0 ] && ${iproute2}/bin/ss -tnpH state established 2>/dev/null \ + | ${gnugrep}/bin/grep "pid=$pid," \ + | ${gnugrep}/bin/grep -qv -e '127\.0\.0\.1' -e '\[::1\]' + } + + # Stamped within the last 5 minutes: still connecting, or already restarted. + recently_started() { [ -n "$(${findutils}/bin/find "$stampdir/$1" -mmin -5 2>/dev/null)" ]; } + + [ $# -ge 1 ] || usage + cmd="$1"; shift + case "$cmd" in + wait-online) + [ $# -eq 1 ] || usage + while ! network_up; do ${coreutils}/bin/sleep 5; done + : > "$stampdir/$1" + ;; + check) + # Nothing can be connected while offline, and restarting wouldn't help. + network_up || exit 0 + for unit; do + # Not "active" (inactive, failed, or still in wait-online): systemd's job, not ours. + ${systemd}/bin/systemctl --user is-active --quiet "$unit" || continue + recently_started "$unit" && continue + connected "$unit" && continue + echo "network-watchdog: $unit is online but has no live connection -> restarting" + ${systemd}/bin/systemctl --user restart "$unit" # its wait-online refreshes the stamp + done + ;; + *) usage ;; + esac +'' diff --git a/systems/valinor/hardware.nix b/systems/valinor/hardware.nix index c7a04d5..e1ddbc3 100755 --- a/systems/valinor/hardware.nix +++ b/systems/valinor/hardware.nix @@ -5,7 +5,7 @@ [ "nvme" "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" ]; boot.initrd.kernelModules = [ ]; boot.kernelModules = [ "v4l2loopback" "kvm-amd" ]; - #boot.kernelPackages = pkgs.linuxPackages_latest; + boot.kernelPackages = pkgs.linuxPackages_latest; boot.extraModulePackages = with config.boot.kernelPackages; [ v4l2loopback ]; boot.extraModprobeConfig = ''