From 8f57a731a4b66bf8e674525cc14cfe6d1d793061 Mon Sep 17 00:00:00 2001 From: "scanner[bot]" Date: Thu, 20 Aug 2026 13:46:23 -0400 Subject: [PATCH 1/4] [scanner] fix: redirect Bazaar Zed installs Signed-off-by: scanner[bot] --- system_files/bluefin/etc/bazaar/bazaar.yaml | 18 +++++++++ system_files/bluefin/etc/bazaar/hooks.py | 41 ++++++++++++++++++++- tests/test_hooks.py | 39 ++++++++++++++++++++ 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/system_files/bluefin/etc/bazaar/bazaar.yaml b/system_files/bluefin/etc/bazaar/bazaar.yaml index dd6d84251..0a4b778f3 100644 --- a/system_files/bluefin/etc/bazaar/bazaar.yaml +++ b/system_files/bluefin/etc/bazaar/bazaar.yaml @@ -57,3 +57,21 @@ hooks: # styling style: suggested shell: exec python3 /run/host/etc/bazaar/hooks.py + + - id: zed + when: before-transaction + dialogs: + - id: zed-warning + title: >- + Zed is managed through Homebrew on Bluefin + body: >- + Install Zed from the Universal Blue Homebrew tap for the supported + Bluefin integration. + default-response-id: cancel + options: + - id: cancel + string: "Cancel" + - id: download + string: "Download from Homebrew" + style: suggested + shell: exec python3 /run/host/etc/bazaar/hooks.py diff --git a/system_files/bluefin/etc/bazaar/hooks.py b/system_files/bluefin/etc/bazaar/hooks.py index d7b8e1264..bde7d90d7 100644 --- a/system_files/bluefin/etc/bazaar/hooks.py +++ b/system_files/bluefin/etc/bazaar/hooks.py @@ -26,11 +26,11 @@ def spawn_and_detach(args): def spawn_ujust(id): spawn_and_detach(['flatpak-spawn', '--host', 'xdg-terminal-exec', '-x', f'ujust {id}']) -def spawn_brew(app): +def spawn_brew(app, tap='ublue-os/tap'): brew = '/home/linuxbrew/.linuxbrew/bin/brew' spawn_and_detach([ 'flatpak-spawn', '--host', 'xdg-terminal-exec', '-x', - 'bash', '-c', f'{brew} install --cask {app}' + 'bash', '-c', f'{brew} tap --trust {tap} && {brew} install --cask {app}' ]) def handle_jetbrains(): @@ -105,6 +105,41 @@ def appid_is_code(appid): case 'teardown': return 'deny' +def handle_zed(): + + def appid_is_zed(appid): + return appid == 'dev.zed.Zed' + + match stage: + case 'setup': + if transaction_type == 'install' and appid_is_zed(transaction_appid): + return 'ok' + else: + return 'pass' + + case 'setup-dialog': + return 'ok' + + case 'teardown-dialog': + if dialog_response_id == 'download': + return 'ok' + else: + return 'abort' + + case 'catch': + return 'abort' + + case 'action': + try: + spawn_brew('ublue-os/experimental-tap/zed-linux', + 'ublue-os/experimental-tap') + except: + pass + return '' + + case 'teardown': + return 'deny' + # --- response = 'pass' @@ -113,6 +148,8 @@ def appid_is_code(appid): response = handle_jetbrains() case 'code': response = handle_code() + case 'zed': + response = handle_zed() print(response) sys.exit(0) diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 8e863e990..0a3e33f49 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -225,6 +225,45 @@ def test_action_codium_returns_empty(self): assert resp == "" +# --------------------------------------------------------------------------- +# Zed hook +# --------------------------------------------------------------------------- + +class TestZedHook: + def test_setup_install_zed_returns_ok(self): + resp = _load_hooks({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "setup", + "BAZAAR_TS_TYPE": "install", + "BAZAAR_TS_APPID": "dev.zed.Zed", + }) + assert resp == "ok" + + def test_setup_non_zed_returns_pass(self): + resp = _load_hooks({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "setup", + "BAZAAR_TS_TYPE": "install", + "BAZAAR_TS_APPID": "org.gnome.Calculator", + }) + assert resp == "pass" + + def test_teardown_dialog_download_returns_ok(self): + resp = _load_hooks({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "teardown-dialog", + "BAZAAR_HOOK_DIALOG_RESPONSE_ID": "download", + }) + assert resp == "ok" + + def test_teardown_returns_deny(self): + resp = _load_hooks({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "teardown", + }) + assert resp == "deny" + + # --------------------------------------------------------------------------- # Unknown hook ID # --------------------------------------------------------------------------- From e821077726cf6cdd59fdaf2b070f9f04dc763748 Mon Sep 17 00:00:00 2001 From: castrojo Date: Sun, 6 Sep 2026 01:07:49 +0000 Subject: [PATCH 2/4] chore: trigger CI with updated PR title From 8a3d126079debd3556759684857f906e90431e60 Mon Sep 17 00:00:00 2001 From: castrojo <1264109+castrojo@users.noreply.github.com> Date: Sat, 12 Sep 2026 17:55:09 -0400 Subject: [PATCH 3/4] fix(bazaar): tap then trust experimental tap in hooks.py Replace invalid 'brew tap --trust' syntax with separate 'brew tap' and 'brew trust' calls matching Homebrew 6.0 semantics and the repository standard. Narrow bare except to except Exception and add test assertion verifying brew command string. --- system_files/bluefin/etc/bazaar/hooks.py | 4 ++-- tests/test_hooks.py | 25 +++++++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/system_files/bluefin/etc/bazaar/hooks.py b/system_files/bluefin/etc/bazaar/hooks.py index bde7d90d7..92add7b36 100644 --- a/system_files/bluefin/etc/bazaar/hooks.py +++ b/system_files/bluefin/etc/bazaar/hooks.py @@ -30,7 +30,7 @@ def spawn_brew(app, tap='ublue-os/tap'): brew = '/home/linuxbrew/.linuxbrew/bin/brew' spawn_and_detach([ 'flatpak-spawn', '--host', 'xdg-terminal-exec', '-x', - 'bash', '-c', f'{brew} tap --trust {tap} && {brew} install --cask {app}' + 'bash', '-c', f'{brew} tap {tap} 2>/dev/null || true; {brew} trust {tap} 2>/dev/null || true; {brew} install --cask {app}' ]) def handle_jetbrains(): @@ -133,7 +133,7 @@ def appid_is_zed(appid): try: spawn_brew('ublue-os/experimental-tap/zed-linux', 'ublue-os/experimental-tap') - except: + except Exception: pass return '' diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 0a3e33f49..d37a3141e 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -23,6 +23,12 @@ def _load_hooks(env: dict) -> str: """Execute hooks.py under a given environment, return stdout.""" + resp, _ = _load_hooks_with_mock(env) + return resp + + +def _load_hooks_with_mock(env: dict) -> tuple[str, list]: + """Execute hooks.py under a given environment, return stdout and Popen calls.""" env_defaults = { "BAZAAR_HOOK_INITIATED_UNIX_STAMP": "0", "BAZAAR_HOOK_INITIATED_UNIX_STAMP_USEC": "0", @@ -40,14 +46,14 @@ def _load_hooks(env: dict) -> str: env_defaults.update(env) buf = io.StringIO() + popen_calls = [] with patch.dict(os.environ, env_defaults, clear=True): - # Mock subprocess.Popen so spawn helpers don't actually launch processes with patch("subprocess.Popen") as mock_popen: mock_popen.return_value = MagicMock() + mock_popen.side_effect = lambda args, **kwargs: (popen_calls.append(args), MagicMock())[1] spec = importlib.util.spec_from_file_location("hooks", HOOKS_PATH) mod = importlib.util.module_from_spec(spec) - # Redirect sys.stdout during exec so print() is captured old_stdout = sys.stdout sys.stdout = buf try: @@ -57,7 +63,7 @@ def _load_hooks(env: dict) -> str: finally: sys.stdout = old_stdout - return buf.getvalue().strip() + return buf.getvalue().strip(), popen_calls # --------------------------------------------------------------------------- @@ -263,6 +269,19 @@ def test_teardown_returns_deny(self): }) assert resp == "deny" + def test_action_spawns_brew_with_tap_and_trust(self): + resp, popen_calls = _load_hooks_with_mock({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "action", + }) + assert resp == "" + assert len(popen_calls) == 1 + cmd = " ".join(popen_calls[0]) + assert "brew tap ublue-os/experimental-tap" in cmd + assert "brew trust ublue-os/experimental-tap" in cmd + assert "--trust" not in cmd + assert "zed-linux" in cmd + # --------------------------------------------------------------------------- # Unknown hook ID From d68baa5214d856507eb9173a37bf08a819f01d71 Mon Sep 17 00:00:00 2001 From: castrojo <1264109+castrojo@users.noreply.github.com> Date: Fri, 18 Sep 2026 08:44:06 -0400 Subject: [PATCH 4/4] fix(bazaar): synchronize Zed hook with bazaar-hook and use ublue-os/tap - Update hooks.py and bazaar-hook to install ublue-os/tap/zed-linux from ublue-os/tap - Align code hook callers to use matching ublue-os/tap - Add full stage tests for Zed hook in test_hooks.py and test_bazaar_hook.py - Document Zed hook and dual hook maintenance rule in docs/skills/bazaar.md Assisted-by: Gemini via GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/skills/bazaar.md | 5 +- system_files/bluefin/etc/bazaar/hooks.py | 12 ++-- system_files/bluefin/usr/libexec/bazaar-hook | 37 ++++++++++ tests/test_bazaar_hook.py | 74 ++++++++++++++++++++ tests/test_hooks.py | 27 ++++++- 5 files changed, 145 insertions(+), 10 deletions(-) diff --git a/docs/skills/bazaar.md b/docs/skills/bazaar.md index a3311bddc..8f8b26941 100644 --- a/docs/skills/bazaar.md +++ b/docs/skills/bazaar.md @@ -27,7 +27,7 @@ metadata: - Editing Bazaar config in `system_files/bluefin/etc/bazaar/` - Porting curated-page structure across Bazaar schema versions -- Changing Bazaar hook behavior for app install interception +- Changing Bazaar hook behavior for app install interception (JetBrains, VS Code/Codium, Zed) - Adding or changing banner images (JXL→PNG conversion pipeline) - Validating Bazaar behavior locally before opening a PR @@ -50,6 +50,8 @@ metadata: | `tests/test_bazaar_hook.py` | `bazaar-hook` state machine tests | | `tests/test_curated_config.py` | Curated/Bazaar config shape regression checks | +Both hook scripts must remain synchronized: `hooks.py` (host `/run/host/etc/bazaar/hooks.py`) and `bazaar-hook` (`/usr/libexec/bazaar-hook`) must implement identical hook IDs, stages, and package redirect actions. + ## Curated schema and compatibility notes Bazaar supports two distinct configuration schemas depending on the installed Flatpak version. Because stable releases may lag behind upstream GitHub commits, agents must verify the local version's expected format before editing. @@ -168,6 +170,7 @@ just test - Editing curated content without local preview causes UI regressions to slip through. - Copying Aurora/Bazaar examples directly can leave non-Bluefin branding or links. - Changing hook dialog/response IDs must be mirrored in tests to avoid silent behavior drift. +- Editing `system_files/bluefin/etc/bazaar/hooks.py` without applying the same hook handler to `system_files/bluefin/usr/libexec/bazaar-hook` leaves the in-image entry point out of sync. - Dropping `set -e` from the JXL conversion RUN step lets silent build failures through. - Using `--color_space=sRGB` instead of `-C sRGB` breaks the conversion with "Unknown argument" error. diff --git a/system_files/bluefin/etc/bazaar/hooks.py b/system_files/bluefin/etc/bazaar/hooks.py index 92add7b36..af81eeca1 100644 --- a/system_files/bluefin/etc/bazaar/hooks.py +++ b/system_files/bluefin/etc/bazaar/hooks.py @@ -26,11 +26,12 @@ def spawn_and_detach(args): def spawn_ujust(id): spawn_and_detach(['flatpak-spawn', '--host', 'xdg-terminal-exec', '-x', f'ujust {id}']) -def spawn_brew(app, tap='ublue-os/tap'): +def spawn_brew(app): brew = '/home/linuxbrew/.linuxbrew/bin/brew' spawn_and_detach([ 'flatpak-spawn', '--host', 'xdg-terminal-exec', '-x', - 'bash', '-c', f'{brew} tap {tap} 2>/dev/null || true; {brew} trust {tap} 2>/dev/null || true; {brew} install --cask {app}' + 'bash', '-c', + f'{brew} tap ublue-os/tap; {brew} trust ublue-os/tap; {brew} install --cask {app}' ]) def handle_jetbrains(): @@ -95,9 +96,9 @@ def appid_is_code(appid): case 'action': try: if transaction_appid == ('com.vscodium.codium'): - spawn_brew('ublue/tap/vscodium-linux') + spawn_brew('ublue-os/tap/vscodium-linux') else: - spawn_brew('ublue/tap/visual-studio-code-linux') + spawn_brew('ublue-os/tap/visual-studio-code-linux') except: pass return '' @@ -131,8 +132,7 @@ def appid_is_zed(appid): case 'action': try: - spawn_brew('ublue-os/experimental-tap/zed-linux', - 'ublue-os/experimental-tap') + spawn_brew('ublue-os/tap/zed-linux') except Exception: pass return '' diff --git a/system_files/bluefin/usr/libexec/bazaar-hook b/system_files/bluefin/usr/libexec/bazaar-hook index b4fae5477..ee0c7fa19 100755 --- a/system_files/bluefin/usr/libexec/bazaar-hook +++ b/system_files/bluefin/usr/libexec/bazaar-hook @@ -107,6 +107,41 @@ def handle_code(): case 'teardown': return 'deny' +def handle_zed(): + + def appid_is_zed(appid): + return appid == 'dev.zed.Zed' + + match stage: + case 'setup': + if transaction_type == 'install' and appid_is_zed(transaction_appid): + return 'ok' + else: + return 'pass' + + case 'setup-dialog': + return 'ok' + + case 'teardown-dialog': + if dialog_response_id == 'download': + return 'ok' + else: + return 'abort' + + case 'catch': + return 'abort' + + case 'action': + try: + spawn_brew('ublue-os/tap/zed-linux') + except Exception: + pass + return '' + + case 'teardown': + return 'deny' + + # --- response = 'pass' @@ -115,6 +150,8 @@ match hook_id: response = handle_jetbrains() case 'code': response = handle_code() + case 'zed': + response = handle_zed() print(response) sys.exit(0) diff --git a/tests/test_bazaar_hook.py b/tests/test_bazaar_hook.py index 09e59a892..0024ea24d 100644 --- a/tests/test_bazaar_hook.py +++ b/tests/test_bazaar_hook.py @@ -279,6 +279,80 @@ def test_teardown_returns_deny(self): assert resp == "deny" +# --------------------------------------------------------------------------- +# Zed hook +# --------------------------------------------------------------------------- + +class TestZedHook: + def test_setup_install_zed_returns_ok(self): + resp = _run_hook({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "setup", + "BAZAAR_TS_TYPE": "install", + "BAZAAR_TS_APPID": "dev.zed.Zed", + }) + assert resp == "ok" + + def test_setup_non_zed_returns_pass(self): + resp = _run_hook({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "setup", + "BAZAAR_TS_TYPE": "install", + "BAZAAR_TS_APPID": "org.mozilla.firefox", + }) + assert resp == "pass" + + def test_setup_dialog_returns_ok(self): + resp = _run_hook({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "setup-dialog", + }) + assert resp == "ok" + + def test_teardown_dialog_download_returns_ok(self): + resp = _run_hook({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "teardown-dialog", + "BAZAAR_HOOK_DIALOG_RESPONSE_ID": "download", + }) + assert resp == "ok" + + def test_teardown_dialog_cancel_returns_abort(self): + resp = _run_hook({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "teardown-dialog", + "BAZAAR_HOOK_DIALOG_RESPONSE_ID": "cancel", + }) + assert resp == "abort" + + def test_catch_returns_abort(self): + resp = _run_hook({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "catch", + }) + assert resp == "abort" + + def test_action_spawns_brew_and_returns_empty(self): + resp, popen_calls = _run_hook_with_mock({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "action", + "BAZAAR_TS_APPID": "dev.zed.Zed", + }) + assert resp == "" + assert len(popen_calls) == 1 + cmd = " ".join(popen_calls[0]) + assert "brew tap ublue-os/tap" in cmd + assert "brew trust ublue-os/tap" in cmd + assert "zed-linux" in cmd + + def test_teardown_returns_deny(self): + resp = _run_hook({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "teardown", + }) + assert resp == "deny" + + # --------------------------------------------------------------------------- # Unknown hook ID # --------------------------------------------------------------------------- diff --git a/tests/test_hooks.py b/tests/test_hooks.py index d37a3141e..b8888d30b 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -254,6 +254,13 @@ def test_setup_non_zed_returns_pass(self): }) assert resp == "pass" + def test_setup_dialog_returns_ok(self): + resp = _load_hooks({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "setup-dialog", + }) + assert resp == "ok" + def test_teardown_dialog_download_returns_ok(self): resp = _load_hooks({ "BAZAAR_HOOK_ID": "zed", @@ -262,6 +269,21 @@ def test_teardown_dialog_download_returns_ok(self): }) assert resp == "ok" + def test_teardown_dialog_cancel_returns_abort(self): + resp = _load_hooks({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "teardown-dialog", + "BAZAAR_HOOK_DIALOG_RESPONSE_ID": "cancel", + }) + assert resp == "abort" + + def test_catch_returns_abort(self): + resp = _load_hooks({ + "BAZAAR_HOOK_ID": "zed", + "BAZAAR_HOOK_STAGE": "catch", + }) + assert resp == "abort" + def test_teardown_returns_deny(self): resp = _load_hooks({ "BAZAAR_HOOK_ID": "zed", @@ -277,12 +299,11 @@ def test_action_spawns_brew_with_tap_and_trust(self): assert resp == "" assert len(popen_calls) == 1 cmd = " ".join(popen_calls[0]) - assert "brew tap ublue-os/experimental-tap" in cmd - assert "brew trust ublue-os/experimental-tap" in cmd + assert "brew tap ublue-os/tap" in cmd + assert "brew trust ublue-os/tap" in cmd assert "--trust" not in cmd assert "zed-linux" in cmd - # --------------------------------------------------------------------------- # Unknown hook ID # ---------------------------------------------------------------------------