flow-iron 0.3.8

Infrastructure-as-code CLI — deploy Docker Compose apps with Caddy reverse proxy and Cloudflare DNS
Documentation
use anyhow::{Result, bail};

use crate::config::Fleet;
use crate::ssh::SshPool;
use crate::ui;

pub async fn run(fleet: &Fleet, app_name: &str, server_filter: Option<&str>) -> Result<()> {
    let app = fleet
        .apps
        .get(app_name)
        .ok_or_else(|| anyhow::anyhow!("Unknown app: {app_name}"))?;

    let target_servers: Vec<&str> = if let Some(server) = server_filter {
        if !app.servers.contains(&server.to_string()) {
            bail!("App '{app_name}' is not assigned to server '{server}'");
        }
        vec![server]
    } else {
        app.servers.iter().map(String::as_str).collect()
    };

    let servers_to_connect: std::collections::HashMap<_, _> = fleet
        .servers
        .iter()
        .filter(|(name, _)| target_servers.contains(&name.as_str()))
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect();

    let sp = ui::spinner("Connecting to servers...");
    let pool = SshPool::connect(&servers_to_connect).await?;
    sp.finish_and_clear();

    let app_dir = format!("/opt/flow/{}", app.name);

    for server_name in &target_servers {
        let sp = ui::spinner(&format!("{server_name} → restarting {app_name}..."));
        pool.exec(
            server_name,
            &format!("cd {app_dir} && docker compose restart"),
        )
        .await?;
        sp.finish_and_clear();
        ui::success(&format!("{server_name}{app_name} restarted"));
    }

    pool.close().await?;
    Ok(())
}