Prepare 0.3.3 and migrate recognized legacy console wrappers

This commit is contained in:
Gerardo Betancourt committed 2026-10-28 12:07:52 -04:00
1 parent c0a679d989
commit b1ff87dd81
5 files changed
+55 -4

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ The desktop console and its authenticated tty1 view share the existing local Bas
Older private installations used a user-level desktop launcher. That launcher could hide the package launcher and reject a valid console after other work windows were added. Version 0.3.3 targets the existing named `Console` window, while preserving the other windows.
During an upgrade, a running profile can migrate the exact recognized old launcher to the packaged CLI. Custom launchers and changed legacy components are preserved. The old desktop entry is backed up privately in the profile; the console socket/session binding is recorded there. No login or boot policy changes.
During an upgrade, a running profile can migrate the exact recognized old launcher to the packaged CLI. Custom launchers and changed legacy components are preserved. The exact recognized legacy wrapper is also redirected to the packaged CLI, so desktop or panel copies that invoke it keep working. The old desktop entry and wrapper are backed up privately in the profile; the console socket/session binding is recorded there. No login or boot policy changes.
For an explicit owner repair, use the real configured profile:
@@ -1 +1 @@
__version__ = "0.3.2+private.20261028.2"
__version__ = "0.3.3"
@@ -78,6 +78,38 @@ def _new_entry(state):
"StartupWMClass=Gnome-terminal\n").encode()
def _new_wrapper(state):
command = ["/usr/bin/fb", "--state-dir", str(state), "console"]
return ("#!/usr/bin/python3\n"
'"""Open the packaged FrameBridge console using the retained local profile."""\n'
"import os\nimport sys\n"
"command = " + repr(command) + "\n"
"if sys.argv[1:] == ['--inside']:\n"
" command += ['--inside', '--mode', 'gui']\n"
"elif sys.argv[1:]:\n"
" raise SystemExit('Usage: framebridge-console')\n"
"os.execv(command[0], command)\n").encode()
def _migrate_wrapper(state, home):
path = home / ".local/bin/framebridge-console"
before, mode = _snapshot(path)
after = _new_wrapper(state)
if before == after:
return False
_need(hashlib.sha256(before).hexdigest() == LEGACY_WRAPPER_SHA256,
"CONSOLE_DESKTOP_LEGACY_WRAPPER_CHANGED")
backup = state / "console-wrapper-legacy.backup"
if backup.exists() or backup.is_symlink():
data, backup_mode = _snapshot(backup)
_need(data == before and backup_mode == 0o600, "CONSOLE_DESKTOP_WRAPPER_BACKUP_CHANGED")
else:
_atomic(backup, before)
_need(_snapshot(path)[0] == before, "CONSOLE_DESKTOP_WRAPPER_CHANGED_DURING_MIGRATION")
_atomic(path, after, mode)
return True
def _legacy_identity(node, runtime):
return {"socket": runtime / (node.lower() + "-tty1") / "console.sock",
"session": node.upper() + "-FBTERM"}
@@ -123,7 +155,9 @@ def migrate_legacy(state, *, home=None, node=None, runtime=None):
after = _new_entry(state)
if before == after:
_need(legacy_context(state, node, runtime) is not None, "CONSOLE_DESKTOP_RECORD_MISSING")
return {"ok": True, "changed": False, "reason": "ALREADY_MIGRATED"}
wrapper_changed = _migrate_wrapper(state, home)
return {"ok": True, "changed": wrapper_changed, "reason": "ALREADY_MIGRATED",
"wrapper_changed": wrapper_changed}
if before != _legacy_entry(home):
return {"ok": True, "changed": False, "reason": "CUSTOM_DESKTOP_ENTRY_PRESERVED"}
wrapper, _ = _snapshot(home / ".local/bin/framebridge-console")
@@ -166,6 +200,8 @@ def migrate_legacy(state, *, home=None, node=None, runtime=None):
_atomic(state / RECORD, (json.dumps(record, sort_keys=True, indent=2) + "\n").encode())
_need(_snapshot(desktop)[0] == before, "CONSOLE_DESKTOP_CHANGED_DURING_MIGRATION")
_atomic(desktop, after, mode)
wrapper_changed = _migrate_wrapper(state, home)
return {"ok": True, "changed": True, "desktop": str(desktop),
"wrapper_changed": wrapper_changed,
"session": identity["session"], "socket": str(identity["socket"]),
"backup": str(backup), "runtime_changed": False}
+1 -1
View File
@@ -11,7 +11,7 @@ from pathlib import Path
import re
CANDIDATE_VERSION = "0.3.2"
CANDIDATE_VERSION = "0.3.3"
VENDOR_RELATIVE = Path("original/geranas/FrameBridge-Fleet-Hub/vendor")
SOURCE_VENDOR = Path("sources/original-geranas/FrameBridge-Fleet-Hub/vendor")
GLIBC_BASELINE = (2, 34)
+15
View File
@@ -56,6 +56,9 @@ class DesktopMigrationTests(unittest.TestCase):
self.assertFalse(value["runtime_changed"])
self.assertEqual((self.state / "console-desktop-legacy.backup").read_bytes(), before)
self.assertEqual(self.desktop.read_bytes(), console_desktop._new_entry(self.state))
self.assertEqual(self.wrapper.read_bytes(), console_desktop._new_wrapper(self.state))
self.assertEqual((self.state / "console-wrapper-legacy.backup").read_bytes(), b"#!/usr/bin/python3\npass\n")
self.assertTrue(value["wrapper_changed"])
context = console_desktop.legacy_context(self.state, "TestHost", self.runtime)
self.assertEqual(context["session"], "TESTHOST-FBTERM")
self.assertEqual(context["socket"], self.runtime / "testhost-tty1/console.sock")
@@ -122,6 +125,18 @@ class ConsoleWindowSelectionTests(unittest.TestCase):
value = console.exists()
return console, value
def test_migrated_wrapper_targets_the_packaged_profile_without_a_shell(self):
state = Path("/home/user/profile")
with mock.patch("sys.argv", ["framebridge-console"]), mock.patch("os.execv") as execute:
exec(compile(console_desktop._new_wrapper(state), "wrapper", "exec"), {})
execute.assert_called_once_with("/usr/bin/fb", ["/usr/bin/fb", "--state-dir", str(state), "console"])
def test_migrated_inside_wrapper_does_not_create_another_viewer(self):
state = Path("/home/user/profile")
with mock.patch("sys.argv", ["framebridge-console", "--inside"]), mock.patch("os.execv") as execute:
exec(compile(console_desktop._new_wrapper(state), "wrapper", "exec"), {})
self.assertEqual(execute.call_args.args[1][-3:], ["--inside", "--mode", "gui"])
def test_monitor_receives_profile_jobs_root_as_an_explicit_argument(self):
args = SimpleNamespace(socket="/owned/socket", session="NODE-FBTERM", node="NODE",
jobs_root=Path("/profile with space/jobs"))