[Unit] Description=Steam Backlog Enforcer After=network-online.target Wants=network-online.target [Service] Type=simple WorkingDirectory=/opt/steam-backlog-enforcer ExecStart=/usr/bin/python3 -m steam_backlog_enforcer.main enforce Restart=always RestartSec=5 Environment=PYTHONUNBUFFERED=1 Environment=PYTHONPATH=/opt/steam-backlog-enforcer Environment=HOME=/home/kuhy Environment=STEAM_ENFORCER_DESKTOP_USER=__DESKTOP_USER__ Environment=DISPLAY=:0 # Hardening: enforcer must not be easily killed. OOMScoreAdjust=-900 # /run/steam-backlog-enforcer: the control socket for the web UI (ctl.sock) and # the playtime refusal stub (gaming-blocked, bind-mounted over /usr/bin/steam # while the budget is spent). Mode 0755 so the stub stays executable by # everyone; the socket file carries its own 0660 + SO_PEERCRED check. # Preserve=yes: with the default (no) systemd deletes the directory whenever the # unit stops, which would delete the stub out from under a live block. RuntimeDirectory=steam-backlog-enforcer RuntimeDirectoryMode=0755 RuntimeDirectoryPreserve=yes # /var/lib/steam-backlog-enforcer: root-only state of the control socket (the # persisted restart rate limit). StateDirectory=steam-backlog-enforcer StateDirectoryMode=0700 # Journal backstop. Steam's srt-logger runs in this unit's cgroup and mirrors # Steam's console into journald; 2026-09-12 it wrote 481k "joystick FFB" # lines in one boot (99% of the journal) and evicted two weeks of history. # SRT_LOGGER_USE_JOURNAL=0 in the spawn env is the fix at source; these two # make sure a future Steam that ignores it still cannot flood the journal. LogFilterPatterns=~Failed to get joystick[0-9]+ FFB effect status LogFilterPatterns=~FFBInterface LogRateLimitIntervalSec=1min LogRateLimitBurst=2000 # # DO NOT ADD PrivateMounts=, PrivateTmp=, ProtectSystem= OR ProtectHome= HERE. # The daily gaming budget enforces its cutoff by bind-mounting a refusal stub # over /usr/bin/steam and the other launcher entry points. Any of those # directives gives this service a private mount namespace, so those mounts stay # invisible to the rest of the system: the launchers keep working while the # counter keeps running, and the block silently becomes a no-op. That failure # mode is invisible from the outside, which is why it is spelled out here. # _playtime_block.mounts_are_visible() logs an error if it ever happens. [Install] WantedBy=multi-user.target