mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-20 04:21:23 +00:00
* feat(ota): bundle full server resources tree (server + third_party bins) The OTA Sparkle payload now ships the complete resources/ tree the agent build produced, not just browseros_server. Every third-party binary (bun, ripgrep, podman, gvproxy, vfkit, krunkit, podman-mac-helper, win-sshproxy) flows to OTA-updated installs so podman integration works for users on the OTA channel, matching fresh Chromium-build installs. Extract the per-binary sign table into build/common/server_binaries.py so the Chromium-build sign path (modules/sign/) and OTA sign path (modules/ota/) share a single source of truth. Adding a new third-party dep is now a one-file edit that both paths pick up automatically; unknown executables under resources/bin/ are a hard error at release time. * fix(ota): address review comments on bundle signing flow - Avoid double-zipping during notarization: add notarize_macos_zip for pre-built Sparkle bundles so notarytool submits the zip directly instead of re-wrapping it through ditto --keepParent (Apple's service does not descend into nested archives). Keep notarize_macos_binary for single-binary callers. Share credential setup + submit logic via internal helpers. - Fail fast on unknown executables in sign_server_bundle_macos: collect the unknown-files list before any codesign call so a missing shared- table entry aborts in seconds, not after a full signing round. - Drop dead get_entitlements_path helper (no callers remain after the bundle refactor). * fix(ota): address PR review comments (greptile + claude) - sign_server_bundle_macos filters to executables only (p.is_file() + not p.is_symlink() + os.access X_OK) before applying the unknown-file guard. Non-Mach-O files (configs, dylibs, etc.) under resources/bin/ no longer cause misleading 'unknown executable' hard failures. - sign_server_bundle_windows now hard-errors on a missing expected binary instead of silently skipping it. Symmetric with the macOS guard — an incomplete bundle must not publish. - ServerOTAModule.execute() uses tempfile.TemporaryDirectory context managers for both the download and staging roots so they are cleaned up on every path, including failures. - Per-platform sign/notarize/Sparkle-sign failures now raise RuntimeError instead of silently skipping the platform — a release pipeline can no longer omit a target while reporting success. - Move import os and import shutil to the top of ota/sign_binary.py. - Drop unused log_error import from ota/server.py. * chore: bump server
94 lines
3.4 KiB
Python
Generated
94 lines
3.4 KiB
Python
Generated
#!/usr/bin/env python3
|
|
"""Tests for OTA bundle-zip creation."""
|
|
|
|
import stat
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
from .common import create_server_bundle_zip, find_server_resources_dir
|
|
|
|
|
|
def _write_exec(path: Path, content: bytes) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_bytes(content)
|
|
path.chmod(path.stat().st_mode | 0o755)
|
|
|
|
|
|
class CreateServerBundleZipTest(unittest.TestCase):
|
|
def test_bundles_full_resources_tree(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
staging = Path(tmp) / "darwin-arm64"
|
|
resources = staging / "resources"
|
|
_write_exec(resources / "bin" / "browseros_server", b"server")
|
|
_write_exec(resources / "bin" / "third_party" / "bun", b"bun")
|
|
_write_exec(resources / "bin" / "third_party" / "rg", b"rg")
|
|
_write_exec(resources / "bin" / "third_party" / "podman" / "podman", b"pd")
|
|
_write_exec(
|
|
resources / "bin" / "third_party" / "podman" / "gvproxy", b"gv"
|
|
)
|
|
|
|
zip_path = Path(tmp) / "bundle.zip"
|
|
self.assertTrue(create_server_bundle_zip(resources, zip_path))
|
|
|
|
with zipfile.ZipFile(zip_path) as zf:
|
|
names = set(zf.namelist())
|
|
|
|
self.assertEqual(
|
|
names,
|
|
{
|
|
"resources/bin/browseros_server",
|
|
"resources/bin/third_party/bun",
|
|
"resources/bin/third_party/rg",
|
|
"resources/bin/third_party/podman/podman",
|
|
"resources/bin/third_party/podman/gvproxy",
|
|
},
|
|
)
|
|
|
|
@unittest.skipIf(sys.platform == "win32", "file mode check is meaningless on Windows")
|
|
def test_preserves_executable_bits(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
resources = Path(tmp) / "darwin-arm64" / "resources"
|
|
_write_exec(resources / "bin" / "browseros_server", b"server")
|
|
|
|
zip_path = Path(tmp) / "bundle.zip"
|
|
self.assertTrue(create_server_bundle_zip(resources, zip_path))
|
|
|
|
with zipfile.ZipFile(zip_path) as zf:
|
|
info = zf.getinfo("resources/bin/browseros_server")
|
|
|
|
mode = (info.external_attr >> 16) & 0o777
|
|
self.assertTrue(mode & stat.S_IXUSR)
|
|
|
|
def test_missing_resources_dir_fails(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
missing = Path(tmp) / "does-not-exist"
|
|
zip_path = Path(tmp) / "bundle.zip"
|
|
self.assertFalse(create_server_bundle_zip(missing, zip_path))
|
|
|
|
|
|
class FindServerResourcesDirTest(unittest.TestCase):
|
|
def test_returns_resources_dir_when_present(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
(root / "darwin-arm64" / "resources" / "bin").mkdir(parents=True)
|
|
found = find_server_resources_dir(
|
|
root, {"name": "darwin_arm64", "target": "darwin-arm64"}
|
|
)
|
|
self.assertEqual(found, root / "darwin-arm64" / "resources")
|
|
|
|
def test_returns_none_when_absent(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
self.assertIsNone(
|
|
find_server_resources_dir(
|
|
root, {"name": "darwin_arm64", "target": "darwin-arm64"}
|
|
)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|