Refactor web_scan.py for improved readability and maintainability; add cron job script for npm log backups; update start.ps1 for consistent formatting.

This commit is contained in:
zwitschi committed 2026-09-08 19:49:11 +02:00
1 parent 0d1cd21468
commit c2eba88ac8
113 files changed
+772606 -772579

No files matched your search

+107 -107
View File
@@ -1,107 +1,107 @@
#!/usr/bin/env python3
"""Report retention for generated files under ``/reports/``.
Every subdirectory keeps at most ``REPORT_RETENTION_MAX_FILES`` files, all
newer than ``REPORT_RETENTION_DAYS`` days. Older timestamped reports (e.g.
``reports/copyparty/*.txt``) are pruned automatically after each consolidated
scan so the tree does not grow without bound.
The git-tracked blocklist source of truth
(``<report_dir>/app/data/blocklist_ipv4.txt`` / ``blocklist_ipv6.txt``) is
always protected and never pruned.
"""
from __future__ import annotations
import os
import time
from typing import Iterable
from .constants import (
REPORT_RETENTION_DAYS,
REPORT_RETENTION_MAX_FILES,
)
#: default protected files (never pruned): the cumulative blocklist
BLOCKLIST_FILES = ("blocklist_ipv4.txt", "blocklist_ipv6.txt")
def _age_days(mtime: float) -> float:
return (time.time() - mtime) / 86400.0
def prune_report_tree(root: str,
*,
max_age_days: int = REPORT_RETENTION_DAYS,
max_files: int = REPORT_RETENTION_MAX_FILES,
dry_run: bool = False,
protected: Iterable[str] = ()) -> dict[str, list[str]]:
"""Prune a report tree so each directory keeps ≤ ``max_files`` recent files.
A file is kept only if it is both among the ``max_files`` most recently
modified files in its directory AND newer than ``max_age_days`` days.
Protected paths are never touched. Returns ``{dir: [deleted paths]}``.
"""
protected_abs = {os.path.abspath(p) for p in protected}
removed: dict[str, list[str]] = {}
for dirpath, _dirnames, filenames in os.walk(root):
files: list[tuple[str, float]] = []
for name in filenames:
path = os.path.join(dirpath, name)
if os.path.abspath(path) in protected_abs:
continue # protected files are neither kept-counted nor pruned
try:
mtime = os.path.getmtime(path)
except OSError:
continue
files.append((path, mtime))
# Newest first; only these (within the age window) survive.
files.sort(key=lambda t: t[1], reverse=True)
newest = {path for path, _ in files[:max_files]}
cutoff_days = max_age_days
deleted: list[str] = []
for path, mtime in files:
if path in newest and _age_days(mtime) <= cutoff_days:
continue # keep: recent and within the file cap
deleted.append(path)
if not dry_run:
try:
os.remove(path)
except OSError:
# e.g. file in use on Windows — skip and leave it.
continue
if deleted:
removed[dirpath] = deleted
return removed
def prune_report_dirs(*roots: str,
dry_run: bool = False,
max_age_days: int = REPORT_RETENTION_DAYS,
max_files: int = REPORT_RETENTION_MAX_FILES,
) -> dict[str, list[str]]:
"""Prune each of the given report roots (deduplicated by real path).
The blocklist source-of-truth files are always protected.
Returns ``{dir: [deleted paths]}`` across all roots.
"""
protected = [
os.path.join(root, "app", "data", name)
for root in roots
for name in BLOCKLIST_FILES
]
seen: set[str] = set()
summary: dict[str, list[str]] = {}
for root in roots:
real = os.path.realpath(root)
if real in seen or not os.path.isdir(root):
continue
seen.add(real)
summary.update(prune_report_tree(
root, max_age_days=max_age_days, max_files=max_files,
dry_run=dry_run, protected=protected))
return summary
#!/usr/bin/env python3
"""Report retention for generated files under ``/reports/``.
Every subdirectory keeps at most ``REPORT_RETENTION_MAX_FILES`` files, all
newer than ``REPORT_RETENTION_DAYS`` days. Older timestamped reports (e.g.
``reports/copyparty/*.txt``) are pruned automatically after each consolidated
scan so the tree does not grow without bound.
The git-tracked blocklist source of truth
(``<report_dir>/app/data/blocklist_ipv4.txt`` / ``blocklist_ipv6.txt``) is
always protected and never pruned.
"""
from __future__ import annotations
import os
import time
from typing import Iterable
from .constants import (
REPORT_RETENTION_DAYS,
REPORT_RETENTION_MAX_FILES,
)
#: default protected files (never pruned): the cumulative blocklist
BLOCKLIST_FILES = ("blocklist_ipv4.txt", "blocklist_ipv6.txt")
def _age_days(mtime: float) -> float:
return (time.time() - mtime) / 86400.0
def prune_report_tree(root: str,
*,
max_age_days: int = REPORT_RETENTION_DAYS,
max_files: int = REPORT_RETENTION_MAX_FILES,
dry_run: bool = False,
protected: Iterable[str] = ()) -> dict[str, list[str]]:
"""Prune a report tree so each directory keeps ≤ ``max_files`` recent files.
A file is kept only if it is both among the ``max_files`` most recently
modified files in its directory AND newer than ``max_age_days`` days.
Protected paths are never touched. Returns ``{dir: [deleted paths]}``.
"""
protected_abs = {os.path.abspath(p) for p in protected}
removed: dict[str, list[str]] = {}
for dirpath, _dirnames, filenames in os.walk(root):
files: list[tuple[str, float]] = []
for name in filenames:
path = os.path.join(dirpath, name)
if os.path.abspath(path) in protected_abs:
continue # protected files are neither kept-counted nor pruned
try:
mtime = os.path.getmtime(path)
except OSError:
continue
files.append((path, mtime))
# Newest first; only these (within the age window) survive.
files.sort(key=lambda t: t[1], reverse=True)
newest = {path for path, _ in files[:max_files]}
cutoff_days = max_age_days
deleted: list[str] = []
for path, mtime in files:
if path in newest and _age_days(mtime) <= cutoff_days:
continue # keep: recent and within the file cap
deleted.append(path)
if not dry_run:
try:
os.remove(path)
except OSError:
# e.g. file in use on Windows — skip and leave it.
continue
if deleted:
removed[dirpath] = deleted
return removed
def prune_report_dirs(*roots: str,
dry_run: bool = False,
max_age_days: int = REPORT_RETENTION_DAYS,
max_files: int = REPORT_RETENTION_MAX_FILES,
) -> dict[str, list[str]]:
"""Prune each of the given report roots (deduplicated by real path).
The blocklist source-of-truth files are always protected.
Returns ``{dir: [deleted paths]}`` across all roots.
"""
protected = [
os.path.join(root, "app", "data", name)
for root in roots
for name in BLOCKLIST_FILES
]
seen: set[str] = set()
summary: dict[str, list[str]] = {}
for root in roots:
real = os.path.realpath(root)
if real in seen or not os.path.isdir(root):
continue
seen.add(real)
summary.update(prune_report_tree(
root, max_age_days=max_age_days, max_files=max_files,
dry_run=dry_run, protected=protected))
return summary