Files
gitlab-instance-0a899031_d2…/web2_config_governance.py
2026-07-10 05:15:29 -05:00

158 lines
5.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Config file backup/restore before writes (simplified from sh2 config_governance.py)."""
from __future__ import annotations
import errno
import shutil
from datetime import datetime
from pathlib import Path
from typing import Any
MAX_BACKUPS_PER_FILE = 24
def _now_stamp() -> str:
return datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
def _backup_root(base_dir: Path) -> Path:
return base_dir / "backup_config" / "__web__"
def _history_dir(base_dir: Path, rel_name: str) -> Path:
rel = Path(rel_name.replace("\\", "/").lstrip("/"))
return _backup_root(base_dir) / rel.parent / f"{rel.name}.history"
def backup_config_file(config_dir: Path, rel_name: str, *, reason: str = "save") -> dict[str, Any] | None:
"""Copy current config to history before overwrite. rel_name e.g. youtube.ini or relay_pro/foo.ini"""
config_dir = Path(config_dir)
target = config_dir / rel_name.replace("\\", "/").lstrip("/")
if not target.is_file():
return None
hist_dir = _history_dir(config_dir, rel_name)
try:
hist_dir.mkdir(parents=True, exist_ok=True)
except OSError:
return None
suffix = target.suffix or ".bak"
backup_name = f"{_now_stamp()}__{reason}{suffix}"
backup_path = hist_dir / backup_name
try:
shutil.copy2(target, backup_path)
except OSError:
return None
files = sorted(
(p for p in hist_dir.iterdir() if p.is_file()),
key=lambda p: p.stat().st_mtime,
reverse=True,
)
for old in files[MAX_BACKUPS_PER_FILE:]:
try:
old.unlink(missing_ok=True)
except OSError:
pass
stat = backup_path.stat()
return {
"id": backup_name,
"reason": reason,
"label": backup_name,
"created_at": datetime.fromtimestamp(stat.st_mtime).isoformat(timespec="seconds"),
"size": stat.st_size,
}
def list_config_backups(config_dir: Path, rel_name: str) -> list[dict[str, Any]]:
hist_dir = _history_dir(config_dir, rel_name)
if not hist_dir.is_dir():
return []
out: list[dict[str, Any]] = []
for item in sorted(hist_dir.iterdir(), key=lambda p: p.stat().st_mtime, reverse=True):
if not item.is_file():
continue
stat = item.stat()
stem = item.stem
reason = "snapshot"
if "__" in stem:
_, _, reason = stem.partition("__")
out.append(
{
"id": item.name,
"reason": reason or "snapshot",
"label": item.name,
"created_at": datetime.fromtimestamp(stat.st_mtime).isoformat(timespec="seconds"),
"size": stat.st_size,
}
)
return out
def restore_config_backup(config_dir: Path, rel_name: str, backup_id: str) -> tuple[bool, str]:
config_dir = Path(config_dir)
rel = rel_name.replace("\\", "/").lstrip("/")
target = config_dir / rel
hist_dir = _history_dir(config_dir, rel_name)
backup_path = hist_dir / Path(backup_id).name
if not backup_path.is_file() or backup_path.parent != hist_dir:
return False, "备份不存在"
try:
target.parent.mkdir(parents=True, exist_ok=True)
if target.is_file():
backup_config_file(config_dir, rel_name, reason="pre_restore")
shutil.copy2(backup_path, target)
return True, "已恢复配置"
except OSError as exc:
if exc.errno in {errno.EACCES, errno.EPERM}:
return False, f"无写入权限: {target}"
return False, str(exc)
def write_config_text(config_dir: Path, rel_name: str, content: str, *, reason: str = "save") -> Path:
config_dir = Path(config_dir)
rel = rel_name.replace("\\", "/").lstrip("/")
target = config_dir / rel
backup_config_file(config_dir, rel_name, reason=reason)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding="utf-8")
return target
def _normalize_url_line_for_dedupe(line: str) -> str:
from web2_relay_pro import extract_douyin_url_from_config_line
url = extract_douyin_url_from_config_line(line)
if url:
return url.rstrip("/").lower()
t = (line or "").strip()
if not t or t.startswith("#"):
return ""
return t.rstrip("/").lower()
def dedupe_url_config_content(content: str) -> tuple[str, int]:
"""去除 URL_config 中重复的抖音直播间行(参照 d2ypp2 config_governance dedupe_urls"""
seen: set[str] = set()
removed = 0
out: list[str] = []
for raw in content.splitlines():
line = raw.strip()
if not line or line.startswith("#"):
out.append(raw.rstrip())
continue
key = _normalize_url_line_for_dedupe(line)
if not key:
out.append(raw.rstrip())
continue
if "douyin" not in key:
out.append(raw.rstrip())
continue
if key in seen:
removed += 1
continue
seen.add(key)
out.append(raw.rstrip())
text = "\n".join(out).rstrip()
if text:
text += "\n"
return text, removed