volli 0.1.10

CLI frontend for volli
use crate::AgentOpts;
use crate::commands::utils::{default_profile, show_profile};
use crate::namegen;
use daemonize::Daemonize;
use tracing::{error, info};
use volli_agent::{self, AgentConfig, Protocol as AgentProtocol, run as run_agent};
use volli_core::BootstrapSecret;

pub async fn run(mut profile: String, opts: AgentOpts) {
    if opts.daemon {
        if let Err(e) = Daemonize::new().start() {
            error!("failed to daemonize: {}", e);
            return;
        }
    }
    if let Some(ref cfg_dir) = opts.config_dir {
        unsafe {
            std::env::set_var("VOLLI_CONFIG_DIR", cfg_dir);
        }
    }
    let (host, port_opt) = if let Some((h, p)) = opts.connect.split_once(':') {
        (h.to_string(), Some(p))
    } else {
        (opts.connect.clone(), None)
    };
    let protocol = opts.protocol.as_deref();
    let mut cfg = AgentConfig::default();

    if opts.join.is_some() && profile == default_profile() && volli_agent::profile_exists(&profile)
    {
        profile = namegen::random_profile();
        println!("Using profile '{profile}'");
    }
    let state_path = volli_agent::agent_dir(Some(&profile)).join("agent_state");

    if opts.join.is_some() && state_path.exists() && !opts.force {
        println!("Profile {profile} already exists. Overwrite? [y/N]");
        let mut c = String::new();
        std::io::stdin().read_line(&mut c).ok();
        if c.trim() != "y" {
            println!("aborted");
            return;
        }
    }
    if opts.join.is_none() && !state_path.exists() {
        eprintln!("profile '{profile}' not found");
        return;
    }

    let join_provided = opts.join.is_some();
    let mut secret_opt = if let Some(secret) = opts.join {
        Some(secret)
    } else {
        volli_agent::load_state(&profile).ok().flatten()
    };
    let mut save_secret: Option<String> = None;
    if let Some(secret) = secret_opt.take() {
        let mut bs = BootstrapSecret::decode(&secret).expect("parse secret");
        let mut changed = false;
        if let Some(jh) = opts.join_host.clone() {
            bs.host = jh;
            changed = true;
        } else if opts.connect != "localhost" {
            bs.host = host.clone();
            changed = true;
        }
        if let Some(p) = port_opt.and_then(|p| p.parse::<u16>().ok()) {
            bs.quic_port = p;
            bs.tcp_port = p;
            changed = true;
        }
        let new_secret = bs.encode().unwrap();
        if let Err(e) = volli_agent::apply_secret(&mut cfg, &profile, &new_secret) {
            error!("invalid secret: {}", e);
            return;
        }
        if changed {
            save_secret = Some(new_secret);
        } else {
            save_secret = Some(secret);
        }
    } else {
        cfg.host = host;
        if let Some(p) = port_opt.and_then(|p| p.parse::<u16>().ok()) {
            cfg.quic_port = p;
            cfg.tcp_port = p;
        }
    }
    cfg.protocol = protocol.map(|p| {
        if p.eq_ignore_ascii_case("tcp") {
            AgentProtocol::Tcp
        } else {
            AgentProtocol::Quic
        }
    });
    info!(
        "starting agent connecting to {} tcp:{} quic:{}",
        cfg.host, cfg.tcp_port, cfg.quic_port
    );
    let save_profile = join_provided || opts.update_profile;
    if join_provided {
        let cb = if save_profile {
            let prof = profile.clone();
            let sec_opt = save_secret.clone();
            Some(Box::new(move || {
                if let Some(sec) = sec_opt {
                    volli_agent::save_state(&prof, &sec).ok();
                }
                if prof == default_profile() {
                    println!(
                        "Saved agent profile '{prof}'. Launch with:\n  volli [--profile {prof}] agent",
                    );
                } else {
                    println!(
                        "Saved agent profile '{prof}'. Launch with:\n  volli --profile {prof} agent",
                    );
                }
                show_profile(&prof, false, true);
            }) as Box<dyn FnOnce() + Send>)
        } else {
            None
        };
        if let Err(e) = run_agent(cfg, profile.clone(), cb).await {
            error!("agent error: {}", e);
        }
    } else {
        if opts.update_profile {
            if let Some(sec) = save_secret {
                volli_agent::save_state(&profile, &sec).ok();
            }
        }
        if let Err(e) = run_agent(cfg, profile.clone(), None).await {
            error!("agent error: {}", e);
        }
    }
}