初始化奇妙小屏幕控制器项目

This commit is contained in:
2026-09-08 22:56:52 +08:00
commit 8d368de3b5
491 changed files with 67678 additions and 0 deletions
@@ -0,0 +1,206 @@
from __future__ import annotations
from datetime import datetime, timezone
import hashlib
import os
from pathlib import Path
import platform
import shlex
import shutil
import subprocess
import sys
from typing import Any
from app.persistence import atomic_write_bytes
MAX_FAILURE_LOG_BYTES = 1024 * 1024
FAILURE_LOG_RELATIVE = Path("ota") / "last-failure.log"
_TRUNCATION_MARKER = (
"\n\n========== 日志已截断 =========="
"\n原始日志超过 1 MiB;保留开头和最新的失败尾部。"
"\n========== 继续显示最新日志 ==========\n\n"
).encode("utf-8")
_HEAD_BYTES = 128 * 1024
def _timestamp() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with Path(path).open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def bounded_failure_log(body: bytes, *, maximum: int = MAX_FAILURE_LOG_BYTES) -> bytes:
normalized = bytes(body).decode("utf-8", errors="replace").encode("utf-8")
if len(normalized) <= maximum:
return normalized
head = min(_HEAD_BYTES, maximum // 4)
tail = maximum - head - len(_TRUNCATION_MARKER)
if tail <= 0:
return normalized[-maximum:]
return normalized[:head] + _TRUNCATION_MARKER + normalized[-tail:]
def failure_log_path(data_root: Path) -> Path:
return Path(data_root) / FAILURE_LOG_RELATIVE
def persist_failure_log(data_root: Path, runtime_log: Path) -> Path:
source = Path(runtime_log)
body = bounded_failure_log(source.read_bytes())
target = failure_log_path(data_root)
atomic_write_bytes(target, body)
try:
target.chmod(0o600)
except OSError:
target.unlink(missing_ok=True)
raise
return target
def clear_failure_log(data_root: Path) -> None:
failure_log_path(data_root).unlink(missing_ok=True)
def failure_log_metadata(data_root: Path, last_result: dict[str, Any] | None) -> tuple[bool, int]:
if not isinstance(last_result, dict) or last_result.get("status") != "failed":
return False, 0
path = failure_log_path(data_root)
try:
size = path.stat().st_size
except OSError:
return False, 0
if size <= 0 or size > MAX_FAILURE_LOG_BYTES:
return False, 0
return True, size
def read_failure_log(data_root: Path, last_result: dict[str, Any] | None) -> bytes | None:
available, expected_size = failure_log_metadata(data_root, last_result)
if not available:
return None
try:
body = failure_log_path(data_root).read_bytes()
except OSError:
return None
if len(body) != expected_size:
return None
return body
class DiagnosticLog:
def __init__(self, path: Path) -> None:
self.path = Path(path)
self._recent = bytearray()
self._degraded = False
def reset(self) -> None:
self._recent.clear()
try:
self.path.parent.mkdir(parents=True, exist_ok=True)
self.path.write_bytes(b"")
except OSError:
self._degraded = True
self.write("OTA 诊断日志已创建")
def write(self, message: str) -> None:
rendered = str(message).replace("\r\n", "\n").replace("\r", "\n")
entry = f"[{_timestamp()}] {rendered}\n".encode("utf-8", errors="replace")
self._recent.extend(entry)
del self._recent[:-MAX_FAILURE_LOG_BYTES]
try:
self.path.parent.mkdir(parents=True, exist_ok=True)
restore = self._degraded or not self.path.exists()
with self.path.open("wb" if restore else "ab") as handle:
handle.write(bytes(self._recent) if restore else entry)
self._degraded = False
except OSError as exc:
self._degraded = True
try:
# Avoid printing OS messages, paths or the command's content here.
sys.stderr.write(f"OTA diagnostic storage unavailable ({type(exc).__name__}); recovery continues\n")
except (OSError, UnicodeError):
pass
def persist(self, data_root: Path) -> Path:
try:
body = self.path.read_bytes() if not self._degraded else bytes(self._recent)
except OSError:
body = bytes(self._recent)
target = failure_log_path(data_root)
atomic_write_bytes(target, bounded_failure_log(body))
target.chmod(0o600)
return target
def section(self, name: str) -> None:
self.write(f"========== {name} ==========")
def host_summary(self, *, test_root: Path) -> None:
self.section("安全主机摘要")
self.write(f"system={platform.system()} release={platform.release()} machine={platform.machine()}")
self.write(f"python={sys.version.splitlines()[0]}")
for path in (Path("/opt"), Path("/run"), Path(test_root)):
probe = path if path.exists() else path.parent
try:
usage = shutil.disk_usage(probe)
self.write(f"space path={path} free={usage.free} total={usage.total}")
except OSError as exc:
self.write(f"space path={path} unavailable={exc.__class__.__name__}")
def run(
self,
command: list[str],
*,
cwd: Path | None = None,
env: dict[str, str] | None = None,
check: bool = True,
failure_label: str | None = None,
log_output: bool = True,
) -> subprocess.CompletedProcess[bytes]:
display = (
shlex.join([str(item) for item in command])
if log_output
else f"{shlex.quote(str(command[0]))} <参数已按脱敏规则隐藏>"
)
self.section(f"命令开始:{failure_label or Path(command[0]).name}")
self.write(f"cwd={cwd if cwd is not None else os.getcwd()}")
self.write(f"command={display}")
try:
result = subprocess.run(
command,
cwd=cwd,
env=env,
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
except OSError as exc:
self.write(f"command_start_failed={exc.__class__.__name__}: {exc}")
raise RuntimeError(f"{failure_label or '命令'}无法启动:{exc}") from exc
output = result.stdout.decode("utf-8", errors="replace")
if output and log_output:
self.write("stdout_stderr:\n" + output.rstrip("\n"))
try:
sys.stdout.write(output)
sys.stdout.flush()
except (UnicodeError, OSError):
pass
elif output:
self.write("stdout_stderr=<已按脱敏规则隐藏>")
self.write(f"exit_code={result.returncode}")
if check and result.returncode != 0:
tail = (
output.rstrip()[-12000:] or "命令没有输出。"
if log_output
else "命令输出已按脱敏规则隐藏。"
)
raise RuntimeError(
f"{failure_label or '命令'}失败(退出码 {result.returncode}):\n{tail}"
)
return result