Prepare 0.3.3 and migrate recognized legacy console wrappers
This commit is contained in:
1 parent
c0a679d989
commit
b1ff87dd81
5 files changed
+55
-4
No files matched your search
@@ -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.
|
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:
|
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()
|
"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):
|
def _legacy_identity(node, runtime):
|
||||||
return {"socket": runtime / (node.lower() + "-tty1") / "console.sock",
|
return {"socket": runtime / (node.lower() + "-tty1") / "console.sock",
|
||||||
"session": node.upper() + "-FBTERM"}
|
"session": node.upper() + "-FBTERM"}
|
||||||
@@ -123,7 +155,9 @@ def migrate_legacy(state, *, home=None, node=None, runtime=None):
|
|||||||
after = _new_entry(state)
|
after = _new_entry(state)
|
||||||
if before == after:
|
if before == after:
|
||||||
_need(legacy_context(state, node, runtime) is not None, "CONSOLE_DESKTOP_RECORD_MISSING")
|
_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):
|
if before != _legacy_entry(home):
|
||||||
return {"ok": True, "changed": False, "reason": "CUSTOM_DESKTOP_ENTRY_PRESERVED"}
|
return {"ok": True, "changed": False, "reason": "CUSTOM_DESKTOP_ENTRY_PRESERVED"}
|
||||||
wrapper, _ = _snapshot(home / ".local/bin/framebridge-console")
|
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())
|
_atomic(state / RECORD, (json.dumps(record, sort_keys=True, indent=2) + "\n").encode())
|
||||||
_need(_snapshot(desktop)[0] == before, "CONSOLE_DESKTOP_CHANGED_DURING_MIGRATION")
|
_need(_snapshot(desktop)[0] == before, "CONSOLE_DESKTOP_CHANGED_DURING_MIGRATION")
|
||||||
_atomic(desktop, after, mode)
|
_atomic(desktop, after, mode)
|
||||||
|
wrapper_changed = _migrate_wrapper(state, home)
|
||||||
return {"ok": True, "changed": True, "desktop": str(desktop),
|
return {"ok": True, "changed": True, "desktop": str(desktop),
|
||||||
|
"wrapper_changed": wrapper_changed,
|
||||||
"session": identity["session"], "socket": str(identity["socket"]),
|
"session": identity["session"], "socket": str(identity["socket"]),
|
||||||
"backup": str(backup), "runtime_changed": False}
|
"backup": str(backup), "runtime_changed": False}
|
||||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
CANDIDATE_VERSION = "0.3.2"
|
CANDIDATE_VERSION = "0.3.3"
|
||||||
VENDOR_RELATIVE = Path("original/geranas/FrameBridge-Fleet-Hub/vendor")
|
VENDOR_RELATIVE = Path("original/geranas/FrameBridge-Fleet-Hub/vendor")
|
||||||
SOURCE_VENDOR = Path("sources/original-geranas/FrameBridge-Fleet-Hub/vendor")
|
SOURCE_VENDOR = Path("sources/original-geranas/FrameBridge-Fleet-Hub/vendor")
|
||||||
GLIBC_BASELINE = (2, 34)
|
GLIBC_BASELINE = (2, 34)
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ class DesktopMigrationTests(unittest.TestCase):
|
|||||||
self.assertFalse(value["runtime_changed"])
|
self.assertFalse(value["runtime_changed"])
|
||||||
self.assertEqual((self.state / "console-desktop-legacy.backup").read_bytes(), before)
|
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.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)
|
context = console_desktop.legacy_context(self.state, "TestHost", self.runtime)
|
||||||
self.assertEqual(context["session"], "TESTHOST-FBTERM")
|
self.assertEqual(context["session"], "TESTHOST-FBTERM")
|
||||||
self.assertEqual(context["socket"], self.runtime / "testhost-tty1/console.sock")
|
self.assertEqual(context["socket"], self.runtime / "testhost-tty1/console.sock")
|
||||||
@@ -122,6 +125,18 @@ class ConsoleWindowSelectionTests(unittest.TestCase):
|
|||||||
value = console.exists()
|
value = console.exists()
|
||||||
return console, value
|
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):
|
def test_monitor_receives_profile_jobs_root_as_an_explicit_argument(self):
|
||||||
args = SimpleNamespace(socket="/owned/socket", session="NODE-FBTERM", node="NODE",
|
args = SimpleNamespace(socket="/owned/socket", session="NODE-FBTERM", node="NODE",
|
||||||
jobs_root=Path("/profile with space/jobs"))
|
jobs_root=Path("/profile with space/jobs"))
|
||||||
|
|||||||
Reference in new issue
Block a user