diff options
| author | soryu <soryu@soryu.co> | 2026-01-11 05:52:14 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-15 00:21:16 +0000 |
| commit | 87044a747b47bd83249d61a45842c7f7b2eae56d (patch) | |
| tree | ef2000ce79ffcc2723ef841acef5aa1deb1d5378 /makima/daemon/src/main.rs | |
| parent | 077820c4167c168072d217a1b01df840463a12a8 (diff) | |
| download | soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.tar.gz soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.zip | |
Contract system
Diffstat (limited to 'makima/daemon/src/main.rs')
| -rw-r--r-- | makima/daemon/src/main.rs | 313 |
1 files changed, 0 insertions, 313 deletions
diff --git a/makima/daemon/src/main.rs b/makima/daemon/src/main.rs deleted file mode 100644 index e4ca5d4..0000000 --- a/makima/daemon/src/main.rs +++ /dev/null @@ -1,313 +0,0 @@ -//! Makima Daemon - Git worktree orchestration for Claude Code. - -use std::sync::Arc; - -use std::path::Path; - -use clap::Parser; -use makima_daemon::cli::Cli; -use makima_daemon::config::{DaemonConfig, RepoEntry}; -use makima_daemon::db::LocalDb; -use makima_daemon::error::DaemonError; -use makima_daemon::task::{TaskConfig, TaskManager}; -use makima_daemon::ws::{DaemonCommand, WsClient}; -use tokio::process::Command; -use tokio::sync::mpsc; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; - -#[tokio::main] -async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { - eprintln!("=== Makima Daemon Starting ==="); - - // Parse command-line arguments - let cli = Cli::parse(); - - // Load configuration with CLI overrides - eprintln!("[1/5] Loading configuration..."); - let config = match DaemonConfig::load_with_cli(&cli) { - Ok(cfg) => { - eprintln!(" Config loaded: server={}", cfg.server.url); - cfg - } - Err(e) => { - eprintln!("Failed to load configuration: {}", e); - eprintln!(); - eprintln!("Use CLI flags:"); - eprintln!(" makima-daemon --server-url ws://localhost:8080 --api-key your-api-key"); - eprintln!(); - eprintln!("Or set environment variables:"); - eprintln!(" MAKIMA_DAEMON_SERVER_URL=ws://localhost:8080"); - eprintln!(" MAKIMA_API_KEY=your-api-key"); - eprintln!(); - eprintln!("Or create a config file: makima-daemon.toml"); - eprintln!(); - eprintln!("Run 'makima-daemon --help' for all options."); - std::process::exit(1); - } - }; - - // Initialize logging - init_logging(&config.logging.level, &config.logging.format); - eprintln!("[2/5] Logging initialized"); - - // Initialize local database - eprintln!("[3/5] Opening local database: {}", config.local_db.path.display()); - let _local_db = LocalDb::open(&config.local_db.path)?; - eprintln!(" Database opened"); - - // Initialize worktree directories - eprintln!("[4/5] Setting up directories..."); - tokio::fs::create_dir_all(&config.worktree.base_dir).await?; - tokio::fs::create_dir_all(&config.worktree.repos_dir).await?; - tokio::fs::create_dir_all(&config.repos.home_dir).await?; - eprintln!(" Worktree base: {}", config.worktree.base_dir.display()); - eprintln!(" Repos cache: {}", config.worktree.repos_dir.display()); - eprintln!(" Home dir: {}", config.repos.home_dir.display()); - - // Auto-clone repositories if configured - if !config.repos.auto_clone.is_empty() { - eprintln!(" Auto-cloning {} repositories...", config.repos.auto_clone.len()); - for repo_entry in &config.repos.auto_clone { - if let Err(e) = auto_clone_repo(repo_entry, &config.repos.home_dir).await { - eprintln!(" WARNING: Failed to clone {}: {}", repo_entry.url(), e); - } - } - } - - // Create channels for communication - let (command_tx, mut command_rx) = mpsc::channel::<DaemonCommand>(64); - - // Get machine info - let machine_id = get_machine_id(); - let hostname = get_hostname(); - eprintln!(" Machine ID: {}", machine_id); - eprintln!(" Hostname: {}", hostname); - - // Create WebSocket client - eprintln!("[5/5] Connecting to server: {}", config.server.url); - let mut ws_client = WsClient::new( - config.server.clone(), - machine_id, - hostname, - config.process.max_concurrent_tasks as i32, - command_tx, - ); - - // Get sender for task manager - let ws_tx = ws_client.sender(); - - // Create task configuration - let task_config = TaskConfig { - max_concurrent_tasks: config.process.max_concurrent_tasks, - worktree_base_dir: config.worktree.base_dir.clone(), - env_vars: config.process.env_vars.clone(), - claude_command: config.process.claude_command.clone(), - claude_args: config.process.claude_args.clone(), - claude_pre_args: config.process.claude_pre_args.clone(), - enable_permissions: config.process.enable_permissions, - disable_verbose: config.process.disable_verbose, - }; - - // Create task manager - let task_manager = Arc::new(TaskManager::new(task_config, ws_tx.clone())); - - // Spawn command handler - let task_manager_clone = task_manager.clone(); - tokio::spawn(async move { - tracing::info!("Command handler started, waiting for commands..."); - while let Some(command) = command_rx.recv().await { - tracing::info!("Received command from channel: {:?}", command); - if let Err(e) = task_manager_clone.handle_command(command).await { - tracing::error!("Failed to handle command: {}", e); - } - } - tracing::info!("Command handler stopped"); - }); - - // Handle shutdown signals - let shutdown_signal = async { - tokio::signal::ctrl_c() - .await - .expect("Failed to install Ctrl+C handler"); - eprintln!("\nReceived shutdown signal"); - }; - - eprintln!("=== Daemon running (Ctrl+C to stop) ==="); - - // Run WebSocket client with shutdown handling - tokio::select! { - result = ws_client.run() => { - match result { - Ok(()) => eprintln!("WebSocket client exited cleanly"), - Err(DaemonError::AuthFailed(msg)) => { - eprintln!("ERROR: Authentication failed: {}", msg); - std::process::exit(1); - } - Err(e) => { - eprintln!("ERROR: WebSocket client error: {}", e); - std::process::exit(1); - } - } - } - _ = shutdown_signal => { - eprintln!("Shutting down..."); - } - } - - // Cleanup - tracing::info!("Daemon stopped"); - - Ok(()) -} - -fn init_logging(level: &str, format: &str) { - let filter = EnvFilter::try_from_default_env() - .or_else(|_| EnvFilter::try_new(level)) - .unwrap_or_else(|_| EnvFilter::new("info")); - - let subscriber = tracing_subscriber::registry().with(filter); - - if format == "json" { - subscriber.with(fmt::layer().json()).init(); - } else { - subscriber.with(fmt::layer()).init(); - } -} - -fn get_machine_id() -> String { - // Try to read machine-id from standard locations - #[cfg(target_os = "linux")] - { - if let Ok(id) = std::fs::read_to_string("/etc/machine-id") { - return id.trim().to_string(); - } - if let Ok(id) = std::fs::read_to_string("/var/lib/dbus/machine-id") { - return id.trim().to_string(); - } - } - - #[cfg(target_os = "macos")] - { - // Use IOPlatformSerialNumber - if let Ok(output) = std::process::Command::new("ioreg") - .args(["-rd1", "-c", "IOPlatformExpertDevice"]) - .output() - { - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines() { - if line.contains("IOPlatformUUID") { - if let Some(uuid) = line.split('"').nth(3) { - return uuid.to_string(); - } - } - } - } - } - - // Fallback: generate a random ID and persist it - let state_dir = dirs_next::data_local_dir() - .unwrap_or_else(|| std::path::PathBuf::from(".")) - .join("makima-daemon"); - let machine_id_file = state_dir.join("machine-id"); - - if let Ok(id) = std::fs::read_to_string(&machine_id_file) { - return id.trim().to_string(); - } - - // Generate new ID - let new_id = uuid::Uuid::new_v4().to_string(); - std::fs::create_dir_all(&state_dir).ok(); - std::fs::write(&machine_id_file, &new_id).ok(); - new_id -} - -fn get_hostname() -> String { - hostname::get() - .map(|h| h.to_string_lossy().to_string()) - .unwrap_or_else(|_| "unknown".to_string()) -} - -/// Auto-clone a repository to the home directory if it doesn't exist. -async fn auto_clone_repo( - repo_entry: &RepoEntry, - home_dir: &Path, -) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { - let dir_name = repo_entry - .dir_name() - .ok_or("Could not determine directory name from URL")?; - let target_dir = home_dir.join(&dir_name); - - // Check if already cloned - if target_dir.exists() { - eprintln!(" [skip] {} (already exists)", dir_name); - return Ok(()); - } - - let url = repo_entry.expanded_url(); - eprintln!(" [clone] {} -> {}", url, target_dir.display()); - - // Build git clone command - let mut args = vec!["clone".to_string()]; - - // Add shallow clone if requested - if repo_entry.shallow() { - args.push("--depth".to_string()); - args.push("1".to_string()); - } - - // Add branch if specified - if let Some(branch) = repo_entry.branch() { - args.push("--branch".to_string()); - args.push(branch.to_string()); - } - - args.push(url.clone()); - args.push(target_dir.to_string_lossy().to_string()); - - // Run git clone - let output = Command::new("git") - .args(&args) - .output() - .await?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("git clone failed: {}", stderr).into()); - } - - eprintln!(" [done] {}", dir_name); - Ok(()) -} - -/// dirs_next minimal replacement -mod dirs_next { - use std::path::PathBuf; - - pub fn data_local_dir() -> Option<PathBuf> { - #[cfg(target_os = "macos")] - { - std::env::var("HOME") - .ok() - .map(|h| PathBuf::from(h).join("Library").join("Application Support")) - } - #[cfg(target_os = "linux")] - { - std::env::var("XDG_DATA_HOME") - .ok() - .map(PathBuf::from) - .or_else(|| { - std::env::var("HOME") - .ok() - .map(|h| PathBuf::from(h).join(".local").join("share")) - }) - } - #[cfg(target_os = "windows")] - { - std::env::var("LOCALAPPDATA").ok().map(PathBuf::from) - } - #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] - { - None - } - } -} |
