steam-backlog-enforcer/steam_backlog_enforcer/_command_locks.py
Krzysztof kuhy Rudnicki 87c6f200e3 Research window: keep Steam dir, extend once
kuhy 2026-11-11, multi-day research without re-setup.

W1: once a window was used in a lock, the block's purge spares
~/.local/share/Steam (Librarian's install and local saves) for the
rest of the lock; ~/steam, ~/.steam and the rest still go. The
package stays removed, hosts/iptables are re-applied, and since the
kept install could otherwise be launched without the package, every
Steam game is killed on sight. A record that cannot be read also
keeps the folder (an irreversible delete never rests on unknown
state); the block itself stays fully applied.

W2: the 48 h cap is gone; --until is bounded by the lock's end. An
open window may be extended ONCE, to the lock end at most, never
shortened, never reopened after it ended. The extension is a
separate extended_until field beside until, so a daemon still on
the previous code reads the original window instead of rejecting the
record as corrupt (which would have purged the folder).

Failure modes unchanged: missing/corrupt record = no window, and a
corrupt one refuses a new window or extension.

Checked: 3325 tests at 100% coverage; pylint 10/10 in parts; vmbox:
extension accepted once, refused beyond the lock end, earlier or
equal, second time and non-root; extended end honoured past the
original end; at close the Steam dir and a save survive, ~/steam and
~/.steam go, a game started after close is killed; extension to
exactly the lock end accepted. Not verified on the host.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWWzuCEg1m87jpKyRrw
2026-11-11 16:28:43 +01:00

66 lines
2.7 KiB
Python

"""Which commands stay usable under each lock.
A leaf module (no imports) so the CLI dispatcher in ``main._shared`` and the
root daemon's control socket consult ONE table: a lock the daemon enforced
differently from the CLI would be a way around it for anyone who can reach
the socket. ``main._shared`` cannot be imported by the daemon directly --
``main`` imports the enforce loop, which starts the control socket.
"""
from __future__ import annotations
# Commands that remain usable while the manual pick lock is active.
# Principle: only what is needed to release the lock (done/check) or
# that cannot change the game assignment (status, enforce, setup, serve).
_MANUAL_LOCK_EXEMPT_COMMANDS = frozenset(
{
"done",
"check",
"status",
"enforce",
"setup",
"serve",
"abandon-pick",
# Allowed so a second game can be locked in alongside the first; the
# cap inside cmd_pick_manual is what stops this being a way out.
"pick-manual",
# The daily gaming budget is orthogonal to which game is assigned, and
# gaming-unblock is a recovery hatch for a stuck bind mount - locking
# it behind a manual pick would leave Steam masked with no way back.
"gaming-status",
"gaming-unblock",
"gaming-reset",
# Reconciles the block's domain list; cannot shorten or end a lock.
"total-block-refresh",
# Records the one bounded research window (see _research_window); the
# lock itself and its end date stay untouched.
"total-block-research",
}
)
# Commands that remain usable while a total gaming block is active. Far
# stricter than _MANUAL_LOCK_EXEMPT_COMMANDS: no done/pick/reset/
# add-exception - nothing here shortens or ends a total block. The one bounded
# opening is total-block-research: root-only, a single window per lock that
# only Librarian may use (up to the lock's end, extendable once), recorded in a
# root-only state directory; the daemon re-applies the full block when it
# expires. The lock and its end date are never touched.
#
# gaming-unblock is included because a playtime bind mount makes the total
# block's own `pacman -R steam` fail EBUSY - it must stay reachable exactly
# when the two collide. gaming-reset is NOT included: it shortens enforcement.
# serve changes nothing itself (every job it starts is lock-checked on its own)
# and refusing it took the web UI down for the whole block, crash-looping its
# systemd unit, the first time anything restarted it.
_TOTAL_BLOCK_EXEMPT_COMMANDS = frozenset(
{
"status",
"enforce",
"gaming-status",
"gaming-unblock",
"serve",
"total-block-refresh",
"total-block-research",
}
)