Index: devel/codex/Makefile =================================================================== --- devel/codex/Makefile +++ devel/codex/Makefile @@ -17,6 +17,7 @@ GH_PROJECT = codex GH_TAGNAME = rust-v${V} PKGNAME = codex-${V} +REVISION = 0 # only build the Rust workspace (the repo also contains JS tooling). WRKSRC = ${WRKDIST}/codex-rs @@ -95,6 +96,12 @@ --bin codex \ --bin codex-code-mode-host +# Use the package-owned executable for the per-user daemon. +MODCARGO_ENV += CODEX_SYSTEM_DAEMON_PATH=${LOCALBASE}/libexec/codex/bin/codex + +CODEX_TARGET = ${MACHINE_ARCH:S/amd64/x86_64/}-unknown-openbsd +SUBST_VARS += V CODEX_TARGET + MAKE_ENV = ${MODCARGO_ENV} # test suite expects network access and a configured OpenAI account @@ -111,12 +118,22 @@ ${V8_DISTDIR}/third_party/icu/common/ post-build: + ${SUBST_CMD} -m 644 -c ${FILESDIR}/codex-package.json \ + ${WRKBUILD}/codex-package.json .for s in bash fish zsh cd ${WRKBUILD}/target/release && \ ./codex completion $s > codex.$s .endfor post-install: + ${INSTALL_DATA_DIR} ${PREFIX}/libexec/codex/bin + mv ${PREFIX}/bin/codex ${PREFIX}/libexec/codex/bin/ + mv ${PREFIX}/bin/codex-code-mode-host ${PREFIX}/libexec/codex/bin/ + ln -s ../libexec/codex/bin/codex ${PREFIX}/bin/codex + ln -s ../libexec/codex/bin/codex-code-mode-host \ + ${PREFIX}/bin/codex-code-mode-host + ${INSTALL_DATA} ${WRKBUILD}/codex-package.json \ + ${PREFIX}/libexec/codex/ mv ${PREFIX}/bin/logs_client ${PREFIX}/bin/codex-logs-client ${INSTALL_DATA_DIR} ${PREFIX}/share/bash-completion/completions \ ${PREFIX}/share/fish/vendor_completions.d \ Index: devel/codex/files/codex-package.json =================================================================== --- /dev/null +++ devel/codex/files/codex-package.json @@ -0,0 +1,7 @@ +{ + "layoutVersion": 1, + "version": "${V}", + "target": "${CODEX_TARGET}", + "variant": "codex", + "entrypoint": "bin/codex" +} Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_backend_pid_rs =================================================================== --- /dev/null +++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_backend_pid_rs @@ -0,0 +1,21 @@ +OpenBSD uses the generic ps-based process identity. Keep its lstart string +independent of the invoking client's locale and timezone so start/stop from +different terminals can verify the same daemon. Other platforms are unchanged. + +Index: codex-rs/app-server-daemon/src/backend/pid.rs +--- codex-rs/app-server-daemon/src/backend/pid.rs.orig ++++ codex-rs/app-server-daemon/src/backend/pid.rs +@@ -746,8 +746,11 @@ async fn read_process_start_time(pid: u32) -> Result Result<(String, String)> { +- let output = Command::new("ps") +- .args(["-p", &pid.to_string(), "-o", "stat=", "-o", "lstart="]) ++ let mut command = Command::new("ps"); ++ command.args(["-p", &pid.to_string(), "-o", "stat=", "-o", "lstart="]); ++ #[cfg(target_os = "openbsd")] ++ command.env("LC_ALL", "C").env("TZ", "UTC"); ++ let output = command + .output() + .await + .context("failed to invoke ps for pid-managed app server")?; Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_lib_rs =================================================================== --- /dev/null +++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_lib_rs @@ -0,0 +1,91 @@ +Use a distributor-owned executable with the normal daemon lifecycle and IPC. +Reject binary self-updates and incompatible running daemons; installation +and replacement belong to pkg_add. + +Index: codex-rs/app-server-daemon/src/lib.rs +--- codex-rs/app-server-daemon/src/lib.rs.orig ++++ codex-rs/app-server-daemon/src/lib.rs +@@ -225,6 +225,12 @@ enum RestartDecision { + Restart, + } + ++/// A distributor-owned daemon is installed and updated outside Codex. ++/// Process lifecycle and IPC still use the normal managed-daemon backend. ++pub fn system_daemon_path() -> Option<&'static Path> { ++ managed_install::system_codex_bin() ++} ++ + pub async fn run(command: LifecycleCommand) -> Result { + ensure_supported_platform()?; + #[cfg(windows)] +@@ -282,6 +288,10 @@ pub async fn run_pid_update_loop( + restore_release: Option, + ) -> Result<()> { + ensure_supported_platform()?; ++ anyhow::ensure!( ++ managed_install::system_codex_bin().is_none(), ++ "the daemon is managed by the system package manager; use pkg_add -u" ++ ); + #[cfg(windows)] + backend::windows::ensure_not_elevated()?; + update_loop::run(http_client_factory, restore_release).await +@@ -291,6 +301,20 @@ pub async fn update( + http_client_factory: codex_http_client::HttpClientFactory, + ) -> Result { + ensure_supported_platform()?; ++ if let Some(path) = managed_install::system_codex_bin() { ++ let daemon = Daemon::from_environment()?; ++ return Ok(UpdateOutput { ++ status: UpdateStatus::Unsupported, ++ managed_codex_path: path.to_path_buf(), ++ installed_version: daemon.managed_codex_version_best_effort().await, ++ running_version: client::probe(&daemon.socket_path) ++ .await ++ .ok() ++ .map(|info| info.app_server_version), ++ message: "The daemon is managed by the system package manager; use pkg_add -u." ++ .to_string(), ++ }); ++ } + #[cfg(windows)] + backend::windows::ensure_not_elevated()?; + update_loop::request_manual_update(&Daemon::from_environment()?, http_client_factory).await +@@ -448,6 +472,15 @@ impl Daemon { + self.wait_until_ready().await?, + ) + }; ++ if managed_install::system_codex_bin().is_some() { ++ anyhow::ensure!( ++ info.app_server_version == env!("CARGO_PKG_VERSION"), ++ "system-managed daemon is running version {} but this CLI requires {}; \ ++ run `codex app-server daemon restart` after pkg_add -u", ++ info.app_server_version, ++ env!("CARGO_PKG_VERSION") ++ ); ++ } + if backend.is_some() + && let Err(err) = managed.ensure_managed_updater(&settings).await + { +@@ -892,6 +925,10 @@ impl Daemon { + + async fn ensure_managed_updater(&self, settings: &DaemonSettings) -> Result { + let updater = backend::pid_update_loop_backend(self.backend_paths(settings)); ++ if managed_install::system_codex_bin().is_some() { ++ updater.stop().await?; ++ return Ok(false); ++ } + if !settings.auto_update_enabled { + updater.stop().await?; + return Ok(false); +@@ -981,6 +1018,11 @@ impl Daemon { + } + + let managed_codex_path = self.managed_codex_bin.display(); ++ if managed_install::system_codex_bin().is_some() { ++ return Err(anyhow!( ++ "system daemon executable not found at {managed_codex_path}; reinstall the codex package with pkg_add" ++ )); ++ } + Err(anyhow!( + "daemon executable not found at {managed_codex_path}; repair the existing installation, or run `codex app-server daemon start` to install a missing daemon" + )) Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_managed_install_rs =================================================================== --- /dev/null +++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_managed_install_rs @@ -0,0 +1,42 @@ +Allow the port to select its already installed codex as the app-server +executable. System ownership is a build-time setting and takes precedence +over user-managed packages; it never opts into the internal release updater. + +Index: codex-rs/app-server-daemon/src/managed_install.rs +--- codex-rs/app-server-daemon/src/managed_install.rs.orig ++++ codex-rs/app-server-daemon/src/managed_install.rs +@@ -14,6 +14,14 @@ use tokio::fs; + use tokio::process::Command; + use tokio::time::timeout; + ++/// A distributor-owned executable has no managed package or internal updater. ++/// This is a build-time choice, so the runtime environment cannot redirect it. ++pub(crate) fn system_codex_bin() -> Option<&'static Path> { ++ option_env!("CODEX_SYSTEM_DAEMON_PATH") ++ .filter(|path| !path.is_empty()) ++ .map(Path::new) ++} ++ + /// New daemons own their packages, regardless of how the calling CLI was installed. + /// Preserve legacy launch state, including logs left after a daemon is stopped; + /// settings, installer selections, and lock files alone do not prove a prior launch. +@@ -57,6 +65,9 @@ pub(crate) fn package_root(codex_home: &Path) -> PathB + + /// Resolve both packaged and legacy binaries without requiring a valid install. + pub(crate) fn managed_codex_bin(codex_home: &Path) -> PathBuf { ++ if let Some(path) = system_codex_bin() { ++ return path.to_path_buf(); ++ } + let root = package_root(codex_home); + let current = root.join("current"); + let packaged = current.join("bin").join(managed_codex_file_name()); +@@ -72,6 +83,9 @@ pub(crate) fn managed_codex_bin(codex_home: &Path) -> + + /// Only latest-channel stable releases may run the public latest-version updater. + pub(crate) fn is_stable_standalone_release(codex_home: &Path, codex_bin: &Path) -> bool { ++ if system_codex_bin().is_some() { ++ return false; ++ } + let standalone = package_root(codex_home); + let Ok(releases) = std::fs::canonicalize(standalone.join("releases")) else { + return false; Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_prepare_install_rs =================================================================== --- /dev/null +++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_prepare_install_rs @@ -0,0 +1,56 @@ +Validate the system package manifest and executable version without copying +a CLI package into CODEX_HOME. Reject explicit --from-cli replacement of +an executable owned by the system package manager. + +Index: codex-rs/app-server-daemon/src/prepare_install.rs +--- codex-rs/app-server-daemon/src/prepare_install.rs.orig ++++ codex-rs/app-server-daemon/src/prepare_install.rs +@@ -26,6 +26,37 @@ pub struct InstallRequest { + + /// Prepare a missing package while the caller holds the daemon operation lock. + pub(super) async fn prepare(daemon: &Daemon, settings: &DaemonSettings) -> Result<()> { ++ if let Some(path) = managed_install::system_codex_bin() { ++ daemon.ensure_managed_codex_bin()?; ++ let context = InstallContext::from_exe( ++ cfg!(target_os = "macos"), ++ Some(path), ++ /*method_override*/ None, ++ ); ++ let manifest = context.package_manifest().with_context(|| { ++ format!( ++ "system Codex package metadata missing or invalid for {}; reinstall the codex package with pkg_add", ++ path.display() ++ ) ++ })?; ++ let expected = env!("CARGO_PKG_VERSION"); ++ anyhow::ensure!( ++ manifest.version.to_string() == expected, ++ "system Codex package version {} differs from CLI version {expected}; update the codex package with pkg_add -u", ++ manifest.version ++ ); ++ let version = tokio::time::timeout( ++ std::time::Duration::from_secs(5), ++ managed_install::managed_codex_version(path), ++ ) ++ .await ++ .context("timed out checking the system Codex executable version")??; ++ anyhow::ensure!( ++ version == expected, ++ "system Codex executable version {version} differs from CLI version {expected}; update the codex package with pkg_add -u" ++ ); ++ return Ok(()); ++ } + let source = InstallContext::current().package_layout.as_ref(); + // Keep package replacement state out of the CLI dispatcher's async stack frame. + Box::pin(prepare_from_package( +@@ -46,6 +77,10 @@ pub async fn update_from_cli( + confirm: impl FnOnce(&InstallRequest) -> Result, + ) -> Result> { + crate::ensure_supported_platform()?; ++ anyhow::ensure!( ++ managed_install::system_codex_bin().is_none(), ++ "the daemon is managed by the system package manager; use pkg_add -u" ++ ); + #[cfg(windows)] + crate::backend::windows::ensure_not_elevated()?; + let daemon = Daemon::from_environment()?; Index: devel/codex/patches/patch-codex-rs_features_src_lib_rs =================================================================== --- devel/codex/patches/patch-codex-rs_features_src_lib_rs +++ /dev/null @@ -1,15 +0,0 @@ -Disable daemon auto start by default -https://github.com/openai/codex/issues/48050 - -Index: codex-rs/features/src/lib.rs ---- codex-rs/features/src/lib.rs.orig -+++ codex-rs/features/src/lib.rs -@@ -992,7 +992,7 @@ pub const FEATURES: &[FeatureSpec] = &[ - id: Feature::DaemonAutoStart, - key: "daemon_auto_start", - stage: Stage::Stable, -- default_enabled: true, -+ default_enabled: false, - }, - FeatureSpec { - id: Feature::TranscriptV2, Index: devel/codex/patches/patch-codex-rs_tui_src_app_daemon_menu_rs =================================================================== --- /dev/null +++ devel/codex/patches/patch-codex-rs_tui_src_app_daemon_menu_rs @@ -0,0 +1,28 @@ +Keep the daemon enabled while directing package updates to pkg_add. +Do not offer self-installation actions for a distributor-owned executable. + +Index: codex-rs/tui/src/app/daemon_menu.rs +--- codex-rs/tui/src/app/daemon_menu.rs.orig ++++ codex-rs/tui/src/app/daemon_menu.rs +@@ -39,6 +39,10 @@ impl App { + Some( + "Manage this server on its host. Local daemon updates are unavailable for remote connections.", + ) ++ } else if codex_app_server_daemon::system_daemon_path().is_some() { ++ Some( ++ "The daemon is installed by the system package manager. Use pkg_add -u, then codex app-server daemon restart.", ++ ) + } else if self.daemon_cli_executable.is_none() { + Some("Run the Codex CLI to manage the daemon from this menu.") + } else { +@@ -89,7 +93,9 @@ impl App { + let Some(executable) = &self.daemon_cli_executable else { + return; + }; +- if matches!(self.app_server_target, AppServerTarget::Remote { .. }) { ++ if matches!(self.app_server_target, AppServerTarget::Remote { .. }) ++ || codex_app_server_daemon::system_daemon_path().is_some() ++ { + return; + } + let mut explanation = match source { Index: devel/codex/pkg/PLIST =================================================================== --- devel/codex/pkg/PLIST +++ devel/codex/pkg/PLIST @@ -1,6 +1,11 @@ -@bin bin/codex -@bin bin/codex-code-mode-host +bin/codex +bin/codex-code-mode-host @bin bin/codex-logs-client +libexec/codex/ +libexec/codex/bin/ +@bin libexec/codex/bin/codex +@bin libexec/codex/bin/codex-code-mode-host +libexec/codex/codex-package.json share/bash-completion/completions/codex share/doc/codex/ share/doc/codex/CLA.md Index: devel/codex/pkg/README =================================================================== --- devel/codex/pkg/README +++ devel/codex/pkg/README @@ -20,3 +20,16 @@ Also, ${PKGSTEM} may use a lot of open files, you may need to raise both the system-wide maxfiles and used login class limits. + +The app-server daemon runs on demand as the invoking user, using the +package-owned executable in ${LOCALBASE}/libexec/codex/bin. There is no +system rc.d service. Manage it with: + + codex app-server daemon start + codex app-server daemon version + codex app-server daemon restart + codex app-server daemon stop + +Use pkg_add -u to update the package, then restart the daemon. Internal +binary updates and --from-cli replacement are disabled. State remains in +CODEX_HOME (default ~/.codex); no daemon package is copied there.