Merge branch 'main' of gitea.moonarch.de:nevaforget/moonix
# Conflicts: # DECISIONS.md # modules/services.nix
This commit is contained in:
commit
e205ea6358
10 files changed
+438
-127
No files matched your search
+154
-47
@@ -1,7 +1,7 @@
|
||||
# ABOUTME: systemd system and user services for the desktop session.
|
||||
# ABOUTME: Mostly rebuilt from moonarch/defaults/etc/systemd/, whose units use FHS paths.
|
||||
|
||||
{ pkgs, lib, moonarchPkgs, ... }:
|
||||
{ config, pkgs, lib, moonarchPkgs, ... }:
|
||||
|
||||
let
|
||||
# Days after which the nixpkgs input counts as stale. nixpkgs carries no
|
||||
@@ -73,6 +73,88 @@ let
|
||||
read -r -p '[enter to close] ' _"
|
||||
'';
|
||||
};
|
||||
|
||||
# Shared by the two root oneshots below. Both read one state file and write a
|
||||
# single sysfs attribute, so everything else is taken away. Scored with
|
||||
# `systemd-analyze security`: 7.5 EXPOSED before, 0.9 SAFE after.
|
||||
#
|
||||
# Deliberately absent:
|
||||
# ProtectKernelTunables - remounts /sys read-only, which is exactly the
|
||||
# write both units exist for. ReadWritePaths is no fix, /sys/class/… is a
|
||||
# symlink into /sys/devices/….
|
||||
# PrivateUsers - capabilities inside a user namespace are namespace-local,
|
||||
# while the kernel's sysfs store handlers test against the initial one.
|
||||
#
|
||||
# ProtectSystem=strict is safe here: it leaves /dev, /proc and /sys writable,
|
||||
# and the state file is only ever read.
|
||||
sysfsWriterHardening = {
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
AmbientCapabilities = [ "" ];
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
PrivateNetwork = true;
|
||||
IPAddressDeny = "any";
|
||||
RestrictAddressFamilies = [ "AF_UNIX" ];
|
||||
ProtectClock = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
||||
KeyringMode = "private";
|
||||
UMask = "0077";
|
||||
};
|
||||
|
||||
# Shared by the Wayland session clients below: socket to the compositor,
|
||||
# nothing else. 9.4 UNSAFE before, 0.9 SAFE after, each one started under the
|
||||
# full set with `systemd-run --user` first.
|
||||
#
|
||||
# Deliberately absent:
|
||||
# ProtectHome - hides /home, /root *and /run/user*, where the Wayland
|
||||
# socket lives. Read-only fails too: connect() needs write on the inode.
|
||||
# PrivateUsers - maps the session uid to nobody, the socket is owned by the
|
||||
# real one.
|
||||
waylandClientHardening = {
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
AmbientCapabilities = [ "" ];
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ReadWritePaths = [ "%t" ];
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
PrivateNetwork = true;
|
||||
IPAddressDeny = "any";
|
||||
RestrictAddressFamilies = [ "AF_UNIX" ];
|
||||
RemoveIPC = true;
|
||||
ProtectClock = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
KeyringMode = "private";
|
||||
};
|
||||
in
|
||||
{
|
||||
# --- Audio ---
|
||||
@@ -100,27 +182,6 @@ in
|
||||
# --- 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
|
||||
@@ -137,15 +198,7 @@ in
|
||||
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;
|
||||
};
|
||||
} // sysfsWriterHardening;
|
||||
};
|
||||
|
||||
systemd.services.moonarch-camera = {
|
||||
@@ -156,14 +209,43 @@ in
|
||||
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;
|
||||
} // sysfsWriterHardening;
|
||||
};
|
||||
|
||||
# --- Session services ---
|
||||
#
|
||||
# Rebuilt rather than copied: the upstream units reference /usr/bin/,
|
||||
# which does not exist under Nix.
|
||||
|
||||
# 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 ];
|
||||
# Restart=on-failure alone spun 32 times through a dead Wayland socket after
|
||||
# a rebuild had sent niri SIGTERM: graphical-session-pre.target went down,
|
||||
# graphical-session.target stayed up, so the unit was still wanted and every
|
||||
# attempt died on "no Qt platform plugin could be initialized". Quickshell
|
||||
# refuses a relaunch itself ("crashed within 10 seconds"), but systemd never
|
||||
# reached the default limit of 5 starts per 10s at a 3s delay. Three tries
|
||||
# per 30s ends it after ~9s. The window slides, so a single crash after a
|
||||
# normal run still gets its restart — the point of running this as a unit.
|
||||
unitConfig = {
|
||||
StartLimitIntervalSec = 30;
|
||||
StartLimitBurst = 3;
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${lib.getExe pkgs.quickshell} -c moonarch";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 3;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -174,16 +256,41 @@ in
|
||||
partOf = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
# stasis runs its actions as `sh -lc <command>` and spawns niri and pactl
|
||||
# directly, all resolved against this PATH. pulseaudio is here for pactl
|
||||
# alone; it stays off systemPackages, see modules/desktop.nix.
|
||||
path = with pkgs; [ bash swaylock brightnessctl niri pulseaudio ];
|
||||
# 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
|
||||
systemd
|
||||
];
|
||||
# No waylandClientHardening: swaylock is a child of this unit and
|
||||
# authenticates through the setuid-root unix_chkpwd. `systemd --user` builds
|
||||
# mount namespaces through an unprivileged user namespace, whose mounts are
|
||||
# nosuid — the wrapper loses setuid and the session is no longer unlockable.
|
||||
# Measured per directive with `echo wrong | unix_chkpwd $USER nonull`: exit
|
||||
# 7 means shadow was read, 9 means no access. Only these five return 7, so
|
||||
# only they are set. 9.4 → 9.0; there is no more to get here.
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${lib.getExe pkgs.stasis}";
|
||||
ExecReload = "${lib.getExe pkgs.stasis} reload";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 2;
|
||||
IPAddressDeny = "any";
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
RemoveIPC = true;
|
||||
KeyringMode = "private";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -197,7 +304,7 @@ in
|
||||
ExecStart = "${pkgs.wl-clipboard}/bin/wl-paste --watch ${pkgs.cliphist}/bin/cliphist -db-path %t/cliphist/db store";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 3;
|
||||
};
|
||||
} // waylandClientHardening;
|
||||
};
|
||||
|
||||
systemd.user.services.cliphist-image = {
|
||||
@@ -210,7 +317,7 @@ in
|
||||
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;
|
||||
};
|
||||
} // waylandClientHardening;
|
||||
};
|
||||
|
||||
# Night light stays disabled by default: it is a user toggle. Enabling it
|
||||
@@ -219,7 +326,7 @@ in
|
||||
systemd.user.services.wlsunset = {
|
||||
description = "Wlsunset night light (blue light filter)";
|
||||
partOf = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" "kanshi.service" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = [ ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
@@ -227,7 +334,7 @@ in
|
||||
ExecStart = "${lib.getExe pkgs.wlsunset} -T 6500 -t 5000 -S 00:00 -s 00:01";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 3;
|
||||
};
|
||||
} // waylandClientHardening;
|
||||
};
|
||||
|
||||
# --- Flake age ---
|
||||
|
||||
Reference in new issue
Block a user