volli 0.1.10

CLI frontend for volli
use crate::ServeOpts;
use crate::commands::utils::{default_profile, show_profile};
use crate::namegen;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD_NO_PAD;
use daemonize::Daemonize;
use sha2::{Digest, Sha256};
use std::env;
use tracing::{error, info};
use volli_core::BootstrapSecret;
use volli_server::{self, ServerConfigOpts, run as run_server};

pub async fn run(mut profile: String, opts: ServeOpts) {
    if opts.join.is_some() && profile == default_profile() && volli_server::profile_exists(&profile)
    {
        profile = namegen::random_profile();
        println!("Using profile '{profile}'");
    }

    if let Some(ref cfg_dir) = opts.config_dir {
        unsafe {
            std::env::set_var("VOLLI_CONFIG_DIR", cfg_dir);
        }
    }
    let mut dir = opts
        .config_dir
        .clone()
        .map(std::path::PathBuf::from)
        .unwrap_or_else(volli_core::config_dir);
    dir.push("profiles");
    dir.push("coordinator");
    dir.push(&profile);
    let mut new_profile = !dir.join("coord_sk").exists();
    if opts.bootstrap {
        if dir.join("coord_sk").exists() && !opts.force {
            println!("Profile {profile} already exists. Overwrite? [y/N]");
            let mut confirm = String::new();
            std::io::stdin().read_line(&mut confirm).ok();
            if confirm.trim() != "y" {
                println!("aborted");
                return;
            }
        }
        std::fs::remove_file(dir.join("coord_sk")).ok();
        std::fs::remove_file(dir.join("coord_pk")).ok();
        std::fs::remove_file(dir.join("tls_cert.der")).ok();
        std::fs::remove_file(dir.join("tls_key.der")).ok();
        new_profile = true;
    } else if opts.join.is_some() && dir.join("coord_sk").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;
        }
    } else if opts.join.is_none() && !dir.join("coord_sk").exists() {
        println!("Creating new coordinator profile '{profile}'");
        new_profile = true;
    }

    if new_profile && opts.join.is_none() && !opts.bootstrap {
        new_profile = true;
    }

    if opts.daemon {
        if let Err(e) = Daemonize::new().start() {
            error!("failed to daemonize: {}", e);
            return;
        }
    }
    let tcp_port = opts
        .tcp_port
        .or_else(|| volli_server::load_tcp_port(&profile).ok().flatten())
        .or_else(|| env::var("VOLLI_TCP_PORT").ok().and_then(|v| v.parse().ok()))
        .unwrap_or(volli_core::DEFAULT_TCP_PORT);
    let quic_port = opts
        .quic_port
        .or_else(|| volli_server::load_quic_port(&profile).ok().flatten())
        .or_else(|| {
            env::var("VOLLI_QUIC_PORT")
                .ok()
                .and_then(|v| v.parse().ok())
        })
        .unwrap_or(volli_core::DEFAULT_QUIC_PORT);
    let mut bind = opts.bind.clone();
    if bind.is_none() {
        bind = volli_server::load_bind_host(&profile).ok().flatten();
    }
    let bind = bind.unwrap_or_else(|| "127.0.0.1".into());
    let save_addrs = opts.update_profile || new_profile;

    let mut advertise = opts.advertise_host.clone();
    if advertise.is_none() {
        advertise = volli_server::load_profile_host(&profile).ok().flatten();
    }
    let advertise = advertise.unwrap_or_else(|| bind.clone());

    let agent_whitelist = volli_server::load_agent_whitelist(&profile).ok().flatten();
    let coord_whitelist = volli_server::load_coord_whitelist(&profile).ok().flatten();

    let mut cfg = ServerConfigOpts {
        advertise_host: advertise.clone(),
        bind: bind.clone(),
        tcp_port,
        quic_port,
        cert: opts.cert,
        key: opts.key,
        token: None,
        secret_dir: Some(dir.to_string_lossy().to_string()),
        profile: profile.clone(),
        max_connections: 1000,
        agent_whitelist,
        coord_whitelist,
        join_hosts: Vec::new(),
    };
    let join_provided = opts.join.is_some();
    let join_host = opts.join_host.clone();
    let join_tcp_port = opts.join_tcp_port;
    let join_quic_port = opts.join_quic_port;
    let mut join_entries = volli_server::load_join_hosts(&profile).unwrap_or_default();
    let join_secret_opt = if join_provided {
        opts.join.clone()
    } else {
        None
    };

    if let Some(secret) = opts.join.clone() {
        if let Ok(bs) = BootstrapSecret::decode(&secret) {
            let fp = hex::encode(Sha256::digest(&bs.cert));
            let entry = volli_server::JoinHostEntry {
                coord_id: None,
                host: join_host.clone().unwrap_or(bs.host.clone()),
                tcp_port: join_tcp_port.or(Some(bs.tcp_port)),
                quic_port: join_quic_port.or(Some(bs.quic_port)),
                token: Some(volli_core::token::encode_token(&bs.token).unwrap()),
                cert: Some(STANDARD_NO_PAD.encode(bs.cert)),
                fingerprint: Some(fp),
                last_ok: None,
                last_fail: None,
            };
            join_entries.retain(|h| h.host != entry.host);
            join_entries.push(entry);
        } else {
            error!("invalid join secret");
            return;
        }
    }
    cfg.join_hosts = join_entries.clone();
    let save_profile = join_provided || opts.update_profile || new_profile;
    info!(
        "starting server bind={} tcp:{} quic:{}",
        bind, tcp_port, quic_port
    );
    let cb_opt: Option<Box<dyn FnOnce() + Send>> = if save_profile {
        let prof = profile.clone();
        let host_opt = join_host.clone();
        let secret_opt = join_secret_opt.clone();
        let jt = join_tcp_port;
        let jq = join_quic_port;
        let tcp_p = tcp_port;
        let quic_p = quic_port;
        let adv = advertise.clone();
        let bind_addr = bind.clone();
        let fresh = new_profile;
        let save_addr = save_addrs;
        let join = join_provided;
        Some(Box::new(move || {
            if fresh {
                if join {
                    if let Some(sec) = secret_opt.as_deref() {
                        if let Ok(bs) = BootstrapSecret::decode(sec) {
                            let host = host_opt.as_deref().unwrap_or(&bs.host).to_string();
                            let fp = hex::encode(Sha256::digest(&bs.cert));
                            let entry = volli_server::JoinHostEntry {
                                coord_id: None,
                                host,
                                tcp_port: jt.or(Some(bs.tcp_port)),
                                quic_port: jq.or(Some(bs.quic_port)),
                                token: Some(volli_core::token::encode_token(&bs.token).unwrap()),
                                cert: Some(STANDARD_NO_PAD.encode(bs.cert)),
                                fingerprint: Some(fp),
                                last_ok: None,
                                last_fail: None,
                            };
                            volli_server::add_join_host(&prof, entry).ok();
                        }
                    }
                } else {
                    volli_server::save_join_hosts(&prof, &[]).ok();
                }
            } else if let Some(sec) = secret_opt.as_deref() {
                if let Ok(bs) = BootstrapSecret::decode(sec) {
                    let host = host_opt.as_deref().unwrap_or(&bs.host).to_string();
                    let fp = hex::encode(Sha256::digest(&bs.cert));
                    let entry = volli_server::JoinHostEntry {
                        coord_id: None,
                        host,
                        tcp_port: jt.or(Some(bs.tcp_port)),
                        quic_port: jq.or(Some(bs.quic_port)),
                        token: Some(volli_core::token::encode_token(&bs.token).unwrap()),
                        cert: Some(STANDARD_NO_PAD.encode(bs.cert)),
                        fingerprint: Some(fp),
                        last_ok: None,
                        last_fail: None,
                    };
                    volli_server::add_join_host(&prof, entry).ok();
                }
            }
            if save_addr {
                volli_server::save_bind_host(&prof, &bind_addr).ok();
                volli_server::save_profile_host(&prof, &adv).ok();
            }
            volli_server::save_tcp_port(&prof, tcp_p).ok();
            volli_server::save_quic_port(&prof, quic_p).ok();
            if prof == default_profile() {
                println!(
                    "Saved coordinator profile '{prof}'. Launch with:\n volli [--profile {prof}] serve",
                );
            } else {
                println!(
                    "Saved coordinator profile '{prof}'. Launch with:\n volli --profile {prof} serve",
                );
            }
            show_profile(&prof, true, false);
        }))
    } else {
        None
    };
    if let Err(e) = run_server(cfg, cb_opt, join_provided).await {
        error!("server error: {}", e);
    }
}