summaryrefslogtreecommitdiff
path: root/makima/daemon/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/daemon/src/main.rs')
-rw-r--r--makima/daemon/src/main.rs313
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
- }
- }
-}