同步移动端工程、设备控制改进与发布资料
This commit is contained in:
@@ -32,6 +32,8 @@ RUNTIME_GUARD_BODY = b'# Managed by the OTA component transaction\n[Service]\nRu
|
||||
SERVICE = 'matrix-screen-controller.service'
|
||||
WORKER = 'matrix-screen-controller-ota.service'
|
||||
FRP = 'matrix-screen-frpc.service'
|
||||
MOBILE_SERVICES = ('aw859-bluetooth.service', 'bluetooth.service')
|
||||
MOBILE_FILES = ('bluetooth-conf', 'bluetooth-original', 'bluetooth-dropin', 'aw859-dropin')
|
||||
JOURNAL = Path('ota/component-transaction')
|
||||
FILES = {
|
||||
'frpc': Path('/usr/local/bin/frpc'),
|
||||
@@ -39,6 +41,10 @@ FILES = {
|
||||
'frpc-dropin': Path('/etc/systemd/system/matrix-screen-frpc.service.d/user.conf'),
|
||||
'app-unit': Path('/etc/systemd/system/matrix-screen-controller.service'),
|
||||
'ota-unit': Path('/etc/systemd/system/matrix-screen-controller-ota.service'),
|
||||
'bluetooth-conf': Path('/etc/bluetooth/main.conf'),
|
||||
'bluetooth-original': Path('/etc/bluetooth/main.conf.matrix-mobile-original'),
|
||||
'bluetooth-dropin': Path('/etc/systemd/system/bluetooth.service.d/50-matrix-mobile.conf'),
|
||||
'aw859-dropin': Path('/etc/systemd/system/aw859-bluetooth.service.d/50-matrix-mobile.conf'),
|
||||
}
|
||||
|
||||
|
||||
@@ -229,15 +235,18 @@ def begin(source: Path, candidate: Path):
|
||||
if RUNTIME_GUARD.exists():
|
||||
raise RuntimeError('存在未清理的 OTA 运行目录保护,请先恢复')
|
||||
bundle = source / 'system-dependencies/frpc'
|
||||
mobile_installer = source / 'scripts/install_mobile_bluetooth.sh'
|
||||
if not bundle.is_dir():
|
||||
check_installed(source, version)
|
||||
return
|
||||
if not mobile_installer.is_file():
|
||||
return
|
||||
# Verify the pinned binary, not just a self-reported checksum file.
|
||||
from app.ota.policy import required_components
|
||||
expected = required_components(source, SoftwareVersion.parse(version))['frpc']
|
||||
if hashlib.sha256((bundle / 'frpc').read_bytes()).hexdigest() != expected['sha256']:
|
||||
raise RuntimeError('frpc payload differs from the registered dependency')
|
||||
user = account()
|
||||
if bundle.is_dir():
|
||||
expected = required_components(source, SoftwareVersion.parse(version))['frpc']
|
||||
if hashlib.sha256((bundle / 'frpc').read_bytes()).hexdigest() != expected['sha256']:
|
||||
raise RuntimeError('frpc payload differs from the registered dependency')
|
||||
user = account() if bundle.is_dir() else None
|
||||
for root in (DATA, candidate):
|
||||
if (root / JOURNAL).exists():
|
||||
raise RuntimeError('存在未完成组件事务,请先恢复')
|
||||
@@ -249,6 +258,13 @@ def begin(source: Path, candidate: Path):
|
||||
'permissions': permission_snapshot(DATA), 'files': {},
|
||||
'active': run(['systemctl', 'is-active', '--quiet', FRP], check=False).returncode == 0,
|
||||
'enabled': run(['systemctl', 'is-enabled', '--quiet', FRP], check=False).returncode == 0}
|
||||
if mobile_installer.is_file():
|
||||
record['mobile_services'] = {
|
||||
unit: {
|
||||
'enabled_state': run(['systemctl', 'is-enabled', unit], check=False).stdout.decode().strip(),
|
||||
'active': run(['systemctl', 'is-active', '--quiet', unit], check=False).returncode == 0,
|
||||
} for unit in MOBILE_SERVICES
|
||||
}
|
||||
try:
|
||||
for name, path in FILES.items():
|
||||
record['files'][name] = metadata(path) if path.exists() else None
|
||||
@@ -273,11 +289,14 @@ def begin(source: Path, candidate: Path):
|
||||
'/usr/bin/python3', str(HELPER), 'watch'])
|
||||
protect_runtime()
|
||||
env = os.environ.copy()
|
||||
env.update(FRPC_BUNDLE=str(bundle), FRPC_RUN_USER=user)
|
||||
run(['/bin/sh', str(source / 'scripts/install_frpc_system.sh')], env=env)
|
||||
if bundle.is_dir():
|
||||
env.update(FRPC_BUNDLE=str(bundle), FRPC_RUN_USER=user)
|
||||
run(['/bin/sh', str(source / 'scripts/install_frpc_system.sh')], env=env)
|
||||
if mobile_installer.is_file():
|
||||
run(['/bin/sh', str(mobile_installer)], env=env)
|
||||
mirror_permissions(candidate)
|
||||
check_installed(source, version)
|
||||
print('FRP component transaction prepared; waiting for OTA health result')
|
||||
print('System component transaction prepared; waiting for OTA health result')
|
||||
except BaseException:
|
||||
# The watcher handles subsequent worker failure. Restore immediately too,
|
||||
# so a failed migration never leaves new system files while rolling back.
|
||||
@@ -289,6 +308,10 @@ def begin(source: Path, candidate: Path):
|
||||
|
||||
|
||||
def restore_components(journal: Path, record: dict):
|
||||
mobile_services = record.get('mobile_services', {})
|
||||
for unit in reversed(MOBILE_SERVICES):
|
||||
if unit in mobile_services:
|
||||
run(['systemctl', 'stop', unit], check=False)
|
||||
run(['systemctl', 'stop', FRP], check=False)
|
||||
for name in ('frpc', 'frpc-unit', 'frpc-dropin'):
|
||||
path = FILES[name]
|
||||
@@ -299,6 +322,15 @@ def restore_components(journal: Path, record: dict):
|
||||
atomic(path, (journal / name).read_bytes(), item['mode'])
|
||||
apply_metadata(path, item)
|
||||
restore_permissions(DATA, record['permissions'])
|
||||
if mobile_services:
|
||||
for name in MOBILE_FILES:
|
||||
path = FILES[name]
|
||||
item = record['files'][name]
|
||||
if item is None:
|
||||
path.unlink(missing_ok=True)
|
||||
else:
|
||||
atomic(path, (journal / name).read_bytes(), item['mode'])
|
||||
apply_metadata(path, item)
|
||||
run(['systemctl', 'daemon-reload'])
|
||||
# A previously absent unit cannot be disabled; avoid treating absence as error.
|
||||
if record['files']['frpc-unit'] is not None:
|
||||
@@ -309,6 +341,24 @@ def restore_components(journal: Path, record: dict):
|
||||
link.unlink(missing_ok=True)
|
||||
if record['active']:
|
||||
run(['systemctl', 'start', FRP])
|
||||
for unit in MOBILE_SERVICES:
|
||||
previous = mobile_services.get(unit)
|
||||
if previous is None:
|
||||
continue
|
||||
run(['systemctl', 'unmask', unit])
|
||||
state = previous['enabled_state']
|
||||
run(['systemctl', 'disable', unit], check=False)
|
||||
if state == 'enabled':
|
||||
run(['systemctl', 'enable', unit])
|
||||
elif state == 'enabled-runtime':
|
||||
run(['systemctl', 'enable', '--runtime', unit])
|
||||
elif state in ('masked', 'masked-runtime'):
|
||||
args = ['systemctl', 'mask']
|
||||
if state == 'masked-runtime':
|
||||
args.append('--runtime')
|
||||
run(args + [unit])
|
||||
if previous['active']:
|
||||
run(['systemctl', 'start', unit])
|
||||
|
||||
|
||||
def locate_journal():
|
||||
|
||||
Reference in New Issue
Block a user