From c2b0c3d35e4858f1d09e4a5006428a78e808829d Mon Sep 17 00:00:00 2001 From: cproudlock Date: Sat, 17 Oct 2026 12:40:00 -0400 Subject: [PATCH] Share accounts: an older live Blancco release warns instead of failing A fresh server gets boot-tools/blancco from the USB, which can be an older Blancco (7.15.1) than our preferences (7.20). The version guard refused to patch its config.img and the exception failed the whole share apply, so the first-boot playbook failed. refresh_live_prefs() now returns a warning for config.img and still writes the served preferences.xml (UEFI path). --- webapp/services/blancco_update.py | 17 +++++++++++++---- webapp/services/share_accounts.py | 4 +++- webapp/tests/test_share_accounts.py | 17 +++++++++++++++++ webapp/views/blancco_update.py | 6 ++++-- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/webapp/services/blancco_update.py b/webapp/services/blancco_update.py index 69dd13a..f61d376 100644 --- a/webapp/services/blancco_update.py +++ b/webapp/services/blancco_update.py @@ -991,9 +991,13 @@ def apply_prefs(version): def refresh_live_prefs(): """Re-render the served preferences.xml (site settings, share login) and write the same preferences into the live config.img, which the - BIOS (native) boot path reads instead. Returns True if a file changed. - release.json keeps describing the release when it did before.""" + BIOS (native) boot path reads instead. Returns (changed, warning): + warning is set when config.img could not take them (a release older + than our preferences, see check_prefs); the served file, which the + UEFI path uses, is written regardless. release.json keeps describing + the release when it did before.""" from services import blancco_prefs + warning = None with _lock: root = blancco_root() meta_path = os.path.join(root, "release.json") @@ -1005,13 +1009,18 @@ def refresh_live_prefs(): if cfg is not None: with open(blancco_prefs.served_prefs_path(), "rb") as f: ours = f.read() - new = patch_config_img(cfg, ours) + try: + new = patch_config_img(cfg, ours) + except (StageBlocked, ValueError) as e: + warning = (f"the live config.img (BIOS boot) was not updated: {e} " + "Stage a current release on Updates > Blancco releases.") + new = cfg if new != cfg: _atomic_write(cfg_path, new) changed = True if changed and meta_ok: _write_live_meta(meta) - return changed + return changed, warning def save_merged_prefs(version, data): diff --git a/webapp/services/share_accounts.py b/webapp/services/share_accounts.py index 77ef4bf..c29c823 100644 --- a/webapp/services/share_accounts.py +++ b/webapp/services/share_accounts.py @@ -177,7 +177,9 @@ def _apply_menu(password): def _apply_blancco(): from services import blancco_update as bu - return "Blancco preferences: " + ("updated" if bu.refresh_live_prefs() else "unchanged") + changed, warning = bu.refresh_live_prefs() + out = "Blancco preferences: " + ("updated" if changed else "unchanged") + return out + (f" (WARNING: {warning})" if warning else "") def apply(only=None, values=None): diff --git a/webapp/tests/test_share_accounts.py b/webapp/tests/test_share_accounts.py index 00c6007..53560c2 100644 --- a/webapp/tests/test_share_accounts.py +++ b/webapp/tests/test_share_accounts.py @@ -179,6 +179,23 @@ class ShareAccounts(Fixture): with open(BP.repo_prefs_path(), "rb") as f: self.assertEqual(self.prefs_share_pw(BP.apply(f.read())), PW) + def test_older_live_release_warns_instead_of_failing(self): + # A config.img from an older Blancco than our preferences (the repo + # payload can lag): the version guard refuses it. The served + # preferences (UEFI path) still change; the change is not refused. + old = cpio_file(self.cfg, "preferences.xml").replace( + b"20", b"15", 1) + self.assertIn(b"15", old) + cfg = newc({"report.key": b"KEY", "preferences.xml": old, "config.xml": b""}) + with open(os.path.join(BU.blancco_root(), "config.img"), "wb") as f: + f.write(cfg) + done = SA.set_password("blancco", PW) + self.assertTrue(any("WARNING" in d and "config.img" in d for d in done), done) + with open(BP.served_prefs_path(), "rb") as f: + self.assertEqual(self.prefs_share_pw(f.read()), PW) + with open(os.path.join(BU.blancco_root(), "config.img"), "rb") as f: + self.assertEqual(f.read(), cfg) # left as it was + def test_apply_is_idempotent(self): SA.set_password("pxe-upload", PW) SA.set_password("clonezilla", PW + "x") diff --git a/webapp/views/blancco_update.py b/webapp/views/blancco_update.py index 668e934..5d444e1 100644 --- a/webapp/views/blancco_update.py +++ b/webapp/views/blancco_update.py @@ -53,8 +53,8 @@ def site_settings(): request.form.get("wifi_ssid", ""), request.form.get("wifi_password", "")) # Served preferences.xml and the live config.img (BIOS path). - changed = bu.refresh_live_prefs() - except (OSError, ValueError, bu.StageBlocked) as e: + changed, warning = bu.refresh_live_prefs() + except (OSError, ValueError) as e: flash(f"Blancco site settings not saved: {e}", "danger") return _back() s = blancco_prefs.public() @@ -62,6 +62,8 @@ def site_settings(): f"password={'set' if s['wifi_password_set'] else 'empty'}") flash("Blancco site settings saved" + (" and preferences.xml updated." if changed else " (no change)."), "success") + if warning: + flash(f"Blancco: {warning}", "warning") return _back()