{ config, lib, pkgs, service_configs, ... }: let cgroupDir = "/sys/fs/cgroup/system.slice/xmrig.service"; cgroupFreeze = "${cgroupDir}/cgroup.freeze"; in lib.mkIf config.services.xmrig.enable { systemd.services.xmrig-auto-pause = { description = "Auto-pause xmrig via cgroup freezer when other services need CPU"; after = [ "xmrig.service" ]; # PartOf cascades stop/restart: when xmrig stops (deploy, apcupsd battery, # manual), systemd stops auto-pause first and ExecStop thaws xmrig so # xmrig's own stop does not hang on a frozen cgroup. partOf = [ "xmrig.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${pkgs.python3}/bin/python3 ${./xmrig-auto-pause.py}"; # Safety net: any exit path (SIGTERM from PartOf cascade, systemctl stop, # crash with Restart=) must leave xmrig thawed. The Python SIGTERM # handler does the same thing; this covers SIGKILL / hard crash paths # too. Idempotent. ExecStop = pkgs.writeShellScript "xmrig-auto-pause-thaw" '' f=${cgroupFreeze} [ -w "$f" ] && echo 0 > "$f" || true ''; Restart = "always"; RestartSec = "10s"; NoNewPrivileges = true; ProtectHome = true; ProtectSystem = "strict"; PrivateTmp = true; RestrictAddressFamilies = [ "AF_UNIX" # systemctl talks to systemd over D-Bus unix socket ]; MemoryDenyWriteExecute = true; StateDirectory = "xmrig-auto-pause"; # Required so the script can write to cgroup.freeze under # ProtectSystem=strict (which makes /sys read-only by default). ReadWritePaths = [ cgroupDir ]; }; environment = { POLL_INTERVAL = "3"; GRACE_PERIOD = "15"; # Background services (qbittorrent, bitmagnet, postgresql, etc.) produce # 15-25% non-nice CPU during normal operation. The stop threshold must # sit above transient spikes; the resume threshold must be below the # steady-state floor to avoid restarting xmrig while services are active. CPU_STOP_THRESHOLD = "40"; CPU_RESUME_THRESHOLD = "10"; STATE_DIR = "/var/lib/xmrig-auto-pause"; XMRIG_CGROUP_FREEZE = cgroupFreeze; # Per-service CPU thresholds. Catches sub-threshold activity that never # trips the system-wide gauge — a single Minecraft player uses 3-15% of # one core (0.3-1.3% of a 12-thread host) which is pure noise in # /proc/stat but dominant in the minecraft cgroup. WATCHED_SERVICES = lib.concatStringsSep "," ( lib.optional config.services.minecraft-servers.enable "minecraft-server-${service_configs.minecraft.server_name}:2" ); }; }; # Pull auto-pause along whenever xmrig starts. After= on auto-pause ensures # correct order; Wants= here ensures it actually starts. systemd.services.xmrig.wants = [ "xmrig-auto-pause.service" ]; }