diff --git a/packages/browseros/build/build.py b/packages/browseros/build/build.py
index cf9cef56..1c8239e8 100755
--- a/packages/browseros/build/build.py
+++ b/packages/browseros/build/build.py
@@ -307,12 +307,16 @@ def build_main(
ctx, interactive=patch_interactive, commit_each=patch_commit
)
- # Copy resources
+ if slack_notifications:
+ notify_build_step("Completed applying patches")
+
+ # Copy resources for each architecture (YAML filters by arch)
+ if apply_patches_flag:
copy_resources(ctx, commit_each=patch_commit)
if slack_notifications:
notify_build_step(
- "Completed applying patches and copying resources"
+ f"Completed copying resources for {arch_name}"
)
# Build for this architecture
diff --git a/packages/browseros/build/config/NXTSCAPE_VERSION b/packages/browseros/build/config/NXTSCAPE_VERSION
index b5489e5e..8e14edce 100644
--- a/packages/browseros/build/config/NXTSCAPE_VERSION
+++ b/packages/browseros/build/config/NXTSCAPE_VERSION
@@ -1 +1 @@
-69
+78
diff --git a/packages/browseros/build/config/package.linux.yaml b/packages/browseros/build/config/package.linux.yaml
new file mode 100644
index 00000000..5bb2e7c6
--- /dev/null
+++ b/packages/browseros/build/config/package.linux.yaml
@@ -0,0 +1,35 @@
+# BrowserOS Linux Release Build Configuration
+build:
+ type: release
+ architecture: x64 # Linux x64 only
+ # architectures: [x64] # Single architecture for Linux
+ universal: false # Linux doesn't support universal binaries
+
+gn_flags:
+ file: build/config/gn/flags.linux.release.gn
+
+steps:
+ clean: false
+ git_setup: false
+ apply_patches: false
+ build: false
+ sign: false # Linux doesn't require code signing
+ package: true
+
+paths:
+ root_dir: .
+ # chromium_src: ../chromium-src
+
+# Environment-specific settings
+env:
+ PYTHONPATH: scripts
+
+# Linux-specific settings
+linux:
+ appimage:
+ compression: gzip # Compression type for AppImage
+ architecture: x86_64 # AppImage architecture designation
+
+# Notification settings
+notifications:
+ slack: true # Enable Slack notifications for release builds
diff --git a/packages/browseros/build/modules/package_windows.py b/packages/browseros/build/modules/package_windows.py
index 2fab6383..86a7379e 100644
--- a/packages/browseros/build/modules/package_windows.py
+++ b/packages/browseros/build/modules/package_windows.py
@@ -31,7 +31,7 @@
def get_browseros_server_binary_paths(build_output_dir: Path) -> List[Path]:
"""Return absolute paths to BrowserOS Server binaries for signing."""
- server_dir = build_output_dir / "BrowserOSServer" / "default"
+ server_dir = build_output_dir / "BrowserOSServer" / "default" / "resources" / "bin"
return [server_dir / binary for binary in BROWSEROS_SERVER_BINARIES]
@@ -69,12 +69,18 @@ def build_mini_installer(ctx: BuildContext) -> bool:
# Get paths
build_output_dir = join_paths(ctx.chromium_src, ctx.out_dir)
mini_installer_path = build_output_dir / "mini_installer.exe"
+ setup_exe_path = build_output_dir / "setup.exe"
- if mini_installer_path.exists():
- log_info("mini_installer.exe already exists")
- return True
+ if mini_installer_path.exists() and setup_exe_path.exists():
+ log_info(
+ "mini_installer.exe and setup.exe already exist; rebuilding to ensure freshness"
+ )
+ elif setup_exe_path.exists() and not mini_installer_path.exists():
+ log_info("setup.exe exists but mini_installer.exe missing")
+ elif mini_installer_path.exists() and not setup_exe_path.exists():
+ log_info("mini_installer.exe exists but setup.exe missing")
- log_info("Building mini_installer target...")
+ log_info("Building setup and mini_installer targets...")
# Build mini_installer using autoninja
try:
@@ -86,6 +92,7 @@ def build_mini_installer(ctx: BuildContext) -> bool:
autoninja_cmd,
"-C",
ctx.out_dir, # Use relative path like in compile.py
+ "setup",
"mini_installer",
]
@@ -101,15 +108,24 @@ def build_mini_installer(ctx: BuildContext) -> bool:
os.chdir(old_cwd)
# Verify the file was created
- if mini_installer_path.exists():
- log_success("mini_installer built successfully")
+ missing_artifacts = []
+ if not setup_exe_path.exists():
+ missing_artifacts.append("setup.exe")
+ if not mini_installer_path.exists():
+ missing_artifacts.append("mini_installer.exe")
+
+ if not missing_artifacts:
+ log_success("mini_installer and setup built successfully")
return True
- else:
- log_error("mini_installer build completed but file not found")
- return False
+
+ log_error(
+ "Build completed but missing artifacts: "
+ + ", ".join(missing_artifacts)
+ )
+ return False
except Exception as e:
- log_error(f"Failed to build mini_installer: {e}")
+ log_error(f"Failed to build setup/mini_installer: {e}")
return False
diff --git a/packages/browseros/chromium_patches/base/threading/thread_restrictions.h b/packages/browseros/chromium_patches/base/threading/thread_restrictions.h
new file mode 100644
index 00000000..1d8683f1
--- /dev/null
+++ b/packages/browseros/chromium_patches/base/threading/thread_restrictions.h
@@ -0,0 +1,30 @@
+diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
+index 59d6e6e4d899f..9f2737ff17c6f 100644
+--- a/base/threading/thread_restrictions.h
++++ b/base/threading/thread_restrictions.h
+@@ -200,6 +200,9 @@ namespace scheduler {
+ class NonMainThreadImpl;
+ }
+ } // namespace blink
++namespace browseros {
++class BrowserOSServerManager;
++} // namespace browseros
+ namespace cc {
+ class CategorizedWorkerPoolJob;
+ class CategorizedWorkerPool;
+@@ -595,6 +598,7 @@ class BASE_EXPORT ScopedAllowBlocking {
+ friend class base::subtle::PlatformSharedMemoryRegion;
+ friend class base::win::ScopedAllowBlockingForUserAccountControl;
+ friend class blink::DiskDataAllocator;
++ friend class browseros::BrowserOSServerManager;
+ friend class chromecast::CrashUtil;
+ friend class content::BrowserProcessIOThread;
+ friend class content::DWriteFontProxyImpl;
+@@ -743,6 +747,7 @@ class BASE_EXPORT ScopedAllowBaseSyncPrimitives {
+ friend class base::SimpleThread;
+ friend class base::internal::GetAppOutputScopedAllowBaseSyncPrimitives;
+ friend class blink::SourceStream;
++ friend class browseros::BrowserOSServerManager;
+ friend class blink::VideoTrackRecorderImplContextProvider;
+ friend class blink::WorkerThread;
+ friend class blink::scheduler::NonMainThreadImpl;
diff --git a/packages/browseros/chromium_patches/chrome/BUILD.gn b/packages/browseros/chromium_patches/chrome/BUILD.gn
index 70aa642b..d462eacd 100644
--- a/packages/browseros/chromium_patches/chrome/BUILD.gn
+++ b/packages/browseros/chromium_patches/chrome/BUILD.gn
@@ -1,5 +1,5 @@
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
-index 97f843f8133c4..b7af2f2d94579 100644
+index 97f843f8133c4..0acbe29f11806 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -18,6 +18,7 @@ import("//build/config/win/manifest.gni")
@@ -37,11 +37,3 @@ index 97f843f8133c4..b7af2f2d94579 100644
configs += [ ":chrome_dll_symbol_order" ]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
-@@ -1493,6 +1500,7 @@ copy("visual_elements_resources") {
- "//chrome/app/theme/$branding_path_component/win/tiles/Logo.png",
- "//chrome/app/theme/$branding_path_component/win/tiles/SmallLogo.png",
- "app/visual_elements_resources/chrome.VisualElementsManifest.xml",
-+ "app/visual_elements_resources/browseros.VisualElementsManifest.xml",
- ]
-
- if (is_chrome_branded) {
diff --git a/packages/browseros/chromium_patches/chrome/app/visual_elements_resources/browseros.VisualElementsManifest.xml b/packages/browseros/chromium_patches/chrome/app/visual_elements_resources/browseros.VisualElementsManifest.xml
deleted file mode 100644
index ae89c096..00000000
--- a/packages/browseros/chromium_patches/chrome/app/visual_elements_resources/browseros.VisualElementsManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-diff --git a/chrome/app/visual_elements_resources/browseros.VisualElementsManifest.xml b/chrome/app/visual_elements_resources/browseros.VisualElementsManifest.xml
-new file mode 100644
-index 0000000000000..a7bb10b4056e2
---- /dev/null
-+++ b/chrome/app/visual_elements_resources/browseros.VisualElementsManifest.xml
-@@ -0,0 +1,10 @@
-+
-+
-+
-+
diff --git a/packages/browseros/chromium_patches/chrome/browser/browseros_server/.gitignore b/packages/browseros/chromium_patches/chrome/browser/browseros_server/.gitignore
new file mode 100644
index 00000000..0fa45fd4
--- /dev/null
+++ b/packages/browseros/chromium_patches/chrome/browser/browseros_server/.gitignore
@@ -0,0 +1,7 @@
+diff --git a/chrome/browser/browseros_server/.gitignore b/chrome/browser/browseros_server/.gitignore
+new file mode 100644
+index 0000000000000..cb76b31565a77
+--- /dev/null
++++ b/chrome/browser/browseros_server/.gitignore
+@@ -0,0 +1 @@
++resources/
diff --git a/packages/browseros/chromium_patches/chrome/browser/browseros_server/BUILD.gn b/packages/browseros/chromium_patches/chrome/browser/browseros_server/BUILD.gn
index 7efe2ae9..5fa302ae 100644
--- a/packages/browseros/chromium_patches/chrome/browser/browseros_server/BUILD.gn
+++ b/packages/browseros/chromium_patches/chrome/browser/browseros_server/BUILD.gn
@@ -1,15 +1,28 @@
diff --git a/chrome/browser/browseros_server/BUILD.gn b/chrome/browser/browseros_server/BUILD.gn
new file mode 100644
-index 0000000000000..7165c4459db08
+index 0000000000000..ddbdf0b78c0a3
--- /dev/null
+++ b/chrome/browser/browseros_server/BUILD.gn
-@@ -0,0 +1,74 @@
+@@ -0,0 +1,66 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/chrome_build.gni")
+
++# Validate that required resources exist at build time
++# GN will fail at generation time if resources/bin/browseros_server is missing
++_browseros_binary_name = "browseros_server"
++if (is_win) {
++ _browseros_binary_name += ".exe"
++}
++
++action("validate_browseros_resources") {
++ script = "validate_resources.py"
++ inputs = [ "resources/bin/${_browseros_binary_name}" ]
++ outputs = [ "$target_gen_dir/browseros_resources_validated" ]
++}
++
+source_set("browseros_server") {
+ sources = [
+ "browseros_server_manager.cc",
@@ -28,53 +41,32 @@ index 0000000000000..7165c4459db08
+ ]
+}
+
-+# Determine OS name for binary selection
-+if (is_mac) {
-+ _browseros_os = "darwin"
-+} else if (is_win) {
-+ _browseros_os = "windows"
-+} else if (is_linux) {
-+ _browseros_os = "linux"
-+}
-+
-+# Construct source binary filename based on target OS and architecture
-+_browseros_binary_name = "browseros-server-${_browseros_os}-${target_cpu}"
-+if (is_win) {
-+ _browseros_binary_name += ".exe"
-+}
-+
-+# Source binary path (architecture-specific)
-+_browseros_source_binary = "binaries/${_browseros_binary_name}"
-+
-+# Output filename (standardized)
-+_browseros_output_name = "browseros_server"
-+if (is_win) {
-+ _browseros_output_name += ".exe"
-+}
-+
+if (is_mac) {
+ import("//build/config/apple/symbols.gni")
+ import("//build/config/mac/mac_sdk.gni")
+
-+ # Bundle data for macOS - packages to Resources/BrowserOSServer/default/
-+ bundle_data("browseros_server_bundle_data") {
-+ sources = [ _browseros_source_binary ]
-+ outputs =
-+ [ "{{bundle_resources_dir}}/BrowserOSServer/default/${_browseros_output_name}" ]
++ # Bundle data for macOS - recursively packages resources/ to Resources/BrowserOSServer/default/
++ bundle_data("browseros_resources_bundle") {
++ sources = [ "resources" ]
++ outputs = [ "{{bundle_resources_dir}}/BrowserOSServer/default/{{source_file_part}}" ]
++ # TODO: Re-enable validation when resources/bin/browseros_server is available
++ # deps = [ ":validate_browseros_resources" ]
+ }
+} else {
-+ # Copy for Windows/Linux - packages to /BrowserOSServer/default/
-+ copy("browseros_server_binary") {
-+ sources = [ _browseros_source_binary ]
-+ outputs = [ "$root_out_dir/BrowserOSServer/default/${_browseros_output_name}" ]
++ # Copy for Windows/Linux - recursively packages resources/ to /BrowserOSServer/default/
++ copy("browseros_resources_copy") {
++ sources = [ "resources" ]
++ outputs = [ "$root_out_dir/BrowserOSServer/default/{{source_file_part}}" ]
++ # TODO: Re-enable validation when resources/bin/browseros_server is available
++ # deps = [ ":validate_browseros_resources" ]
+ }
+}
+
+# Group for all BrowserOS server resources
+group("browseros_server_resources") {
+ if (is_mac) {
-+ deps = [ ":browseros_server_bundle_data" ]
++ deps = [ ":browseros_resources_bundle" ]
+ } else {
-+ deps = [ ":browseros_server_binary" ]
++ deps = [ ":browseros_resources_copy" ]
+ }
+}
diff --git a/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_manager.cc b/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_manager.cc
index f80f07be..041c8fc0 100644
--- a/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_manager.cc
+++ b/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_manager.cc
@@ -1,9 +1,9 @@
diff --git a/chrome/browser/browseros_server/browseros_server_manager.cc b/chrome/browser/browseros_server/browseros_server_manager.cc
new file mode 100644
-index 0000000000000..e6bd851f42bfa
+index 0000000000000..4d34f3efb2645
--- /dev/null
+++ b/chrome/browser/browseros_server/browseros_server_manager.cc
-@@ -0,0 +1,863 @@
+@@ -0,0 +1,1070 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
@@ -25,6 +25,11 @@ index 0000000000000..e6bd851f42bfa
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
++
++#if BUILDFLAG(IS_POSIX)
++#include
++#endif
++
+#include "chrome/browser/browseros_server/browseros_server_prefs.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/profiles/profile.h"
@@ -93,6 +98,8 @@ index 0000000000000..e6bd851f42bfa
+// This function performs blocking I/O operations (PathExists, LaunchProcess).
+base::Process LaunchProcessOnBackgroundThread(
+ const base::FilePath& exe_path,
++ const base::FilePath& resources_dir,
++ const base::FilePath& execution_dir,
+ uint16_t cdp_port,
+ uint16_t mcp_port,
+ uint16_t agent_port,
@@ -104,12 +111,26 @@ index 0000000000000..e6bd851f42bfa
+ return base::Process();
+ }
+
++ if (execution_dir.empty()) {
++ LOG(ERROR) << "browseros: Execution directory path is empty";
++ return base::Process();
++ }
++
++ // Ensure execution directory exists (blocking I/O)
++ if (!base::CreateDirectory(execution_dir)) {
++ LOG(ERROR) << "browseros: Failed to create execution directory at: "
++ << execution_dir;
++ return base::Process();
++ }
++
+ // Build command line
+ base::CommandLine cmd(exe_path);
+ cmd.AppendSwitchASCII("cdp-port", base::NumberToString(cdp_port));
+ cmd.AppendSwitchASCII("http-mcp-port", base::NumberToString(mcp_port));
+ cmd.AppendSwitchASCII("agent-port", base::NumberToString(agent_port));
+ cmd.AppendSwitchASCII("extension-port", base::NumberToString(extension_port));
++ cmd.AppendSwitchPath("resources-dir", resources_dir);
++ cmd.AppendSwitchPath("execution-dir", execution_dir);
+
+ // Set up launch options
+ base::LaunchOptions options;
@@ -158,6 +179,8 @@ index 0000000000000..e6bd851f42bfa
+
+} // namespace
+
++namespace browseros {
++
+// static
+BrowserOSServerManager* BrowserOSServerManager::GetInstance() {
+ static base::NoDestructor instance;
@@ -170,88 +193,76 @@ index 0000000000000..e6bd851f42bfa
+ Shutdown();
+}
+
-+void BrowserOSServerManager::Start() {
-+ if (is_running_) {
-+ LOG(INFO) << "browseros: BrowserOS server already running";
-+ return;
-+ }
++bool BrowserOSServerManager::AcquireLock() {
++ // Allow blocking for lock file operations (short-duration I/O)
++ base::ScopedAllowBlocking allow_blocking;
+
-+ // Check if server is disabled via command line
-+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-+ if (command_line->HasSwitch("disable-browseros-server")) {
-+ LOG(INFO) << "browseros: BrowserOS server disabled via command line";
-+ return;
++ base::FilePath exec_dir = GetBrowserOSExecutionDir();
++ if (exec_dir.empty()) {
++ LOG(ERROR) << "browseros: Failed to resolve execution directory for lock";
++ return false;
+ }
+
-+ LOG(INFO) << "browseros: Starting BrowserOS server";
-+
-+ // Step 1: Start CDP server
-+ StartCDPServer();
-+
-+ // Step 2: Launch BrowserOS process
-+ LaunchBrowserOSProcess();
++ base::FilePath lock_path = exec_dir.Append(FILE_PATH_LITERAL("server.lock"));
+
-+ // Step 3: Start health checking every 60 seconds
-+ health_check_timer_.Start(FROM_HERE, base::Seconds(60), this,
-+ &BrowserOSServerManager::CheckServerHealth);
-+}
++ lock_file_ = base::File(lock_path,
++ base::File::FLAG_OPEN_ALWAYS |
++ base::File::FLAG_READ |
++ base::File::FLAG_WRITE);
+
-+void BrowserOSServerManager::Stop() {
-+ if (!is_running_) {
-+ return;
++ if (!lock_file_.IsValid()) {
++ LOG(ERROR) << "browseros: Failed to open lock file: " << lock_path;
++ return false;
+ }
+
-+ LOG(INFO) << "browseros: Stopping BrowserOS server";
-+ health_check_timer_.Stop();
-+ process_check_timer_.Stop();
-+
-+ TerminateBrowserOSProcess();
-+ StopCDPServer();
-+}
-+
-+bool BrowserOSServerManager::IsRunning() const {
-+ return is_running_ && process_.IsValid();
-+}
++ base::File::Error lock_error =
++ lock_file_.Lock(base::File::LockMode::kExclusive);
++ if (lock_error != base::File::FILE_OK) {
++ LOG(INFO) << "browseros: Server already running in another Chrome process "
++ << "(lock file: " << lock_path << ")";
++ lock_file_.Close();
++ return false;
++ }
+
-+void BrowserOSServerManager::Shutdown() {
-+ Stop();
++ LOG(INFO) << "browseros: Acquired exclusive lock on " << lock_path;
++ return true;
+}
+
-+void BrowserOSServerManager::StartCDPServer() {
++void BrowserOSServerManager::InitializePortsAndPrefs() {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-+
+ PrefService* prefs = g_browser_process->local_state();
++
++ // STEP 1: Read from prefs or use defaults
+ if (!prefs) {
-+ LOG(ERROR) << "browseros: Failed to get local state prefs";
-+ // Find available ports from standard defaults
-+ cdp_port_ = FindAvailablePort(browseros_server::kDefaultCDPPort);
-+ mcp_port_ = FindAvailablePort(browseros_server::kDefaultMCPPort);
-+ agent_port_ = FindAvailablePort(browseros_server::kDefaultAgentPort);
-+ extension_port_ = FindAvailablePort(browseros_server::kDefaultExtensionPort);
++ cdp_port_ = browseros_server::kDefaultCDPPort;
++ mcp_port_ = browseros_server::kDefaultMCPPort;
++ agent_port_ = browseros_server::kDefaultAgentPort;
++ extension_port_ = browseros_server::kDefaultExtensionPort;
++ mcp_enabled_ = true;
+ } else {
-+ // Read CDP port from prefs and find available port
-+ int saved_cdp_port = prefs->GetInteger(browseros_server::kCDPServerPort);
-+ int cdp_starting_port = (saved_cdp_port > 0) ? saved_cdp_port : browseros_server::kDefaultCDPPort;
-+ cdp_port_ = FindAvailablePort(cdp_starting_port);
-+
-+ // Read MCP settings
-+ int saved_mcp_port = prefs->GetInteger(browseros_server::kMCPServerPort);
-+ int mcp_starting_port = (saved_mcp_port > 0) ? saved_mcp_port : browseros_server::kDefaultMCPPort;
-+ mcp_port_ = FindAvailablePort(mcp_starting_port);
-+
-+ // Read Agent port from prefs and find available port
-+ int saved_agent_port = prefs->GetInteger(browseros_server::kAgentServerPort);
-+ int agent_starting_port = (saved_agent_port > 0) ? saved_agent_port : browseros_server::kDefaultAgentPort;
-+ agent_port_ = FindAvailablePort(agent_starting_port);
-+
-+ // Read Extension port from prefs and find available port
-+ int saved_extension_port = prefs->GetInteger(browseros_server::kExtensionServerPort);
-+ int extension_starting_port = (saved_extension_port > 0) ? saved_extension_port : browseros_server::kDefaultExtensionPort;
-+ extension_port_ = FindAvailablePort(extension_starting_port);
++ cdp_port_ = prefs->GetInteger(browseros_server::kCDPServerPort);
++ if (cdp_port_ <= 0) {
++ cdp_port_ = browseros_server::kDefaultCDPPort;
++ }
++
++ mcp_port_ = prefs->GetInteger(browseros_server::kMCPServerPort);
++ if (mcp_port_ <= 0) {
++ mcp_port_ = browseros_server::kDefaultMCPPort;
++ }
++
++ agent_port_ = prefs->GetInteger(browseros_server::kAgentServerPort);
++ if (agent_port_ <= 0) {
++ agent_port_ = browseros_server::kDefaultAgentPort;
++ }
++
++ extension_port_ = prefs->GetInteger(browseros_server::kExtensionServerPort);
++ if (extension_port_ <= 0) {
++ extension_port_ = browseros_server::kDefaultExtensionPort;
++ }
+
+ mcp_enabled_ = prefs->GetBoolean(browseros_server::kMCPServerEnabled);
+
-+ // Set up preference change observer for MCP enabled flag
++ // Set up pref change observers
+ if (!pref_change_registrar_) {
+ pref_change_registrar_ = std::make_unique();
+ pref_change_registrar_->Init(prefs);
@@ -259,21 +270,29 @@ index 0000000000000..e6bd851f42bfa
+ browseros_server::kMCPServerEnabled,
+ base::BindRepeating(&BrowserOSServerManager::OnMCPEnabledChanged,
+ base::Unretained(this)));
++ pref_change_registrar_->Add(
++ browseros_server::kRestartServerRequested,
++ base::BindRepeating(&BrowserOSServerManager::OnRestartServerRequestedChanged,
++ base::Unretained(this)));
+ }
+ }
++ cdp_port_ = FindAvailablePort(cdp_port_);
++ mcp_port_ = FindAvailablePort(mcp_port_);
++ agent_port_ = FindAvailablePort(agent_port_);
++ extension_port_ = FindAvailablePort(extension_port_);
++
++ // STEP 3: Apply command-line overrides (these take highest priority)
++ int cdp_override = GetPortOverrideFromCommandLine(
++ command_line, "browseros-cdp-port", "CDP port");
++ if (cdp_override > 0) {
++ cdp_port_ = cdp_override;
++ }
+
-+ LOG(INFO) << "browseros: Ports allocated - CDP: " << cdp_port_
-+ << ", MCP: " << mcp_port_ << ", Agent: " << agent_port_
-+ << ", Extension: " << extension_port_;
-+
-+ // Check for command-line port overrides
+ int mcp_override = GetPortOverrideFromCommandLine(
+ command_line, "browseros-mcp-port", "MCP port");
+ if (mcp_override > 0) {
+ mcp_port_ = mcp_override;
-+ // Implicitly enable MCP when port is specified
-+ mcp_enabled_ = true;
-+ LOG(INFO) << "browseros: MCP server implicitly enabled via command line";
++ mcp_enabled_ = true; // Implicit enable when port specified
+ }
+
+ int agent_override = GetPortOverrideFromCommandLine(
@@ -288,18 +307,99 @@ index 0000000000000..e6bd851f42bfa
+ extension_port_ = extension_override;
+ }
+
++ LOG(INFO) << "browseros: Final ports - CDP: " << cdp_port_
++ << ", MCP: " << mcp_port_ << ", Agent: " << agent_port_
++ << ", Extension: " << extension_port_;
++}
++
++void BrowserOSServerManager::SavePortsToPrefs() {
++ PrefService* prefs = g_browser_process->local_state();
++ if (!prefs) {
++ return;
++ }
++
++ prefs->SetInteger(browseros_server::kCDPServerPort, cdp_port_);
++ prefs->SetInteger(browseros_server::kMCPServerPort, mcp_port_);
++ prefs->SetInteger(browseros_server::kAgentServerPort, agent_port_);
++ prefs->SetInteger(browseros_server::kExtensionServerPort, extension_port_);
++ prefs->SetBoolean(browseros_server::kMCPServerEnabled, mcp_enabled_);
++
++ LOG(INFO) << "browseros: Saved finalized ports to prefs";
++}
++
++void BrowserOSServerManager::Start() {
++ if (is_running_) {
++ LOG(INFO) << "browseros: BrowserOS server already running";
++ return;
++ }
++
++ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
++ if (command_line->HasSwitch("disable-browseros-server")) {
++ LOG(INFO) << "browseros: BrowserOS server disabled via command line";
++ return;
++ }
++
++ // Try to acquire system-wide lock
++ if (!AcquireLock()) {
++ return; // Another Chrome process already owns the server
++ }
++
++ LOG(INFO) << "browseros: Starting BrowserOS server";
++
++ // Initialize and finalize ports
++ InitializePortsAndPrefs();
++ SavePortsToPrefs();
++
++ // Start servers and process
++ StartCDPServer();
++ LaunchBrowserOSProcess();
++
++ health_check_timer_.Start(FROM_HERE, base::Seconds(60), this,
++ &BrowserOSServerManager::CheckServerHealth);
++}
++
++void BrowserOSServerManager::Stop() {
++ if (!is_running_) {
++ return;
++ }
++
++ LOG(INFO) << "browseros: Stopping BrowserOS server";
++ health_check_timer_.Stop();
++ process_check_timer_.Stop();
++
++ TerminateBrowserOSProcess();
++ StopCDPServer();
++
++ // Release lock
++ if (lock_file_.IsValid()) {
++ lock_file_.Unlock();
++ lock_file_.Close();
++ LOG(INFO) << "browseros: Released lock file";
++ }
++}
++
++bool BrowserOSServerManager::IsRunning() const {
++ return is_running_ && process_.IsValid();
++}
++
++void BrowserOSServerManager::Shutdown() {
++ Stop();
++}
++
++void BrowserOSServerManager::StartCDPServer() {
+ LOG(INFO) << "browseros: Starting CDP server on port " << cdp_port_;
+
-+ // Start Chromium's built-in DevTools remote debugging server
+ content::DevToolsAgentHost::StartRemoteDebuggingServer(
+ std::make_unique(cdp_port_),
-+ base::FilePath(), // No output dir needed
-+ base::FilePath()); // No debug frontend dir
++ base::FilePath(),
++ base::FilePath());
+
+ LOG(INFO) << "browseros: CDP WebSocket server started at ws://127.0.0.1:"
+ << cdp_port_;
+ LOG(INFO) << "browseros: MCP server port: " << mcp_port_
+ << " (enabled: " << (mcp_enabled_ ? "true" : "false") << ")";
++ LOG(INFO) << "browseros: Agent server port: " << agent_port_;
++ LOG(INFO) << "browseros: Extension server port: " << extension_port_;
+}
+
+void BrowserOSServerManager::StopCDPServer() {
@@ -313,8 +413,18 @@ index 0000000000000..e6bd851f42bfa
+}
+
+void BrowserOSServerManager::LaunchBrowserOSProcess() {
-+ // Get executable path on UI thread (PathService::Get is thread-safe)
+ base::FilePath exe_path = GetBrowserOSServerExecutablePath();
++ base::FilePath resources_dir = GetBrowserOSServerResourcesPath();
++ base::FilePath execution_dir = GetBrowserOSExecutionDir();
++ if (execution_dir.empty()) {
++ LOG(ERROR) << "browseros: Failed to resolve execution directory";
++ StopCDPServer();
++ return;
++ }
++
++ LOG(INFO) << "browseros: Launching server - binary: " << exe_path;
++ LOG(INFO) << "browseros: Launching server - resources: " << resources_dir;
++ LOG(INFO) << "browseros: Launching server - execution dir: " << execution_dir;
+
+ // Capture values to pass to background thread
+ uint16_t cdp_port = cdp_port_;
@@ -325,8 +435,9 @@ index 0000000000000..e6bd851f42bfa
+ // Post blocking work to background thread, get result back on UI thread
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
-+ base::BindOnce(&LaunchProcessOnBackgroundThread, exe_path, cdp_port,
-+ mcp_port, agent_port, extension_port),
++ base::BindOnce(&LaunchProcessOnBackgroundThread, exe_path, resources_dir,
++ execution_dir, cdp_port, mcp_port, agent_port,
++ extension_port),
+ base::BindOnce(&BrowserOSServerManager::OnProcessLaunched,
+ weak_factory_.GetWeakPtr()));
+}
@@ -335,6 +446,7 @@ index 0000000000000..e6bd851f42bfa
+ if (!process.IsValid()) {
+ LOG(ERROR) << "browseros: Failed to launch BrowserOS server";
+ StopCDPServer();
++ is_restarting_ = false;
+ return;
+ }
+
@@ -347,21 +459,19 @@ index 0000000000000..e6bd851f42bfa
+ LOG(INFO) << "browseros: Agent port: " << agent_port_;
+ LOG(INFO) << "browseros: Extension port: " << extension_port_;
+
-+ // Save prefs to Local State now that we know the server launched
-+ PrefService* prefs = g_browser_process->local_state();
-+ if (prefs) {
-+ prefs->SetInteger(browseros_server::kCDPServerPort, cdp_port_);
-+ prefs->SetInteger(browseros_server::kMCPServerPort, mcp_port_);
-+ prefs->SetInteger(browseros_server::kAgentServerPort, agent_port_);
-+ prefs->SetInteger(browseros_server::kExtensionServerPort, extension_port_);
-+ prefs->SetBoolean(browseros_server::kMCPServerEnabled, mcp_enabled_);
-+ LOG(INFO) << "browseros: Saved prefs to Local State";
-+ }
-+
-+ // Start monitoring the process
+ process_check_timer_.Start(FROM_HERE, base::Seconds(5), this,
+ &BrowserOSServerManager::CheckProcessStatus);
+
++ // Reset restart flag and pref after successful launch
++ if (is_restarting_) {
++ is_restarting_ = false;
++ PrefService* prefs = g_browser_process->local_state();
++ if (prefs && prefs->GetBoolean(browseros_server::kRestartServerRequested)) {
++ prefs->SetBoolean(browseros_server::kRestartServerRequested, false);
++ LOG(INFO) << "browseros: Restart completed, reset restart_requested pref";
++ }
++ }
++
+ // /init will be sent after first successful periodic health check
+
+ // If MCP is disabled, send control request to disable it
@@ -375,26 +485,40 @@ index 0000000000000..e6bd851f42bfa
+ return;
+ }
+
-+ LOG(INFO) << "browseros: Terminating BrowserOS server process";
++ LOG(INFO) << "browseros: Force killing BrowserOS server process (PID: "
++ << process_.Pid() << ")";
+
+ // Reset init flag so it gets sent again after restart
+ init_request_sent_ = false;
+
-+ // Try graceful shutdown first
-+ process_.Terminate(0, false);
-+
-+ // Give it some time to shut down, then force kill if still running
-+ base::ThreadPool::PostDelayedTask(
-+ FROM_HERE, {base::MayBlock()},
-+ base::BindOnce(
-+ [](base::Process process) {
-+ if (process.IsValid()) {
-+ // Force kill if still running
-+ process.Terminate(0, false);
-+ }
-+ },
-+ process_.Duplicate()),
-+ base::Seconds(2));
++ // sync primitives is needed for process termination.
++ // NOTE: only run on background threads
++ base::ScopedAllowBaseSyncPrimitives allow_sync;
++ base::ScopedAllowBlocking allow_blocking;
++
++#if BUILDFLAG(IS_POSIX)
++ // POSIX: Send SIGKILL for immediate termination (no graceful shutdown)
++ // This matches Windows TerminateProcess behavior
++ base::ProcessId pid = process_.Pid();
++ if (kill(pid, SIGKILL) == 0) {
++ int exit_code = 0;
++ if (process_.WaitForExit(&exit_code)) {
++ LOG(INFO) << "browseros: Process killed successfully with SIGKILL";
++ } else {
++ LOG(WARNING) << "browseros: SIGKILL sent but WaitForExit failed";
++ }
++ } else {
++ PLOG(ERROR) << "browseros: Failed to send SIGKILL to PID " << pid;
++ }
++#else
++ // Windows: TerminateProcess is already immediate force kill
++ bool terminated = process_.Terminate(0, true);
++ if (terminated) {
++ LOG(INFO) << "browseros: Process terminated successfully";
++ } else {
++ LOG(ERROR) << "browseros: Failed to terminate process";
++ }
++#endif
+
+ is_running_ = false;
+}
@@ -553,6 +677,60 @@ index 0000000000000..e6bd851f42bfa
+ }
+}
+
++void BrowserOSServerManager::OnRestartServerRequestedChanged() {
++ PrefService* prefs = g_browser_process->local_state();
++ if (!prefs) {
++ return;
++ }
++
++ bool restart_requested = prefs->GetBoolean(browseros_server::kRestartServerRequested);
++
++ // Only process if pref is set to true
++ if (!restart_requested) {
++ return;
++ }
++
++ // Ignore if already restarting (prevents thrashing from UI spam)
++ if (is_restarting_) {
++ LOG(INFO) << "browseros: Restart already in progress, ignoring duplicate request";
++ return;
++ }
++
++ // Ignore if not running
++ if (!is_running_) {
++ LOG(WARNING) << "browseros: Cannot restart - server is not running";
++ // Reset pref anyway
++ prefs->SetBoolean(browseros_server::kRestartServerRequested, false);
++ return;
++ }
++
++ LOG(INFO) << "browseros: Server restart requested via preference";
++ is_restarting_ = true;
++
++ // Stop timer now (must be on UI thread)
++ process_check_timer_.Stop();
++
++ // Capture UI task runner to post back after background work
++ auto ui_task_runner = base::SequencedTaskRunner::GetCurrentDefault();
++
++ // Kill process on background thread, then relaunch on UI thread
++ base::ThreadPool::PostTask(
++ FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
++ base::BindOnce(
++ [](BrowserOSServerManager* manager,
++ scoped_refptr ui_runner) {
++ // Kill old process and wait for exit (blocking, safe on background)
++ manager->TerminateBrowserOSProcess();
++
++ // Post back to UI thread to launch new process
++ ui_runner->PostTask(
++ FROM_HERE,
++ base::BindOnce(&BrowserOSServerManager::LaunchBrowserOSProcess,
++ base::Unretained(manager)));
++ },
++ base::Unretained(this), ui_task_runner));
++}
++
+void BrowserOSServerManager::SendMCPControlRequest(bool enabled) {
+ if (!is_running_) {
+ return;
@@ -813,13 +991,13 @@ index 0000000000000..e6bd851f42bfa
+ return true;
+}
+
-+base::FilePath BrowserOSServerManager::GetBrowserOSServerExecutablePath() const {
++base::FilePath BrowserOSServerManager::GetBrowserOSServerResourcesPath() const {
+ // Check for command-line override first
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-+ if (command_line->HasSwitch("browseros-server-binary")) {
++ if (command_line->HasSwitch("browseros-server-resources-dir")) {
+ base::FilePath custom_path =
-+ command_line->GetSwitchValuePath("browseros-server-binary");
-+ LOG(INFO) << "browseros: Using custom server binary from command line: "
++ command_line->GetSwitchValuePath("browseros-server-resources-dir");
++ LOG(INFO) << "browseros: Using custom resources dir from command line: "
+ << custom_path;
+ return custom_path;
+ }
@@ -844,7 +1022,6 @@ index 0000000000000..e6bd851f42bfa
+ return base::FilePath();
+ }
+ // Append version directory (chrome.release places BrowserOSServer under versioned dir)
-+ // chrome/installer/mini_installer/chrome.release
+ exe_dir = exe_dir.AppendASCII(version_info::GetVersionNumber());
+
+#elif BUILDFLAG(IS_LINUX)
@@ -855,11 +1032,39 @@ index 0000000000000..e6bd851f42bfa
+ }
+#endif
+
-+ // Navigate to BrowserOSServer/default/ subdirectory
-+ // This structure allows future updates to install to versioned directories
-+ base::FilePath browseros_exe = exe_dir.Append(FILE_PATH_LITERAL("BrowserOSServer"))
-+ .Append(FILE_PATH_LITERAL("default"))
-+ .Append(FILE_PATH_LITERAL("browseros_server"));
++ // Return path to resources directory
++ return exe_dir.Append(FILE_PATH_LITERAL("BrowserOSServer"))
++ .Append(FILE_PATH_LITERAL("default"))
++ .Append(FILE_PATH_LITERAL("resources"));
++}
++
++base::FilePath BrowserOSServerManager::GetBrowserOSExecutionDir() const {
++ base::FilePath user_data_dir;
++ if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
++ LOG(ERROR) << "browseros: Failed to resolve DIR_USER_DATA path";
++ return base::FilePath();
++ }
++
++ base::FilePath exec_dir = user_data_dir.Append(FILE_PATH_LITERAL(".browseros"));
++
++ // Ensure directory exists before returning
++ base::ScopedAllowBlocking allow_blocking;
++ if (!base::PathExists(exec_dir)) {
++ if (!base::CreateDirectory(exec_dir)) {
++ LOG(ERROR) << "browseros: Failed to create execution directory: " << exec_dir;
++ return base::FilePath();
++ }
++ }
++
++ LOG(INFO) << "browseros: Using execution directory: " << exec_dir;
++ return exec_dir;
++}
++
++base::FilePath BrowserOSServerManager::GetBrowserOSServerExecutablePath() const {
++ base::FilePath browseros_exe =
++ GetBrowserOSServerResourcesPath()
++ .Append(FILE_PATH_LITERAL("bin"))
++ .Append(FILE_PATH_LITERAL("browseros_server"));
+
+#if BUILDFLAG(IS_WIN)
+ browseros_exe = browseros_exe.AddExtension(FILE_PATH_LITERAL(".exe"));
@@ -867,3 +1072,5 @@ index 0000000000000..e6bd851f42bfa
+
+ return browseros_exe;
+}
++
++} // namespace browseros
diff --git a/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_manager.h b/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_manager.h
index 687eb850..a6690cd7 100644
--- a/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_manager.h
+++ b/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_manager.h
@@ -1,9 +1,9 @@
diff --git a/chrome/browser/browseros_server/browseros_server_manager.h b/chrome/browser/browseros_server/browseros_server_manager.h
new file mode 100644
-index 0000000000000..edddc9a8a428c
+index 0000000000000..d991d965c4913
--- /dev/null
+++ b/chrome/browser/browseros_server/browseros_server_manager.h
-@@ -0,0 +1,125 @@
+@@ -0,0 +1,138 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
@@ -13,6 +13,7 @@ index 0000000000000..edddc9a8a428c
+
+#include
+
++#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
@@ -30,6 +31,8 @@ index 0000000000000..edddc9a8a428c
+class SimpleURLLoader;
+}
+
++namespace browseros {
++
+// BrowserOS: Manages the lifecycle of the BrowserOS server process (singleton)
+// This manager:
+// 1. Starts Chromium's CDP WebSocket server (port 9222+, auto-discovered)
@@ -80,6 +83,9 @@ index 0000000000000..edddc9a8a428c
+ BrowserOSServerManager();
+ ~BrowserOSServerManager();
+
++ bool AcquireLock();
++ void InitializePortsAndPrefs();
++ void SavePortsToPrefs();
+ void StartCDPServer();
+ void StopCDPServer();
+ void LaunchBrowserOSProcess();
@@ -92,6 +98,7 @@ index 0000000000000..edddc9a8a428c
+ std::unique_ptr url_loader,
+ scoped_refptr headers);
+ void OnMCPEnabledChanged();
++ void OnRestartServerRequestedChanged();
+ void SendMCPControlRequest(bool enabled);
+ void OnMCPControlRequestComplete(
+ bool requested_state,
@@ -103,10 +110,13 @@ index 0000000000000..edddc9a8a428c
+ scoped_refptr headers);
+ void CheckProcessStatus();
+
++ base::FilePath GetBrowserOSServerResourcesPath() const;
++ base::FilePath GetBrowserOSExecutionDir() const;
+ base::FilePath GetBrowserOSServerExecutablePath() const;
+ int FindAvailablePort(int starting_port);
+ bool IsPortAvailable(int port);
+
++ base::File lock_file_; // System-wide lock to ensure single instance
+ base::Process process_;
+ int cdp_port_ = 0; // CDP port (auto-discovered)
+ int mcp_port_ = 0; // MCP port (auto-discovered)
@@ -114,6 +124,7 @@ index 0000000000000..edddc9a8a428c
+ int extension_port_ = 0; // Extension port (auto-discovered)
+ bool mcp_enabled_ = true; // Whether MCP server is enabled
+ bool is_running_ = false;
++ bool is_restarting_ = false; // Whether server is currently restarting
+ bool init_request_sent_ = false; // Whether /init request has been sent
+
+ // Timer for health checks
@@ -128,4 +139,6 @@ index 0000000000000..edddc9a8a428c
+ base::WeakPtrFactory weak_factory_{this};
+};
+
++} // namespace browseros
++
+#endif // CHROME_BROWSER_BROWSEROS_SERVER_BROWSEROS_SERVER_MANAGER_H_
diff --git a/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_prefs.cc b/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_prefs.cc
index e3c9398a..ff4547be 100644
--- a/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_prefs.cc
+++ b/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_prefs.cc
@@ -1,9 +1,9 @@
diff --git a/chrome/browser/browseros_server/browseros_server_prefs.cc b/chrome/browser/browseros_server/browseros_server_prefs.cc
new file mode 100644
-index 0000000000000..fdd265d2f134c
+index 0000000000000..f9c7a9990cb01
--- /dev/null
+++ b/chrome/browser/browseros_server/browseros_server_prefs.cc
-@@ -0,0 +1,43 @@
+@@ -0,0 +1,49 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
@@ -29,6 +29,9 @@ index 0000000000000..fdd265d2f134c
+// Whether MCP server is enabled
+const char kMCPServerEnabled[] = "browseros.server.mcp_enabled";
+
++// Whether server restart has been requested (auto-reset after restart)
++const char kRestartServerRequested[] = "browseros.server.restart_requested";
++
+void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
+ // CDP port
+ registry->RegisterIntegerPref(kCDPServerPort, kDefaultCDPPort);
@@ -44,6 +47,9 @@ index 0000000000000..fdd265d2f134c
+
+ // MCP enabled
+ registry->RegisterBooleanPref(kMCPServerEnabled, true);
++
++ // Restart requested (default false, auto-reset after restart)
++ registry->RegisterBooleanPref(kRestartServerRequested, false);
+}
+
+} // namespace browseros_server
diff --git a/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_prefs.h b/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_prefs.h
index a38fb585..acc9d032 100644
--- a/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_prefs.h
+++ b/packages/browseros/chromium_patches/chrome/browser/browseros_server/browseros_server_prefs.h
@@ -1,9 +1,9 @@
diff --git a/chrome/browser/browseros_server/browseros_server_prefs.h b/chrome/browser/browseros_server/browseros_server_prefs.h
new file mode 100644
-index 0000000000000..e86296bdc15c7
+index 0000000000000..03719e252a15a
--- /dev/null
+++ b/chrome/browser/browseros_server/browseros_server_prefs.h
-@@ -0,0 +1,30 @@
+@@ -0,0 +1,31 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
@@ -27,6 +27,7 @@ index 0000000000000..e86296bdc15c7
+extern const char kAgentServerPort[];
+extern const char kExtensionServerPort[];
+extern const char kMCPServerEnabled[];
++extern const char kRestartServerRequested[];
+
+// Registers BrowserOS server preferences in Local State (browser-wide prefs)
+void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
diff --git a/packages/browseros/chromium_patches/chrome/browser/browseros_server/validate_resources.py b/packages/browseros/chromium_patches/chrome/browser/browseros_server/validate_resources.py
new file mode 100644
index 00000000..2c892019
--- /dev/null
+++ b/packages/browseros/chromium_patches/chrome/browser/browseros_server/validate_resources.py
@@ -0,0 +1,49 @@
+diff --git a/chrome/browser/browseros_server/validate_resources.py b/chrome/browser/browseros_server/validate_resources.py
+new file mode 100644
+index 0000000000000..d7dc82b132dad
+--- /dev/null
++++ b/chrome/browser/browseros_server/validate_resources.py
+@@ -0,0 +1,43 @@
++#!/usr/bin/env python3
++# Copyright 2024 The Chromium Authors
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Validates that required BrowserOS resources exist.
++
++Required resources must be listed in REQUIRED_RESOURCES below.
++"""
++
++import os
++import sys
++
++# Required resources that must exist in the resources/ directory
++# Add more resources as needed - paths are relative to resources/
++REQUIRED_RESOURCES = [
++ "bin/browseros_server",
++]
++
++script_dir = os.path.dirname(os.path.abspath(__file__))
++resources_dir = os.path.join(script_dir, "resources")
++
++all_valid = True
++for resource in REQUIRED_RESOURCES:
++ resource_path = os.path.join(resources_dir, resource)
++
++ if not os.path.exists(resource_path):
++ print(f"ERROR: Required BrowserOS resource not found: {resource_path}")
++ all_valid = False
++ continue
++
++ if not os.path.isfile(resource_path):
++ print(f"ERROR: Resource exists but is not a file: {resource_path}")
++ all_valid = False
++
++if not all_valid:
++ print(f"\nEnsure all required resources exist in resources/ directory:")
++ for resource in REQUIRED_RESOURCES:
++ print(f" - resources/{resource}")
++ sys.exit(1)
++
++print(f"✓ BrowserOS resources validated ({len(REQUIRED_RESOURCES)} resources)")
++sys.exit(0)
diff --git a/packages/browseros/chromium_patches/chrome/browser/chrome_browser_main.cc b/packages/browseros/chromium_patches/chrome/browser/chrome_browser_main.cc
index 64f1b8e9..395bd901 100644
--- a/packages/browseros/chromium_patches/chrome/browser/chrome_browser_main.cc
+++ b/packages/browseros/chromium_patches/chrome/browser/chrome_browser_main.cc
@@ -1,5 +1,5 @@
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
-index 681fd3282078c..8b1d525c80bb9 100644
+index 681fd3282078c..df6e7d2cbb9e4 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -10,6 +10,7 @@
@@ -25,7 +25,7 @@ index 681fd3282078c..8b1d525c80bb9 100644
+ // BrowserOS: Start the BrowserOS server after browser initialization
+ LOG(INFO) << "browseros: Starting BrowserOS server process";
-+ BrowserOSServerManager::GetInstance()->Start();
++ browseros::BrowserOSServerManager::GetInstance()->Start();
+
#if BUILDFLAG(IS_WIN)
// If the command line specifies 'uninstall' then we need to work here
@@ -37,7 +37,7 @@ index 681fd3282078c..8b1d525c80bb9 100644
+
+ // BrowserOS: Stop the BrowserOS server during shutdown
+ LOG(INFO) << "browseros: Stopping BrowserOS server process";
-+ BrowserOSServerManager::GetInstance()->Shutdown();
++ browseros::BrowserOSServerManager::GetInstance()->Shutdown();
+
TranslateService::Shutdown();
diff --git a/packages/browseros/chromium_patches/chrome/browser/extensions/browseros_extension_constants.h b/packages/browseros/chromium_patches/chrome/browser/extensions/browseros_extension_constants.h
index f2be3d1a..d9118e79 100644
--- a/packages/browseros/chromium_patches/chrome/browser/extensions/browseros_extension_constants.h
+++ b/packages/browseros/chromium_patches/chrome/browser/extensions/browseros_extension_constants.h
@@ -1,9 +1,9 @@
diff --git a/chrome/browser/extensions/browseros_extension_constants.h b/chrome/browser/extensions/browseros_extension_constants.h
new file mode 100644
-index 0000000000000..6bb906bc7068f
+index 0000000000000..17b78fbb99a9f
--- /dev/null
+++ b/chrome/browser/extensions/browseros_extension_constants.h
-@@ -0,0 +1,74 @@
+@@ -0,0 +1,80 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
@@ -30,6 +30,12 @@ index 0000000000000..6bb906bc7068f
+inline constexpr char kControllerExtensionId[] =
+ "nlnihljpboknmfagkikhkdblbedophja";
+
++// BrowserOS CDN update manifest URL
++// Used for extensions installed from local .crx files that don't have
++// an update_url in their manifest
++inline constexpr char kBrowserOSUpdateUrl[] =
++ "https://cdn.browseros.com/extensions/update-manifest.xml";
++
+// Allowlist of BrowserOS extension IDs that are permitted to be installed
+// Only extensions with these IDs will be loaded from the config
+constexpr const char* kAllowedExtensions[] = {
diff --git a/packages/browseros/chromium_patches/chrome/browser/extensions/extension_management.cc b/packages/browseros/chromium_patches/chrome/browser/extensions/extension_management.cc
index 5eecf0db..490fdf12 100644
--- a/packages/browseros/chromium_patches/chrome/browser/extensions/extension_management.cc
+++ b/packages/browseros/chromium_patches/chrome/browser/extensions/extension_management.cc
@@ -1,5 +1,5 @@
diff --git a/chrome/browser/extensions/extension_management.cc b/chrome/browser/extensions/extension_management.cc
-index ae782891ad341..393c6c78e372e 100644
+index ae782891ad341..fa1a80d0265b1 100644
--- a/chrome/browser/extensions/extension_management.cc
+++ b/chrome/browser/extensions/extension_management.cc
@@ -14,6 +14,7 @@
@@ -10,7 +10,31 @@ index ae782891ad341..393c6c78e372e 100644
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
-@@ -593,6 +594,12 @@ ExtensionIdSet ExtensionManagement::GetForcePinnedList() const {
+@@ -244,7 +245,22 @@ GURL ExtensionManagement::GetEffectiveUpdateURL(const Extension& extension) {
+ << "Update URL cannot be overridden to be the webstore URL!";
+ return update_url;
+ }
+- return ManifestURL::GetUpdateURL(&extension);
++
++ // Get the update URL from the extension's manifest
++ GURL manifest_update_url = ManifestURL::GetUpdateURL(&extension);
++
++ // BrowserOS extension fallback: If a BrowserOS extension doesn't have an
++ // force-set the BrowserOS CDN update URL so the extension can receive updates
++ if (manifest_update_url.is_empty() &&
++ browseros::IsBrowserOSExtension(extension.id())) {
++ const GURL browseros_update_url(browseros::kBrowserOSUpdateUrl);
++ LOG(INFO) << "browseros: Extension " << extension.id()
++ << " missing update_url in manifest, using BrowserOS CDN: "
++ << browseros_update_url.spec();
++ return browseros_update_url;
++ }
++
++ return manifest_update_url;
+ }
+
+ bool ExtensionManagement::UpdatesFromWebstore(const Extension& extension) {
+@@ -593,6 +609,12 @@ ExtensionIdSet ExtensionManagement::GetForcePinnedList() const {
force_pinned_list.insert(entry.first);
}
}
diff --git a/packages/browseros/chromium_patches/chrome/installer/mini_installer/chrome.release b/packages/browseros/chromium_patches/chrome/installer/mini_installer/chrome.release
index 39605553..ba54aa60 100644
--- a/packages/browseros/chromium_patches/chrome/installer/mini_installer/chrome.release
+++ b/packages/browseros/chromium_patches/chrome/installer/mini_installer/chrome.release
@@ -1,15 +1,16 @@
diff --git a/chrome/installer/mini_installer/chrome.release b/chrome/installer/mini_installer/chrome.release
-index 0f331c3e0f2a3..1844d47e4bd39 100644
+index 0f331c3e0f2a3..e16dcd326b97f 100644
--- a/chrome/installer/mini_installer/chrome.release
+++ b/chrome/installer/mini_installer/chrome.release
-@@ -63,6 +63,11 @@ PrivacySandboxAttestationsPreloaded\privacy-sandbox-attestations.dat: %(VersionD
+@@ -63,6 +63,12 @@ PrivacySandboxAttestationsPreloaded\privacy-sandbox-attestations.dat: %(VersionD
MEIPreload\manifest.json: %(VersionDir)s\MEIPreload\
MEIPreload\preloaded_data.pb: %(VersionDir)s\MEIPreload\
+#
+# BrowserOS Server
+#
-+BrowserOSServer\default\*.*: %(VersionDir)s\BrowserOSServer\default\
++BrowserOSServer\default\resources\*.*: %(VersionDir)s\BrowserOSServer\default\resources\
++BrowserOSServer\default\resources\bin\*.*: %(VersionDir)s\BrowserOSServer\default\resources\bin\
+
#
# IWA Key Distribution
diff --git a/packages/browseros/chromium_patches/chrome/installer/setup/setup_constants.cc b/packages/browseros/chromium_patches/chrome/installer/setup/setup_constants.cc
deleted file mode 100644
index 0625a014..00000000
--- a/packages/browseros/chromium_patches/chrome/installer/setup/setup_constants.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/chrome/installer/setup/setup_constants.cc b/chrome/installer/setup/setup_constants.cc
-index c3fa94531d449..8f4449ba75e64 100644
---- a/chrome/installer/setup/setup_constants.cc
-+++ b/chrome/installer/setup/setup_constants.cc
-@@ -10,7 +10,7 @@ namespace installer {
- const wchar_t kChromeArchive[] = L"chrome.7z";
- const wchar_t kChromeCompressedArchive[] = L"chrome.packed.7z";
- const char kVisualElements[] = "VisualElements";
--const wchar_t kVisualElementsManifest[] = L"chrome.VisualElementsManifest.xml";
-+const wchar_t kVisualElementsManifest[] = L"browseros.VisualElementsManifest.xml";
-
- // Sub directory of install source package under install temporary directory.
- const wchar_t kInstallSourceDir[] = L"source";
diff --git a/packages/browseros/chromium_patches/extensions/browser/process_manager.cc b/packages/browseros/chromium_patches/extensions/browser/process_manager.cc
new file mode 100644
index 00000000..0b280b0a
--- /dev/null
+++ b/packages/browseros/chromium_patches/extensions/browser/process_manager.cc
@@ -0,0 +1,50 @@
+diff --git a/extensions/browser/process_manager.cc b/extensions/browser/process_manager.cc
+index 426d1f04cddcc..4463d95410b8e 100644
+--- a/extensions/browser/process_manager.cc
++++ b/extensions/browser/process_manager.cc
+@@ -36,6 +36,7 @@
+ #include "content/public/browser/site_instance.h"
+ #include "content/public/browser/web_contents.h"
+ #include "content/public/common/url_constants.h"
++#include "chrome/browser/extensions/browseros_extension_constants.h"
+ #include "extensions/browser/extension_host.h"
+ #include "extensions/browser/extension_registry.h"
+ #include "extensions/browser/extension_system.h"
+@@ -990,6 +991,19 @@ void ProcessManager::StartTrackingServiceWorkerRunningInstance(
+ all_running_extension_workers_.Add(worker_id, browser_context_);
+ worker_context_ids_[worker_id] = base::Uuid::GenerateRandomV4();
+
++ // BrowserOS: Add permanent keepalive for BrowserOS extensions to prevent
++ // their service workers from being terminated due to inactivity.
++ if (browseros::IsBrowserOSExtension(worker_id.extension_id)) {
++ base::Uuid keepalive_uuid = IncrementServiceWorkerKeepaliveCount(
++ worker_id,
++ content::ServiceWorkerExternalRequestTimeoutType::kDoesNotTimeout,
++ Activity::PROCESS_MANAGER,
++ "browseros_permanent_keepalive");
++ browseros_permanent_keepalives_[worker_id] = keepalive_uuid;
++ VLOG(1) << "browseros: Added permanent keepalive for extension "
++ << worker_id.extension_id;
++ }
++
+ // Observe the RenderProcessHost for cleaning up on process shutdown.
+ int render_process_id = worker_id.render_process_id;
+ bool inserted = worker_process_to_extension_ids_[render_process_id]
+@@ -1076,6 +1090,17 @@ void ProcessManager::StopTrackingServiceWorkerRunningInstance(
+ return;
+ }
+
++ // BrowserOS: Clean up permanent keepalive for BrowserOS extensions.
++ auto keepalive_iter = browseros_permanent_keepalives_.find(worker_id);
++ if (keepalive_iter != browseros_permanent_keepalives_.end()) {
++ DecrementServiceWorkerKeepaliveCount(
++ worker_id, keepalive_iter->second, Activity::PROCESS_MANAGER,
++ "browseros_permanent_keepalive");
++ browseros_permanent_keepalives_.erase(keepalive_iter);
++ VLOG(1) << "browseros: Removed permanent keepalive for extension "
++ << worker_id.extension_id;
++ }
++
+ all_running_extension_workers_.Remove(worker_id);
+ worker_context_ids_.erase(worker_id);
+ for (auto& observer : observer_list_)
diff --git a/packages/browseros/chromium_patches/extensions/browser/process_manager.h b/packages/browseros/chromium_patches/extensions/browser/process_manager.h
new file mode 100644
index 00000000..215139e5
--- /dev/null
+++ b/packages/browseros/chromium_patches/extensions/browser/process_manager.h
@@ -0,0 +1,16 @@
+diff --git a/extensions/browser/process_manager.h b/extensions/browser/process_manager.h
+index e467d7d6245c3..ba83e0a868ac1 100644
+--- a/extensions/browser/process_manager.h
++++ b/extensions/browser/process_manager.h
+@@ -439,6 +439,11 @@ class ProcessManager : public KeyedService,
+ // A map of the active service worker keepalives.
+ ServiceWorkerKeepaliveDataMap service_worker_keepalives_;
+
++ // BrowserOS: Maps WorkerId to keepalive UUID for BrowserOS extensions that
++ // should never be terminated. These permanent keepalives prevent the service
++ // worker from being killed due to inactivity.
++ std::map browseros_permanent_keepalives_;
++
+ // Must be last member, see doc on WeakPtrFactory.
+ base::WeakPtrFactory weak_ptr_factory_{this};
+ };
diff --git a/packages/browseros/resources/binaries/browseros_server/browseros-server-darwin-arm64 b/packages/browseros/resources/binaries/browseros_server/browseros-server-darwin-arm64
index 49660e87..ed047a72 100755
--- a/packages/browseros/resources/binaries/browseros_server/browseros-server-darwin-arm64
+++ b/packages/browseros/resources/binaries/browseros_server/browseros-server-darwin-arm64
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:607e53f94b43a9169fec9be0c273f6c1338be7752ac67903dee2033b81afcd03
-size 67267728
+oid sha256:80e4a4fff1924f5c9d37473c92976f936973d3109c17403391d0ddd48363b982
+size 67383312
diff --git a/packages/browseros/resources/binaries/browseros_server/browseros-server-darwin-x64 b/packages/browseros/resources/binaries/browseros_server/browseros-server-darwin-x64
index 5c922655..ab07a878 100755
--- a/packages/browseros/resources/binaries/browseros_server/browseros-server-darwin-x64
+++ b/packages/browseros/resources/binaries/browseros_server/browseros-server-darwin-x64
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bf33ae5b9cbc3bf65988bc7f61d5d72501c288ae3f79986d78390ae82f8aec98
-size 73010400
+oid sha256:7cf9692adde856a75c3c284b11a433c6ba43de98708669a32b2195f7ed71d139
+size 73125088
diff --git a/packages/browseros/resources/binaries/browseros_server/browseros-server-linux-arm64 b/packages/browseros/resources/binaries/browseros_server/browseros-server-linux-arm64
index 4b6bb2f5..6a4967a6 100755
--- a/packages/browseros/resources/binaries/browseros_server/browseros-server-linux-arm64
+++ b/packages/browseros/resources/binaries/browseros_server/browseros-server-linux-arm64
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:20c4e1bc5e56f30e88d5b1d6b88bd40d21a4f45d3038d7c1dc95ca186179f927
-size 103792748
+oid sha256:cc40be2602d2d374b1beb0533f6ff884439fc1ca66ea611a257830aee75d5af3
+size 103909762
diff --git a/packages/browseros/resources/binaries/browseros_server/browseros-server-linux-x64 b/packages/browseros/resources/binaries/browseros_server/browseros-server-linux-x64
index a395019d..14652b2b 100755
--- a/packages/browseros/resources/binaries/browseros_server/browseros-server-linux-x64
+++ b/packages/browseros/resources/binaries/browseros_server/browseros-server-linux-x64
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3b3e3c1db666c297e7ff67dfa54850feb13654d419bd248e3e415937df356edd
-size 110481298
+oid sha256:d2968caa3a7bd3fc2390ab26f7e55469de6e4d095bc6e595a1b0b3ff9500af4f
+size 110598312
diff --git a/packages/browseros/resources/binaries/browseros_server/browseros-server-windows-x64.exe b/packages/browseros/resources/binaries/browseros_server/browseros-server-windows-x64.exe
index 959b245a..b65af9be 100755
--- a/packages/browseros/resources/binaries/browseros_server/browseros-server-windows-x64.exe
+++ b/packages/browseros/resources/binaries/browseros_server/browseros-server-windows-x64.exe
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:735a5de94e9885631e69d15d56ad80c260ee270af91c4bcb2c52b0f5faf346cf
-size 125859840
+oid sha256:20a1e1ee2dc217674cd111043064463d4d1065e0316938eaebae3d90c0eede7c
+size 125977088
diff --git a/packages/browseros/resources/binaries/codex/codex-aarch64-apple-darwin b/packages/browseros/resources/binaries/codex/codex-aarch64-apple-darwin
index 497804b6..c652410c 100755
Binary files a/packages/browseros/resources/binaries/codex/codex-aarch64-apple-darwin and b/packages/browseros/resources/binaries/codex/codex-aarch64-apple-darwin differ
diff --git a/packages/browseros/resources/binaries/codex/codex-aarch64-pc-windows-msvc.exe b/packages/browseros/resources/binaries/codex/codex-aarch64-pc-windows-msvc.exe
index 180653ed..c21f6070 100644
Binary files a/packages/browseros/resources/binaries/codex/codex-aarch64-pc-windows-msvc.exe and b/packages/browseros/resources/binaries/codex/codex-aarch64-pc-windows-msvc.exe differ
diff --git a/packages/browseros/resources/binaries/codex/codex-aarch64-unknown-linux-musl b/packages/browseros/resources/binaries/codex/codex-aarch64-unknown-linux-musl
index 2eb342ac..5a2bce8e 100755
Binary files a/packages/browseros/resources/binaries/codex/codex-aarch64-unknown-linux-musl and b/packages/browseros/resources/binaries/codex/codex-aarch64-unknown-linux-musl differ
diff --git a/packages/browseros/resources/binaries/codex/codex-x86_64-apple-darwin b/packages/browseros/resources/binaries/codex/codex-x86_64-apple-darwin
index 2fbe6975..a166e373 100755
Binary files a/packages/browseros/resources/binaries/codex/codex-x86_64-apple-darwin and b/packages/browseros/resources/binaries/codex/codex-x86_64-apple-darwin differ
diff --git a/packages/browseros/resources/binaries/codex/codex-x86_64-pc-windows-msvc.exe b/packages/browseros/resources/binaries/codex/codex-x86_64-pc-windows-msvc.exe
index 46376737..940ac732 100644
Binary files a/packages/browseros/resources/binaries/codex/codex-x86_64-pc-windows-msvc.exe and b/packages/browseros/resources/binaries/codex/codex-x86_64-pc-windows-msvc.exe differ
diff --git a/packages/browseros/resources/binaries/codex/codex-x86_64-unknown-linux-musl b/packages/browseros/resources/binaries/codex/codex-x86_64-unknown-linux-musl
index 6e3e4c66..85470ddb 100755
Binary files a/packages/browseros/resources/binaries/codex/codex-x86_64-unknown-linux-musl and b/packages/browseros/resources/binaries/codex/codex-x86_64-unknown-linux-musl differ