Magnifica_S-Pro/firmware/boot.py

93 lines
2.0 KiB
Python

"""
Stable recovery boot stub.
Install once as /boot.py. It is deliberately excluded from normal OTA updates.
If a staged launcher/application update is interrupted or cannot boot, this
restores the previous .bak files.
"""
try:
import ujson as json
except ImportError:
import json
try:
import uos as os
except ImportError:
import os
PENDING_FILE = "/update_pending.json"
def exists(path):
try:
os.stat(path)
return True
except OSError:
return False
def remove(path):
try:
os.remove(path)
except OSError:
pass
def read_pending():
if not exists(PENDING_FILE):
return None
try:
with open(PENDING_FILE, "r") as f:
return json.loads(f.read())
except Exception:
return None
def rollback(pending):
if not pending:
return False
restored = False
for item in reversed(pending.get("files", [])):
target = item.get("target")
backup = item.get("backup")
if not target or not backup:
continue
try:
if exists(backup):
remove(target)
os.rename(backup, target)
restored = True
except Exception as exc:
print("[boot] rollback failed:", target, exc)
remove(PENDING_FILE)
return restored
pending = read_pending()
# Never boot a half-committed file set after a power loss.
if pending and not pending.get("commit_complete", False):
print("[boot] interrupted OTA commit; rolling back")
rollback(pending)
try:
import machine
machine.reset()
except Exception:
raise SystemExit
try:
import launcher
launcher.run()
except Exception as exc:
print("[boot] launcher/application startup failed:", exc)
pending = read_pending()
if pending:
print("[boot] reverting pending OTA")
rollback(pending)
try:
import machine
machine.reset()
except Exception:
pass
raise