mirror of
https://github.com/openai/codex.git
synced 2026-05-18 10:12:59 +00:00
## Why Desktop and mobile Codex clients need a machine-readable way to bootstrap and manage `codex app-server` on remote machines reached over SSH. The same flow is also useful for bringing up app-server with `remote_control` enabled on a fresh developer machine and keeping that managed install current without requiring a human session. ## What changed - add the new experimental `codex-app-server-daemon` crate and wire it into `codex app-server daemon` lifecycle commands: `start`, `restart`, `stop`, `version`, and `bootstrap` - add explicit `enable-remote-control` and `disable-remote-control` commands that persist the launch setting and restart a running managed daemon so the change takes effect immediately - emit JSON success responses for daemon commands so remote callers can consume them directly - support a Unix-only pidfile-backed detached backend for lifecycle management - assume the standalone `install.sh` layout for daemon-managed binaries and always launch `CODEX_HOME/packages/standalone/current/codex` - add bootstrap support for the standalone managed install plus a detached hourly updater loop - harden lifecycle management around concurrent operations, pidfile ownership, stale state cleanup, updater ownership, managed-binary preflight, Unix-only rejection, forced shutdown after the graceful window, and updater process-group tracking/cleanup - document the experimental Unix-only support boundary plus the standalone bootstrap/update flow in `codex-rs/app-server-daemon/README.md` ## Verification - `cargo test -p codex-app-server-daemon -p codex-cli` - live pid validation on `cb4`: `bootstrap --remote-control`, `restart`, `version`, `stop` ## Follow-up - Add updater self-refresh so the long-lived `pid-update-loop` can replace its own executable image after installing a newer managed Codex binary.
67 lines
1.6 KiB
Rust
67 lines
1.6 KiB
Rust
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
|
|
#[cfg(unix)]
|
|
use anyhow::Context;
|
|
#[cfg(unix)]
|
|
use anyhow::Result;
|
|
#[cfg(unix)]
|
|
use anyhow::anyhow;
|
|
#[cfg(unix)]
|
|
use tokio::process::Command;
|
|
|
|
pub(crate) fn managed_codex_bin(codex_home: &Path) -> PathBuf {
|
|
codex_home
|
|
.join("packages")
|
|
.join("standalone")
|
|
.join("current")
|
|
.join(managed_codex_file_name())
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
pub(crate) async fn managed_codex_version(codex_bin: &Path) -> Result<String> {
|
|
let output = Command::new(codex_bin)
|
|
.arg("--version")
|
|
.output()
|
|
.await
|
|
.with_context(|| {
|
|
format!(
|
|
"failed to invoke managed Codex binary {}",
|
|
codex_bin.display()
|
|
)
|
|
})?;
|
|
if !output.status.success() {
|
|
return Err(anyhow!(
|
|
"managed Codex binary {} exited with status {}",
|
|
codex_bin.display(),
|
|
output.status
|
|
));
|
|
}
|
|
|
|
let stdout = String::from_utf8(output.stdout).with_context(|| {
|
|
format!(
|
|
"managed Codex version was not utf-8: {}",
|
|
codex_bin.display()
|
|
)
|
|
})?;
|
|
parse_codex_version(&stdout)
|
|
}
|
|
|
|
fn managed_codex_file_name() -> &'static str {
|
|
if cfg!(windows) { "codex.exe" } else { "codex" }
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn parse_codex_version(output: &str) -> Result<String> {
|
|
let version = output
|
|
.split_whitespace()
|
|
.nth(1)
|
|
.filter(|version| !version.is_empty())
|
|
.ok_or_else(|| anyhow!("managed Codex version output was malformed"))?;
|
|
Ok(version.to_string())
|
|
}
|
|
|
|
#[cfg(all(test, unix))]
|
|
#[path = "managed_install_tests.rs"]
|
|
mod tests;
|