use crate::app::CliApp;
use crate::cli::UpgradeArgs;
use anyhow::Result;
use client_core::{
api::ApiClient, config::AppConfig, upgrade_strategy::UpgradeStrategy, utils::archive,
};
use std::{
fs,
path::{Path, PathBuf},
};
use tracing::{error, info};
pub fn create_version_download_dir(
download_dir: PathBuf,
version: &str,
download_type: &str,
) -> Result<PathBuf> {
let dir = download_dir.join(version).join(download_type);
fs::create_dir_all(&dir)?;
Ok(dir)
}
async fn handle_service_download(
app: &mut CliApp,
url: &str,
target_version: &client_core::version::Version,
download_dir: PathBuf,
version_str: &str,
download_type: &str,
) -> Result<()> {
let version_download_dir =
create_version_download_dir(download_dir, version_str, download_type)?;
let temp_path = version_download_dir.join("temp_download");
info!(
" Downloading to temp file: {path}",
path = temp_path.display()
);
let download_result = app
.api_client
.download_service_update_optimized(&temp_path, Some(version_str), url)
.await;
match download_result {
Ok(_) => {
let format = archive::detect_format_by_magic(&temp_path)?;
info!(
" Detected file format: {format}",
format = format!("{:?}", format)
);
let arch = client_core::architecture::Architecture::detect();
let arch_str = match arch {
client_core::architecture::Architecture::Aarch64 => "aarch64",
client_core::architecture::Architecture::X86_64 => "x86_64",
_ => "unknown",
};
let filename = archive::generate_docker_filename(arch_str, format);
info!(" Renaming to: {filename}", filename = filename);
let final_path = version_download_dir.join(&filename);
std::fs::rename(&temp_path, &final_path)?;
info!("✅ Service package ready!");
info!(" File location: {path}", path = final_path.display());
info!(
" Download version: {version}",
version = target_version.to_string()
);
info!(
" Current deployed version: {version}",
version = app.config.get_docker_versions()
);
info!("📝 Next step: Run 'nuwax-cli docker-service deploy' to deploy services");
Ok(())
}
Err(e) => {
error!("❌ Operation failed: {error}", error = e.to_string());
info!("💡 Please check network connection or try again later");
Err(e)
}
}
}
pub async fn run_upgrade(app: &mut CliApp, args: UpgradeArgs) -> Result<UpgradeStrategy> {
if args.check {
info!("🔍 Checking Docker service upgrade versions");
info!("========================");
} else {
info!("📦 Downloading Docker service files");
info!("=====================");
}
let docker_compose_path = std::path::Path::new(&app.config.docker.compose_file);
let is_first_time = !docker_compose_path.exists();
if is_first_time {
info!("🆕 Detected first deployment");
info!(" Will download full Docker service package");
} else if args.force {
info!("🔧 Force redownload mode");
}
let current_version_str = app.config.get_docker_versions();
let upgrade_strategy = app.upgrade_manager.check_for_updates(args.force).await?;
let download_dir: PathBuf = app.config.get_download_dir();
match &upgrade_strategy {
UpgradeStrategy::FullUpgrade {
url,
hash: _,
signature: _,
target_version,
download_type,
} => {
info!("🔄 Full upgrade");
info!(" Target version: {version}", version = target_version);
info!(" Download path: {path}", path = url);
info!(
" Current version: {version}",
version = current_version_str
);
info!(" Latest version: {version}", version = target_version);
if args.check {
info!("🔍 Check upgrade version done");
return Ok(upgrade_strategy);
}
let version_str = target_version.base_version_string();
let download_type_str = download_type.to_string();
handle_service_download(
app,
url,
target_version,
download_dir,
&version_str,
&download_type_str,
)
.await?;
}
UpgradeStrategy::PatchUpgrade {
patch_info,
target_version,
download_type: _,
} => {
info!("🔄 Incremental upgrade");
info!(
" Current version: {version}",
version = current_version_str
);
info!(" Latest version: {version}", version = target_version);
if args.check {
info!("🔍 Check upgrade version done");
return Ok(upgrade_strategy);
}
let base_version = target_version.base_version_string();
let version_str = target_version.to_string();
handle_service_download(
app,
&patch_info.url,
target_version,
download_dir,
&base_version,
&version_str,
)
.await?;
}
UpgradeStrategy::NoUpgrade { target_version } => {
info!(
" Current version: {version}",
version = current_version_str
);
info!(" Latest version: {version}", version = target_version);
info!("✅ Current version is latest");
}
}
Ok(upgrade_strategy)
}
pub async fn run_download(config_path: Option<&Path>) -> Result<()> {
let config = match config_path {
Some(path) if path.exists() => AppConfig::load_from_file(path)?,
Some(_) | None => match AppConfig::find_and_load_config() {
Ok(cfg) => cfg,
Err(_) => {
info!(" No config file found, using default configuration");
AppConfig::default()
}
},
};
run_download_with_config(&config).await
}
pub async fn run_download_with_config(config: &AppConfig) -> Result<()> {
info!("📦 Downloading latest Docker service package...");
info!("=====================");
let api_client = ApiClient::new(Some("offline-download".to_string()), None);
let manifest = api_client.get_enhanced_service_manifest().await?;
let latest_version = manifest.version.clone();
info!(
" Latest version: {version}",
version = latest_version.to_string()
);
let current_version = config.get_docker_versions();
if !current_version.is_empty() {
info!(
" Current deployed version: {version}",
version = current_version
);
} else {
info!(" No current deployment detected");
}
let arch = client_core::architecture::Architecture::detect();
let arch_str = match arch {
client_core::architecture::Architecture::Aarch64 => "aarch64",
client_core::architecture::Architecture::X86_64 => "x86_64",
_ => {
return Err(anyhow::anyhow!("Unsupported architecture"));
}
};
info!(" Target architecture: {arch}", arch = arch_str);
let download_url = if let Some(ref platforms) = manifest.platforms {
let platform_info = match arch_str {
"x86_64" => platforms.x86_64.as_ref(),
"aarch64" => platforms.aarch64.as_ref(),
_ => None,
};
platform_info
.map(|p| p.url.clone())
.ok_or_else(|| anyhow::anyhow!("No download URL for architecture: {}", arch_str))?
} else {
return Err(anyhow::anyhow!(
"Manifest does not contain platform information"
));
};
info!(" Download URL: {url}", url = download_url);
let download_dir = config.get_download_dir();
let version_str = latest_version.base_version_string();
let version_download_dir = create_version_download_dir(download_dir, &version_str, "full")?;
let temp_path = version_download_dir.join("temp_download");
info!(
" Downloading to temp file: {path}",
path = temp_path.display()
);
let download_result = api_client
.download_service_update_optimized(&temp_path, Some(version_str.as_str()), &download_url)
.await;
match download_result {
Ok(_) => {
let format = archive::detect_format_by_magic(&temp_path)?;
info!(
" Detected file format: {format}",
format = format!("{:?}", format)
);
let filename = archive::generate_docker_filename(arch_str, format);
info!(" Renaming to: {filename}", filename = filename);
let final_path = version_download_dir.join(&filename);
std::fs::rename(&temp_path, &final_path)?;
info!("✅ Service package downloaded successfully!");
info!(" File location: {path}", path = final_path.display());
info!("💡 Copy this file to offline server for deployment");
}
Err(e) => {
error!("❌ Download failed: {error}", error = e.to_string());
info!("💡 Please check network connection or try again later");
return Err(e);
}
}
Ok(())
}