mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-10 19:27:04 +01:00
The 250-line split left function-sized copies behind: the confidence-only batch fetch was the search half of _fetch_batch verbatim (now _search_batch, and _SearchCtx carries one _HLTBExtras instead of two of its dicts); the workout and LeetCode bonuses each had the same loopback GET and answer cache (now _status_api); the poll-count refetch lived in both _cmd_done and _polls_reporting (now refetch_poll_counts); the candidate-selection prelude in both scanning and _scanning_assign; the /etc/hosts rewrite loop in both block and unblock (now rewrite_hosts_lines with a per-line rule); the timed HLTB fetch and its rate line; and the whitelist constants each module only half used. _hltb_cached's dead facade constants go too. Tests patch where the code now lives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
177 lines
6.0 KiB
Python
177 lines
6.0 KiB
Python
"""Block Steam Store access via /etc/hosts (hosts install script) and iptables.
|
|
|
|
/etc/hosts is protected by guard-lib (~/guard-lib, file-guard instance
|
|
"hosts"): chattr +i, a read-only self-bind-mount, and a systemd path-unit
|
|
watcher. This module checks if the Steam Store domains are already blocked
|
|
in /etc/hosts. If not, it runs the hosts install.sh (which must already
|
|
contain the Steam Store entries in its heredoc), going through guard-lib's
|
|
unlock/relock around any edit. As a belt-and-suspenders fallback, it also
|
|
blocks via iptables.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import subprocess
|
|
|
|
from steam_backlog_enforcer._hosts_protection import (
|
|
_reblock_hosts,
|
|
rewrite_hosts_lines,
|
|
)
|
|
from steam_backlog_enforcer._store_iptables import (
|
|
_block_store_iptables,
|
|
_is_iptables_blocked,
|
|
_unblock_store_iptables,
|
|
flush_dns_cache,
|
|
)
|
|
from steam_backlog_enforcer._store_tools import (
|
|
BASH,
|
|
HOSTS_INSTALL_SCRIPT,
|
|
HOSTS_REDIRECT_IP,
|
|
SUDO,
|
|
)
|
|
from steam_backlog_enforcer.config import (
|
|
BLOCKED_DOMAINS,
|
|
HOSTS_FILE,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def is_store_blocked() -> bool:
|
|
"""Check if Steam Store domains are blocked in /etc/hosts."""
|
|
try:
|
|
content = HOSTS_FILE.read_text(encoding="utf-8")
|
|
# Check for at least the primary store domain.
|
|
if "store.steampowered.com" in content:
|
|
# Verify it's actually blocked (not commented out).
|
|
for line in content.splitlines():
|
|
stripped = line.strip()
|
|
if (
|
|
not stripped.startswith("#")
|
|
and "store.steampowered.com" in stripped
|
|
and stripped.startswith(HOSTS_REDIRECT_IP)
|
|
):
|
|
return True
|
|
except OSError:
|
|
pass
|
|
|
|
return _is_iptables_blocked()
|
|
|
|
|
|
def block_store() -> bool:
|
|
"""Block Steam Store: uncomment hosts entries, or run install script.
|
|
|
|
Returns True if at least one blocking method succeeded.
|
|
"""
|
|
if is_store_blocked():
|
|
logger.info("Steam Store already blocked in /etc/hosts.")
|
|
return True
|
|
|
|
# Try quick re-block (uncomment lines) first.
|
|
if _reblock_hosts() and is_store_blocked():
|
|
_block_store_iptables()
|
|
flush_dns_cache()
|
|
return True
|
|
|
|
# Fall back to the full hosts install script.
|
|
hosts_ok = _block_via_hosts_install()
|
|
ipt_ok = _block_store_iptables()
|
|
|
|
if hosts_ok or ipt_ok:
|
|
flush_dns_cache()
|
|
return True
|
|
|
|
logger.error("All store-blocking methods failed.")
|
|
return False
|
|
|
|
|
|
def _block_via_hosts_install() -> bool:
|
|
"""Run the hosts install.sh to apply /etc/hosts with Steam Store entries.
|
|
|
|
The install script handles: immutable flag removal, bind mount remounting,
|
|
writing the file, re-applying protections, and DoH disabling.
|
|
"""
|
|
if is_store_blocked():
|
|
logger.info("Steam Store already blocked in /etc/hosts.")
|
|
return True
|
|
|
|
if not HOSTS_INSTALL_SCRIPT.exists():
|
|
logger.error("hosts install script not found at %s", HOSTS_INSTALL_SCRIPT)
|
|
return False
|
|
|
|
try:
|
|
logger.info("Running hosts install script to block Steam Store...")
|
|
result = subprocess.run(
|
|
[SUDO, BASH, str(HOSTS_INSTALL_SCRIPT), "--no-flush-dns"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=120,
|
|
check=False,
|
|
)
|
|
except OSError, subprocess.SubprocessError:
|
|
logger.exception("Failed to run hosts install script")
|
|
return False
|
|
if result.returncode == 0:
|
|
logger.info("hosts install script succeeded.")
|
|
return True
|
|
logger.error(
|
|
"hosts install script failed (rc=%d): %s",
|
|
result.returncode,
|
|
result.stderr[-500:] if result.stderr else result.stdout[-500:],
|
|
)
|
|
return False
|
|
|
|
|
|
def unblock_store() -> bool:
|
|
"""Remove Steam Store blocks from both iptables and /etc/hosts."""
|
|
ipt_ok = _unblock_store_iptables()
|
|
hosts_ok = _unblock_hosts()
|
|
flush_dns_cache()
|
|
|
|
if not ipt_ok:
|
|
logger.warning("Failed to remove iptables rules.")
|
|
if not hosts_ok:
|
|
logger.warning("Failed to remove /etc/hosts entries.")
|
|
|
|
return ipt_ok or hosts_ok
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# /etc/hosts protection helpers
|
|
#
|
|
# /etc/hosts is managed by guard-lib (see ~/guard-lib) as file-guard
|
|
# instance "hosts": chattr +i, a read-only self-bind-mount, and a
|
|
# systemd path-unit watcher. "pacman-unlock" stops the watcher and
|
|
# collapses the bind mount (same primitive guard-lib's own pacman hooks
|
|
# use). Relocking uses "sync", NOT "pacman-relock" - pacman-relock calls
|
|
# fg_enforce, which treats any diff from the (stale) canonical as drift
|
|
# and reverts it; that's correct for pacman's own hook flow (undo
|
|
# unwanted tampering) but wrong here, where *we* are the one legitimately
|
|
# changing content - it would silently undo our own edit. "sync" instead
|
|
# adopts the just-written content as the new canonical.
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
def _unblock_hosts() -> bool:
|
|
"""Comment out Steam Store entries in /etc/hosts."""
|
|
if not is_store_blocked():
|
|
logger.info("Steam Store not blocked in /etc/hosts, nothing to do.")
|
|
return True
|
|
|
|
return rewrite_hosts_lines(
|
|
_comment_out_blocked, "Commented out Steam Store entries in /etc/hosts."
|
|
)
|
|
|
|
|
|
def _comment_out_blocked(line: str) -> str | None:
|
|
"""The line commented out, if it is a live redirect of a blocked domain."""
|
|
stripped = line.strip()
|
|
if (
|
|
not stripped.startswith("#")
|
|
and stripped.startswith(HOSTS_REDIRECT_IP)
|
|
and any(d in stripped for d in BLOCKED_DOMAINS)
|
|
):
|
|
return f"# {line}" if line.endswith("\n") else f"# {line}\n"
|
|
return None
|