# ABOUTME: systemd system and user services for the desktop session. # ABOUTME: Rebuilt from moonarch/defaults/etc/systemd/, whose units use FHS paths. { config, pkgs, lib, moonarchPkgs, ... }: { # --- Audio --- services.pipewire = { enable = true; alsa.enable = true; alsa.support32Bit = true; pulse.enable = true; jack.enable = true; }; # --- Hardware --- hardware.bluetooth.enable = true; services.blueman.enable = true; services.udisks2.enable = true; services.upower.enable = true; services.gvfs.enable = true; services.fwupd.enable = true; # power-profiles-daemon over auto-cpufreq: on amd-pstate in active mode the # platform decides the frequency from the min/max and EPP registers alone, so # the governor is the wrong knob. ppd writes EPP and the ACPI platform_profile; # where neither exists it falls back to a driver that touches no hardware. services.power-profiles-daemon.enable = true; # --- Firewall, replaces the ufw calls in post-install.sh --- networking.firewall.enable = true; # --- Session services --- # # Rebuilt rather than copied: the upstream units reference /usr/bin/, # which does not exist under Nix. systemd.user.services.kanshi = { description = "Dynamic display configuration"; partOf = [ "graphical-session.target" ]; after = [ "graphical-session.target" ]; wantedBy = [ "graphical-session.target" ]; # Without a config kanshi exits with a parse error and, with # Restart=on-failure, spins. Monitor profiles are per machine. unitConfig.ConditionPathExists = "%h/.config/kanshi/config"; serviceConfig = { Type = "simple"; ExecStart = lib.getExe pkgs.kanshi; Restart = "on-failure"; RestartSec = 3; }; }; # --- System services from defaults/etc/systemd/system/ --- # # Rebuilt rather than copied, same reason as the user units: the originals # reference /usr/bin. systemd.services.moonarch-batsaver = { description = "Restore battery conservation mode threshold"; after = [ "sysinit.target" ]; wantedBy = [ "multi-user.target" ]; unitConfig.ConditionPathExists = [ "/sys/class/power_supply/BAT0/charge_control_end_threshold" "/var/lib/moonarch/batsaver-threshold" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${moonarchPkgs.moonarch-scripts}/bin/moonarch-batsaver-restore"; NoNewPrivileges = true; ProtectHome = true; PrivateTmp = true; ProtectKernelModules = true; ProtectControlGroups = true; RestrictNamespaces = true; RestrictRealtime = true; LockPersonality = true; }; }; systemd.services.moonarch-camera = { description = "Switch off all cameras on boot"; after = [ "sysinit.target" ]; wantedBy = [ "multi-user.target" ]; unitConfig.ConditionPathExistsGlob = "/sys/class/video4linux/video*"; serviceConfig = { Type = "oneshot"; ExecStart = "${moonarchPkgs.moonarch-scripts}/bin/moonarch-camera-boot"; NoNewPrivileges = true; ProtectHome = true; PrivateTmp = true; ProtectKernelModules = true; ProtectControlGroups = true; RestrictNamespaces = true; RestrictRealtime = true; LockPersonality = true; }; }; # The bar, launcher and every popout. A unit rather than a spawn-at-startup so # a crash does not leave the session without a bar until the next login. systemd.user.services.quickshell = { description = "Quickshell bar and shell"; partOf = [ "graphical-session.target" ]; after = [ "graphical-session.target" ]; wantedBy = [ "graphical-session.target" ]; # The widgets shell out to niri, nmcli, powerprofilesctl, the moonarch # scripts and a dozen more, and which ones is a property of the QML, not of # this file. As a spawn-at-startup it inherited the session PATH; the unit # gets the same set rather than a list that goes stale on the next widget. path = [ config.system.path ]; serviceConfig = { Type = "simple"; ExecStart = "${lib.getExe pkgs.quickshell} -c moonarch"; Restart = "on-failure"; RestartSec = 3; }; }; # nixpkgs ships the package but no module, and the package carries no unit of # its own, so it is declared here. Mirrors /usr/lib/systemd/user/stasis.service. systemd.user.services.stasis = { description = "Stasis Wayland Idle Manager"; partOf = [ "graphical-session.target" ]; after = [ "graphical-session.target" ]; wantedBy = [ "graphical-session.target" ]; # Every binary a configured action names has to be listed here. stasis does # not run the actions through a login shell, so /etc/profile never applies # and nothing resolves via /run/current-system/sw/bin. A missing binary is # silent: the spawn fails, stasis logs the action as started, sees the # immediate exit, treats the step as finished and stops there — which is how # swaylock was absent for the whole idle chain while `niri msg` worked. # pactl is why pulseaudio appears at all — it stays off systemPackages, see # modules/desktop.nix. brightnessctl is for the laptop's brightness step. path = with pkgs; [ bashNonInteractive niri pulseaudio libnotify swaylock brightnessctl ]; serviceConfig = { Type = "simple"; ExecStart = "${lib.getExe pkgs.stasis}"; ExecReload = "${lib.getExe pkgs.stasis} reload"; Restart = "on-failure"; RestartSec = 2; }; }; systemd.user.services.cliphist-text = { description = "Clipboard history manager (text)"; partOf = [ "graphical-session.target" ]; wantedBy = [ "graphical-session.target" ]; serviceConfig = { Type = "simple"; ExecStartPre = "${pkgs.runtimeShell} -c 'mkdir -p $XDG_RUNTIME_DIR/cliphist && ${pkgs.cliphist}/bin/cliphist wipe'"; ExecStart = "${pkgs.wl-clipboard}/bin/wl-paste --watch ${pkgs.cliphist}/bin/cliphist -db-path %t/cliphist/db store"; Restart = "on-failure"; RestartSec = 3; }; }; systemd.user.services.cliphist-image = { description = "Clipboard history manager (image)"; partOf = [ "graphical-session.target" ]; after = [ "cliphist-text.service" ]; wantedBy = [ "graphical-session.target" ]; serviceConfig = { Type = "simple"; ExecStart = "${pkgs.wl-clipboard}/bin/wl-paste --type image --watch ${pkgs.cliphist}/bin/cliphist -db-path %t/cliphist/db store"; Restart = "on-failure"; RestartSec = 3; }; }; # Night light stays disabled by default: it is a user toggle. Enabling it # here would create a system-scope symlink that overrides any # `systemctl --user disable`. systemd.user.services.wlsunset = { description = "Wlsunset night light (blue light filter)"; partOf = [ "graphical-session.target" ]; after = [ "graphical-session.target" "kanshi.service" ]; wantedBy = [ ]; serviceConfig = { Type = "simple"; ExecStartPre = "${pkgs.coreutils}/bin/sleep 2"; ExecStart = "${lib.getExe pkgs.wlsunset} -T 6500 -t 5000 -S 00:00 -s 00:01"; Restart = "on-failure"; RestartSec = 3; }; }; # The polkit agent is not a service here: niri spawns it via # spawn-at-startup in defaults/xdg/niri/config.kdl. Only the FHS path it # references has to exist — see modules/desktop.nix. }