use std::collections::HashMap;
use std::env;
use std::fs;
#[cfg(unix)]
use std::os::unix::fs::{PermissionsExt, symlink};
use std::path::{Path, PathBuf};
use std::process::Command;
use anyhow::{Context, Result, bail};
use clap::{Args, Parser, Subcommand, ValueEnum};
use serde::{Deserialize, Serialize};
use serde_json::json;
use sha2::{Digest, Sha256};
use crate::{BRIDGE_BUILD_HASH, BRIDGE_PROTOCOL_VERSION, BRIDGE_VERSION};
mod commands;
mod environment;
mod records;
mod release;
mod tasks;
#[cfg(test)]
mod tests;
const CRATE_NAME: &str = env!("CARGO_PKG_NAME");
const BINARY_NAME: &str = "codex-mobile-bridge";
const SERVICE_NAME: &str = "codex-mobile-bridge.service";
const DEFAULT_LISTEN_ADDR: &str = "0.0.0.0:8787";
const DEFAULT_RUNTIME_LIMIT: usize = 4;
#[derive(Debug, Parser)]
pub struct ManageCli {
#[command(subcommand)]
command: ManageCommand,
}
#[derive(Debug, Subcommand)]
enum ManageCommand {
Activate(ActivateArgs),
SelfUpdate(SelfUpdateArgs),
Rollback(RollbackArgs),
Repair(RepairArgs),
Metadata,
RunTask(RunTaskArgs),
}
#[derive(Debug, Clone, Args, Default)]
struct EnvOverrides {
#[arg(long)]
bridge_token: Option<String>,
#[arg(long)]
listen_addr: Option<String>,
#[arg(long)]
runtime_limit: Option<usize>,
#[arg(long)]
db_path: Option<PathBuf>,
#[arg(long)]
codex_home: Option<PathBuf>,
#[arg(long)]
codex_binary: Option<String>,
#[arg(long)]
launch_path: Option<String>,
}
#[derive(Debug, Clone, Args)]
pub struct ActivateArgs {
#[command(flatten)]
env: EnvOverrides,
#[arg(long, value_enum, default_value_t = ActivateOperation::Install)]
operation: ActivateOperation,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, ValueEnum)]
pub enum ActivateOperation {
Install,
Update,
Repair,
}
impl ActivateOperation {
fn as_str(self) -> &'static str {
match self {
Self::Install => "install",
Self::Update => "update",
Self::Repair => "repair",
}
}
}
#[derive(Debug, Clone, Args)]
pub struct SelfUpdateArgs {
#[command(flatten)]
env: EnvOverrides,
#[arg(long)]
target_version: String,
#[arg(long, default_value = "cargo")]
cargo_binary: String,
#[arg(long, default_value = "crates-io")]
registry: String,
}
#[derive(Debug, Clone, Args)]
pub struct RollbackArgs {
#[command(flatten)]
env: EnvOverrides,
#[arg(long, default_value = "cargo")]
cargo_binary: String,
#[arg(long, default_value = "crates-io")]
registry: String,
}
#[derive(Debug, Clone, Args)]
pub struct RepairArgs {
#[command(flatten)]
env: EnvOverrides,
#[arg(long, default_value = "cargo")]
cargo_binary: String,
#[arg(long, default_value = "crates-io")]
registry: String,
}
#[derive(Debug, Clone, Args)]
pub struct RunTaskArgs {
#[arg(long)]
task_id: String,
#[arg(long)]
db_path: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct BridgeEnvValues {
bridge_token: String,
listen_addr: String,
runtime_limit: usize,
db_path: PathBuf,
codex_home: Option<PathBuf>,
codex_binary: String,
launch_path: String,
}
#[derive(Debug, Clone)]
struct ManagedPaths {
share_dir: PathBuf,
releases_dir: PathBuf,
current_link: PathBuf,
config_dir: PathBuf,
systemd_user_dir: PathBuf,
state_dir: PathBuf,
env_file: PathBuf,
unit_file: PathBuf,
install_record_file: PathBuf,
bridge_db_path: PathBuf,
}
impl ManagedPaths {
fn new(home_dir: PathBuf) -> Self {
let share_dir = home_dir.join(".local/share/codex-mobile");
let config_dir = home_dir.join(".config/codex-mobile");
let systemd_user_dir = home_dir.join(".config/systemd/user");
let state_dir = home_dir.join(".local/state/codex-mobile");
Self {
current_link: share_dir.join("current"),
releases_dir: share_dir.join("releases"),
env_file: config_dir.join("bridge.env"),
unit_file: systemd_user_dir.join(SERVICE_NAME),
install_record_file: state_dir.join("install.json"),
bridge_db_path: state_dir.join("bridge.db"),
share_dir,
config_dir,
systemd_user_dir,
state_dir,
}
}
fn release_root_for_version(&self, version: &str) -> PathBuf {
self.releases_dir.join(format!("{CRATE_NAME}-{version}"))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
struct InstallRecord {
install_state: String,
current_artifact_id: Option<String>,
current_version: Option<String>,
current_build_hash: Option<String>,
current_sha256: Option<String>,
current_protocol_version: Option<u32>,
current_release_path: Option<String>,
previous_artifact_id: Option<String>,
previous_version: Option<String>,
previous_build_hash: Option<String>,
previous_sha256: Option<String>,
previous_protocol_version: Option<u32>,
previous_release_path: Option<String>,
last_operation: Option<String>,
last_operation_status: Option<String>,
last_operation_at_ms: i64,
installed_at_ms: i64,
updated_at_ms: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ReleaseMetadata {
artifact_id: String,
version: String,
build_hash: String,
sha256: String,
protocol_version: u32,
release_root: String,
executable_path: String,
}
pub fn run(cli: ManageCli) -> Result<()> {
match cli.command {
ManageCommand::Activate(args) => commands::run_activate(args),
ManageCommand::SelfUpdate(args) => commands::run_self_update(args),
ManageCommand::Rollback(args) => commands::run_rollback(args),
ManageCommand::Repair(args) => commands::run_repair(args),
ManageCommand::Metadata => commands::print_metadata(),
ManageCommand::RunTask(args) => tasks::run_bridge_management_task(args),
}
}
pub fn spawn_bridge_management_task(task_id: &str, db_path: &Path) -> Result<()> {
tasks::spawn_bridge_management_task(task_id, db_path)
}
pub fn current_bridge_management_snapshot(
db_path: &Path,
) -> Result<crate::bridge_protocol::ManagedBridgeSnapshot> {
tasks::current_bridge_management_snapshot(db_path)
}
pub fn inspect_remote_state(
db_path: &Path,
) -> Result<crate::bridge_protocol::RemoteInspectionReport> {
tasks::inspect_remote_state(db_path)
}