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:
1 parent
0d1cd21468
commit
c2eba88ac8
113 files changed
+772606
-772579
No files matched your search
+107
-107
@@ -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
|
||||
Reference in new issue
Block a user