diff --git a/.github/dotslash-config.json b/.github/dotslash-config.json index 5803e0a0df..e033652ced 100644 --- a/.github/dotslash-config.json +++ b/.github/dotslash-config.json @@ -25,6 +25,13 @@ "linux-x86_64": { "regex": "^codex-cli-x86_64-unknown-linux-musl\\.zst$", "path": "codex-cli" }, "linux-aarch64": { "regex": "^codex-cli-aarch64-unknown-linux-gnu\\.zst$", "path": "codex-cli" } } + }, + + "codex-linux-sandbox": { + "platforms": { + "linux-x86_64": { "regex": "^codex-linux-sandbox-x86_64-unknown-linux-musl\\.zst$", "path": "codex-linux-sandbox" }, + "linux-aarch64": { "regex": "^codex-linux-sandbox-aarch64-unknown-linux-gnu\\.zst$", "path": "codex-linux-sandbox" } + } } } } diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 3c0d92c45f..00e2dcb15b 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -106,6 +106,15 @@ jobs: cp target/${{ matrix.target }}/release/codex-exec "$dest/codex-exec-${{ matrix.target }}" cp target/${{ matrix.target }}/release/codex-cli "$dest/codex-cli-${{ matrix.target }}" + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }} || ${{ matrix.target == 'aarch64-unknown-linux-gnu' }} + name: Stage Linux-only artifacts + shell: bash + run: | + cp target/${{ matrix.target }}/release/codex-linux-sandbox "$dest/codex-linux-sandbox-${{ matrix.target }}" + + - name: Compress artifacts + shell: bash + run: | zstd -T0 -19 --rm "$dest"/* - uses: actions/upload-artifact@v4 diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index f4fe871e6a..1e0be4798d 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -15,4 +15,7 @@ members = [ version = "0.1.0" [profile.release] -lto = "fat" \ No newline at end of file +lto = "fat" +# Because we bundle some of these executables with the TypeScript CLI, we +# remove everything to make the binary as small as possible. +strip = "symbols" diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index c160942980..6a3a3593b9 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -7,6 +7,14 @@ edition = "2021" name = "codex" path = "src/main.rs" +[[bin]] +name = "codex-linux-sandbox" +path = "src/linux-sandbox/main.rs" + +[lib] +name = "codex_cli" +path = "src/lib.rs" + [dependencies] anyhow = "1" clap = { version = "4", features = ["derive"] } diff --git a/codex-rs/cli/src/landlock.rs b/codex-rs/cli/src/landlock.rs index b57591bfe7..f663889795 100644 --- a/codex-rs/cli/src/landlock.rs +++ b/codex-rs/cli/src/landlock.rs @@ -11,10 +11,7 @@ use std::process::ExitStatus; /// Execute `command` in a Linux sandbox (Landlock + seccomp) the way Codex /// would. -pub(crate) fn run_landlock( - command: Vec, - sandbox_policy: SandboxPolicy, -) -> anyhow::Result<()> { +pub fn run_landlock(command: Vec, sandbox_policy: SandboxPolicy) -> anyhow::Result<()> { if command.is_empty() { anyhow::bail!("command args are empty"); } diff --git a/codex-rs/cli/src/lib.rs b/codex-rs/cli/src/lib.rs new file mode 100644 index 0000000000..8d14388ab3 --- /dev/null +++ b/codex-rs/cli/src/lib.rs @@ -0,0 +1,47 @@ +#[cfg(target_os = "linux")] +pub mod landlock; +pub mod proto; +pub mod seatbelt; + +use clap::Parser; +use codex_core::protocol::SandboxPolicy; +use codex_core::SandboxPermissionOption; + +#[derive(Debug, Parser)] +pub struct SeatbeltCommand { + /// Convenience alias for low-friction sandboxed automatic execution (network-disabled sandbox that can write to cwd and TMPDIR) + #[arg(long = "full-auto", default_value_t = false)] + pub full_auto: bool, + + #[clap(flatten)] + pub sandbox: SandboxPermissionOption, + + /// Full command args to run under seatbelt. + #[arg(trailing_var_arg = true)] + pub command: Vec, +} + +#[derive(Debug, Parser)] +pub struct LandlockCommand { + /// Convenience alias for low-friction sandboxed automatic execution (network-disabled sandbox that can write to cwd and TMPDIR) + #[arg(long = "full-auto", default_value_t = false)] + pub full_auto: bool, + + #[clap(flatten)] + pub sandbox: SandboxPermissionOption, + + /// Full command args to run under landlock. + #[arg(trailing_var_arg = true)] + pub command: Vec, +} + +pub fn create_sandbox_policy(full_auto: bool, sandbox: SandboxPermissionOption) -> SandboxPolicy { + if full_auto { + SandboxPolicy::new_full_auto_policy() + } else { + match sandbox.permissions.map(Into::into) { + Some(sandbox_policy) => sandbox_policy, + None => SandboxPolicy::new_read_only_policy(), + } + } +} diff --git a/codex-rs/cli/src/linux-sandbox/main.rs b/codex-rs/cli/src/linux-sandbox/main.rs new file mode 100644 index 0000000000..e8b887b226 --- /dev/null +++ b/codex-rs/cli/src/linux-sandbox/main.rs @@ -0,0 +1,22 @@ +#[cfg(not(target_os = "linux"))] +fn main() -> anyhow::Result<()> { + eprintln!("codex-linux-sandbox is not supported on this platform."); + std::process::exit(1); +} + +#[cfg(target_os = "linux")] +fn main() -> anyhow::Result<()> { + use clap::Parser; + use codex_cli::create_sandbox_policy; + use codex_cli::landlock; + use codex_cli::LandlockCommand; + + let LandlockCommand { + full_auto, + sandbox, + command, + } = LandlockCommand::parse(); + let sandbox_policy = create_sandbox_policy(full_auto, sandbox); + landlock::run_landlock(command, sandbox_policy)?; + Ok(()) +} diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index ba6b15d99f..6866714e1b 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -1,11 +1,9 @@ -#[cfg(target_os = "linux")] -mod landlock; -mod proto; -mod seatbelt; - use clap::Parser; -use codex_core::protocol::SandboxPolicy; -use codex_core::SandboxPermissionOption; +use codex_cli::create_sandbox_policy; +use codex_cli::proto; +use codex_cli::seatbelt; +use codex_cli::LandlockCommand; +use codex_cli::SeatbeltCommand; use codex_exec::Cli as ExecCli; use codex_repl::Cli as ReplCli; use codex_tui::Cli as TuiCli; @@ -63,34 +61,6 @@ enum DebugCommand { Landlock(LandlockCommand), } -#[derive(Debug, Parser)] -struct SeatbeltCommand { - /// Convenience alias for low-friction sandboxed automatic execution (network-disabled sandbox that can write to cwd and TMPDIR) - #[arg(long = "full-auto", default_value_t = false)] - full_auto: bool, - - #[clap(flatten)] - pub sandbox: SandboxPermissionOption, - - /// Full command args to run under seatbelt. - #[arg(trailing_var_arg = true)] - command: Vec, -} - -#[derive(Debug, Parser)] -struct LandlockCommand { - /// Convenience alias for low-friction sandboxed automatic execution (network-disabled sandbox that can write to cwd and TMPDIR) - #[arg(long = "full-auto", default_value_t = false)] - full_auto: bool, - - #[clap(flatten)] - sandbox: SandboxPermissionOption, - - /// Full command args to run under landlock. - #[arg(trailing_var_arg = true)] - command: Vec, -} - #[derive(Debug, Parser)] struct ReplProto {} @@ -127,7 +97,7 @@ async fn main() -> anyhow::Result<()> { full_auto, }) => { let sandbox_policy = create_sandbox_policy(full_auto, sandbox); - landlock::run_landlock(command, sandbox_policy)?; + codex_cli::landlock::run_landlock(command, sandbox_policy)?; } #[cfg(not(target_os = "linux"))] DebugCommand::Landlock(_) => { @@ -138,14 +108,3 @@ async fn main() -> anyhow::Result<()> { Ok(()) } - -fn create_sandbox_policy(full_auto: bool, sandbox: SandboxPermissionOption) -> SandboxPolicy { - if full_auto { - SandboxPolicy::new_full_auto_policy() - } else { - match sandbox.permissions.map(Into::into) { - Some(sandbox_policy) => sandbox_policy, - None => SandboxPolicy::new_read_only_policy(), - } - } -} diff --git a/codex-rs/cli/src/seatbelt.rs b/codex-rs/cli/src/seatbelt.rs index f4a8edde00..6c49d8cc7e 100644 --- a/codex-rs/cli/src/seatbelt.rs +++ b/codex-rs/cli/src/seatbelt.rs @@ -1,7 +1,7 @@ use codex_core::exec::create_seatbelt_command; use codex_core::protocol::SandboxPolicy; -pub(crate) async fn run_seatbelt( +pub async fn run_seatbelt( command: Vec, sandbox_policy: SandboxPolicy, ) -> anyhow::Result<()> {