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