Skip to main content

codex_mobile_bridge/manage/
mod.rs

1use std::collections::HashMap;
2use std::env;
3use std::fs;
4#[cfg(unix)]
5use std::os::unix::fs::{PermissionsExt, symlink};
6use std::path::{Path, PathBuf};
7use std::process::Command;
8
9use anyhow::{Context, Result, bail};
10use clap::{Args, Parser, Subcommand, ValueEnum};
11use serde::{Deserialize, Serialize};
12use serde_json::json;
13use sha2::{Digest, Sha256};
14
15use crate::{BRIDGE_BUILD_HASH, BRIDGE_PROTOCOL_VERSION, BRIDGE_VERSION};
16
17mod commands;
18mod environment;
19mod records;
20mod release;
21#[cfg(test)]
22mod tests;
23
24const CRATE_NAME: &str = env!("CARGO_PKG_NAME");
25const BINARY_NAME: &str = "codex-mobile-bridge";
26const SERVICE_NAME: &str = "codex-mobile-bridge.service";
27const DEFAULT_LISTEN_ADDR: &str = "0.0.0.0:8787";
28const DEFAULT_RUNTIME_LIMIT: usize = 4;
29
30#[derive(Debug, Parser)]
31pub struct ManageCli {
32    #[command(subcommand)]
33    command: ManageCommand,
34}
35
36#[derive(Debug, Subcommand)]
37enum ManageCommand {
38    Activate(ActivateArgs),
39    SelfUpdate(SelfUpdateArgs),
40    Rollback(RollbackArgs),
41    Repair(RepairArgs),
42    Metadata,
43}
44
45#[derive(Debug, Clone, Args, Default)]
46struct EnvOverrides {
47    #[arg(long)]
48    bridge_token: Option<String>,
49
50    #[arg(long)]
51    listen_addr: Option<String>,
52
53    #[arg(long)]
54    runtime_limit: Option<usize>,
55
56    #[arg(long)]
57    db_path: Option<PathBuf>,
58
59    #[arg(long)]
60    codex_home: Option<PathBuf>,
61
62    #[arg(long)]
63    codex_binary: Option<String>,
64
65    #[arg(long)]
66    launch_path: Option<String>,
67}
68
69#[derive(Debug, Clone, Args)]
70pub struct ActivateArgs {
71    #[command(flatten)]
72    env: EnvOverrides,
73
74    #[arg(long, value_enum, default_value_t = ActivateOperation::Install)]
75    operation: ActivateOperation,
76}
77
78#[derive(Debug, Clone, Copy, Eq, PartialEq, ValueEnum)]
79pub enum ActivateOperation {
80    Install,
81    Update,
82    Repair,
83}
84
85impl ActivateOperation {
86    fn as_str(self) -> &'static str {
87        match self {
88            Self::Install => "install",
89            Self::Update => "update",
90            Self::Repair => "repair",
91        }
92    }
93}
94
95#[derive(Debug, Clone, Args)]
96pub struct SelfUpdateArgs {
97    #[command(flatten)]
98    env: EnvOverrides,
99
100    #[arg(long)]
101    target_version: String,
102
103    #[arg(long, default_value = "cargo")]
104    cargo_binary: String,
105
106    #[arg(long, default_value = "crates-io")]
107    registry: String,
108}
109
110#[derive(Debug, Clone, Args)]
111pub struct RollbackArgs {
112    #[command(flatten)]
113    env: EnvOverrides,
114
115    #[arg(long, default_value = "cargo")]
116    cargo_binary: String,
117
118    #[arg(long, default_value = "crates-io")]
119    registry: String,
120}
121
122#[derive(Debug, Clone, Args)]
123pub struct RepairArgs {
124    #[command(flatten)]
125    env: EnvOverrides,
126
127    #[arg(long, default_value = "cargo")]
128    cargo_binary: String,
129
130    #[arg(long, default_value = "crates-io")]
131    registry: String,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
135struct BridgeEnvValues {
136    bridge_token: String,
137    listen_addr: String,
138    runtime_limit: usize,
139    db_path: PathBuf,
140    codex_home: Option<PathBuf>,
141    codex_binary: String,
142    launch_path: String,
143}
144
145#[derive(Debug, Clone)]
146struct ManagedPaths {
147    share_dir: PathBuf,
148    releases_dir: PathBuf,
149    current_link: PathBuf,
150    config_dir: PathBuf,
151    systemd_user_dir: PathBuf,
152    state_dir: PathBuf,
153    env_file: PathBuf,
154    unit_file: PathBuf,
155    install_record_file: PathBuf,
156    bridge_db_path: PathBuf,
157}
158
159impl ManagedPaths {
160    fn new(home_dir: PathBuf) -> Self {
161        let share_dir = home_dir.join(".local/share/codex-mobile");
162        let config_dir = home_dir.join(".config/codex-mobile");
163        let systemd_user_dir = home_dir.join(".config/systemd/user");
164        let state_dir = home_dir.join(".local/state/codex-mobile");
165        Self {
166            current_link: share_dir.join("current"),
167            releases_dir: share_dir.join("releases"),
168            env_file: config_dir.join("bridge.env"),
169            unit_file: systemd_user_dir.join(SERVICE_NAME),
170            install_record_file: state_dir.join("install.json"),
171            bridge_db_path: state_dir.join("bridge.db"),
172            share_dir,
173            config_dir,
174            systemd_user_dir,
175            state_dir,
176        }
177    }
178
179    fn release_root_for_version(&self, version: &str) -> PathBuf {
180        self.releases_dir.join(format!("{CRATE_NAME}-{version}"))
181    }
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, Default)]
185#[serde(rename_all = "camelCase")]
186#[serde(default)]
187struct InstallRecord {
188    install_state: String,
189    current_artifact_id: Option<String>,
190    current_version: Option<String>,
191    current_build_hash: Option<String>,
192    current_sha256: Option<String>,
193    current_protocol_version: Option<u32>,
194    current_release_path: Option<String>,
195    previous_artifact_id: Option<String>,
196    previous_version: Option<String>,
197    previous_build_hash: Option<String>,
198    previous_sha256: Option<String>,
199    previous_protocol_version: Option<u32>,
200    previous_release_path: Option<String>,
201    last_operation: Option<String>,
202    last_operation_status: Option<String>,
203    last_operation_at_ms: i64,
204    installed_at_ms: i64,
205    updated_at_ms: i64,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210struct ReleaseMetadata {
211    artifact_id: String,
212    version: String,
213    build_hash: String,
214    sha256: String,
215    protocol_version: u32,
216    release_root: String,
217    executable_path: String,
218}
219
220pub fn run(cli: ManageCli) -> Result<()> {
221    match cli.command {
222        ManageCommand::Activate(args) => commands::run_activate(args),
223        ManageCommand::SelfUpdate(args) => commands::run_self_update(args),
224        ManageCommand::Rollback(args) => commands::run_rollback(args),
225        ManageCommand::Repair(args) => commands::run_repair(args),
226        ManageCommand::Metadata => commands::print_metadata(),
227    }
228}