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

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,102 @@
"""Promote an explicitly authorized, device-validated OTA without rebuilding it."""
from __future__ import annotations
from datetime import datetime, timezone
import json
import os
from pathlib import Path
import shutil
import tempfile
import uuid
from app.ota.package import inspect_package, extract_payload, _source_file_allowed
from app.ota.policy import read_policy, required_components, release_metadata, package_format
from app.ota.versioning import read_software_version
from app.ota.diagnostics import sha256_file
def promote(source: Path, candidate: Path, validation: Path, notes: str) -> Path:
from scripts.export_release import _history, _commit_repair
project = source.parent
lock = project / '.release-export.lock'
fd = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
os.close(fd)
try:
version = read_software_version(source)
current = project / '发布更新相关' / 'OTA数据包' / str(version)
name = f'matrix-screen-controller-{version}.ota'
history_path = project / '发布记录.json'
history = _history(history_path)
matches = [(i, r) for i, r in enumerate(history['releases']) if r['version'] == str(version)]
if len(matches) != 1 or matches[0][1]['artifact_type'] != 'ota':
raise ValueError('current version must have exactly one OTA release record')
index, previous = matches[0]
original_sha = sha256_file(current / name)
manifest = json.loads((current / 'manifest.json').read_text(encoding='utf-8'))
if (previous['artifact_sha256'] != original_sha or manifest['artifact_sha256'] != original_sha
or (current / (name + '.sha256')).read_text(encoding='ascii') != f'{original_sha} {name}\n'):
raise ValueError('original OTA release metadata does not match its artifact')
info = inspect_package(candidate)
digest = sha256_file(candidate)
report = json.loads(validation.read_text(encoding='utf-8'))
if (info.target_version != version or report.get('package_sha256') != digest
or report.get('installed_version') != str(version) or report.get('status') != 'success'
or report.get('runtime_lifecycle_passed') is not True
or report.get('user_data_preserved') is not True
or report.get('frp_state_preserved') is not True
or report.get('transaction_cleanup_passed') is not True):
raise ValueError('candidate lacks matching successful real-device validation')
if digest == original_sha:
raise ValueError('repair candidate is identical to the current artifact')
with tempfile.TemporaryDirectory(prefix='matrix-ota-repair-check-') as text:
unpacked = Path(text) / 'unpacked'
extract_payload(info, unpacked)
software = unpacked / 'software'
expected = {p.relative_to(source).as_posix() for p in source.rglob('*')
if p.is_file() and _source_file_allowed(p, source)}
actual = {p.relative_to(software).as_posix() for p in software.rglob('*')
if p.is_file() and _source_file_allowed(p, software)}
if actual != expected or any((software / n).read_bytes() != (source / n).read_bytes() for n in expected):
raise ValueError('validated candidate differs from current source; validate a new candidate')
for component, requirement in required_components(source, version).items():
if version.patch == 0 and sha256_file(software / 'system-dependencies' / component / component) != requirement['sha256']:
raise ValueError('candidate system dependency differs from policy')
stamp = datetime.now(timezone.utc).astimezone().isoformat(timespec='seconds')
archive = project / '各种归档' / f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_OTA{version}修复前_{uuid.uuid4().hex[:6]}"
archive.mkdir(parents=True)
shutil.copytree(current, archive / '原安装包')
shutil.copy2(history_path, archive / '发布记录_修复前.json')
shutil.copy2(validation, archive / '实机验收.json')
staged = current.parent / f'.{version}.{uuid.uuid4().hex}.repairing'
staged.mkdir()
try:
shutil.copy2(candidate, staged / name)
if sha256_file(staged / name) != digest:
raise ValueError('candidate copy checksum mismatch')
metadata = {**manifest, **release_metadata(version), 'format_version': package_format(version),
'created_at': info.created_at, 'notes': notes, 'artifact_bytes': candidate.stat().st_size,
'artifact_sha256': digest, 'repaired_at': stamp, 'replaces_sha256': original_sha}
(staged / 'manifest.json').write_text(json.dumps(metadata, ensure_ascii=False, indent=2)+'\n', encoding='utf-8')
(staged / (name+'.sha256')).write_text(f'{digest} {name}\n', encoding='ascii', newline='\n')
(staged / 'README.md').write_text(
f'# OTA {version} 软件安装包(修复版)\n\n{notes}\n\n'
f'- SHA-256:`{digest}`\n- 修复时间:`{stamp}`\n'
'- 通过系统设置上传 `.ota`,无需解压。1.0.x 必须先安装本节点,再安装后续补丁。\n'
'- 已通过真实 systemd 停启测试及测试设备 OTA;正式包与实机验收包字节一致。\n'
'- 已安装 frp 时保留配置和启停状态,完整组件跳过,缺失或损坏时离线修复。\n'
'- 本包修复停止旧服务时运行目录被删除的问题;仍为 1.1.0,不是 1.1.1。\n'
f'- 原失败包与旧记录:`{archive.relative_to(project).as_posix()}`。\n', encoding='utf-8')
repair = {'at': stamp, 'reason': notes, 'previous_sha256': original_sha,
'archive_path': archive.relative_to(project).as_posix()}
history['releases'][index] = {**previous, 'created_at': info.created_at, 'notes': notes,
'artifact_sha256': digest, 'component_checkpoints': read_policy(source)['checkpoints'],
'repairs': [*previous.get('repairs', []), repair]}
leftover = _commit_repair(current, staged, history_path, history)
if leftover:
print(f'obsolete temporary backup requires cleanup: {leftover}')
except BaseException:
shutil.rmtree(staged, ignore_errors=True)
raise
return current
finally:
lock.unlink(missing_ok=True)