OTA committed for troubleshooting

This commit is contained in:
2026-04-06 12:19:23 +02:00
parent 1907e97049
commit 53f3b026c6

69
ota.py
View File

@@ -7,13 +7,17 @@ on the next boot rather than bricking the device.
Strategy Strategy
-------- --------
1. Fetch ota_manifest.txt from the repo to determine which files to sync. 1. Check if last boot was good (OK flag exists).
2. Compare SHA1 hashes with a local manifest (.ota_manifest.json). 2. If good, fetch remote commit SHA and compare with local — if unchanged,
3. Download only changed or missing files, writing to .tmp first. skip file check entirely.
4. On success, rename .tmp files into place and update the manifest. 3. If new commit or failed boot, fetch ota_manifest.txt from the repo
5. If anything fails mid-update, the manifest is not updated, so the to determine which files to sync.
4. Compare SHA1 hashes with a local manifest (.ota_manifest.json).
5. Download only changed or missing files, writing to .tmp first.
6. On success, rename .tmp files into place and update the manifest.
7. If anything fails mid-update, the manifest is not updated, so the
next boot will retry. Partially written .tmp files are cleaned up. next boot will retry. Partially written .tmp files are cleaned up.
6. A "safety" flag file (.ota_ok) is written by main.py on successful 8. A "safety" flag file (.ota_ok) is written by main.py on successful
startup. If it is absent on boot, the previous update is suspected startup. If it is absent on boot, the previous update is suspected
bad — the manifest is wiped so all files are re-fetched cleanly. bad — the manifest is wiped so all files are re-fetched cleanly.
@@ -146,6 +150,26 @@ def _match_pattern(name, pattern):
i += 1 i += 1
return i == n and j == m return i == n and j == m
def _fetch_commit_sha():
url = (
f"{GITEA_BASE}/api/v1/repos/{REPO_OWNER}/{REPO_NAME}"
f"/branches/{REPO_BRANCH}"
)
info(f"Fetching commit from {url}")
try:
r = urequests.get(url, headers=_headers())
info(f"Response status: {r.status_code}")
if r.status_code == 200:
data = r.json()
r.close()
sha = data.get("commit", {}).get("sha")
info(f"Got commit: {sha}")
return sha
r.close()
except Exception as e:
log_err(f"Failed to fetch commit: {e}")
return None
def _fetch_manifest(): def _fetch_manifest():
url = ( url = (
f"{GITEA_BASE}/api/v1/repos/{REPO_OWNER}/{REPO_NAME}" f"{GITEA_BASE}/api/v1/repos/{REPO_OWNER}/{REPO_NAME}"
@@ -223,9 +247,11 @@ def _load_manifest():
except Exception: except Exception:
return {} return {}
def _save_manifest(manifest): def _save_manifest(manifest, commit_sha=None):
try: try:
with open(MANIFEST_FILE, "w") as f: with open(MANIFEST_FILE, "w") as f:
if commit_sha:
manifest["_commit"] = commit_sha
ujson.dump(manifest, f) ujson.dump(manifest, f)
except Exception as e: except Exception as e:
warn(f"Could not save manifest: {e}") warn(f"Could not save manifest: {e}")
@@ -311,7 +337,7 @@ def _fetch_file_list():
fetch_matching(root, manifest_patterns) fetch_matching(root, manifest_patterns)
return files return files
def _do_update(): def _do_update(commit_sha=None):
""" """
Fetch file list, download changed files, update manifest. Fetch file list, download changed files, update manifest.
Returns True if all succeeded (or nothing needed updating). Returns True if all succeeded (or nothing needed updating).
@@ -348,11 +374,10 @@ def _do_update():
if failed: if failed:
log_err(f"Update incomplete — {len(failed)} file(s) failed: {failed}") log_err(f"Update incomplete — {len(failed)} file(s) failed: {failed}")
# Save partial manifest so successful files aren't re-downloaded _save_manifest(manifest, commit_sha)
_save_manifest(manifest)
return False return False
_save_manifest(manifest) _save_manifest(manifest, commit_sha)
if updated: if updated:
info(f"Update complete — {len(updated)} file(s) updated: {updated}") info(f"Update complete — {len(updated)} file(s) updated: {updated}")
@@ -371,6 +396,8 @@ def update():
- If the OK flag is missing, the previous boot is assumed to have - If the OK flag is missing, the previous boot is assumed to have
failed — wipes the manifest so everything is re-fetched cleanly. failed — wipes the manifest so everything is re-fetched cleanly.
- If the commit hash hasn't changed and last boot was good, skip
file comparison entirely.
- Runs the update. - Runs the update.
- Clears the OK flag so main.py must re-assert it on successful start. - Clears the OK flag so main.py must re-assert it on successful start.
""" """
@@ -380,17 +407,33 @@ def update():
load_config() load_config()
if not _ok_flag_exists(): ok_flag = _ok_flag_exists()
manifest = _load_manifest()
if not ok_flag:
warn("OK flag missing — last boot may have failed") warn("OK flag missing — last boot may have failed")
warn("Wiping manifest to force full re-fetch") warn("Wiping manifest to force full re-fetch")
_wipe_manifest() _wipe_manifest()
manifest = {}
else: else:
info("OK flag present — last boot was good") info("OK flag present — last boot was good")
commit_sha = _fetch_commit_sha()
if ok_flag and commit_sha and manifest.get("_commit") == commit_sha:
info(f"Commit unchanged ({commit_sha[:8]}) — skipping file check")
info("-" * 40)
return
if commit_sha:
info(f"Remote commit: {commit_sha[:8]}")
else:
warn("Could not fetch remote commit — proceeding with file check")
# Clear the flag now; main.py must call ota.mark_ok() to re-set it # Clear the flag now; main.py must call ota.mark_ok() to re-set it
_clear_ok_flag() _clear_ok_flag()
success = _do_update() success = _do_update(commit_sha)
if success: if success:
info("OTA check complete — booting application") info("OTA check complete — booting application")