1use crate::config::ClientConfig;
2use crate::shell::get_cache_dir;
3use anyhow::{Context, Result};
4use cloudpub_common::protocol::message::Message;
5use parking_lot::RwLock;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9use tokio::sync::mpsc;
10
11#[cfg(unix)]
13const UNIX_SERVICE_UPDATE_SCRIPT: &str = include_str!("scripts/unix_service_update.sh");
14#[cfg(unix)]
15const UNIX_CLIENT_UPDATE_SCRIPT: &str = include_str!("scripts/unix_client_update.sh");
16#[cfg(windows)]
17const WINDOWS_SERVICE_UPDATE_SCRIPT: &str = include_str!("scripts/windows_service_update.bat");
18#[cfg(windows)]
19const WINDOWS_CLIENT_UPDATE_SCRIPT: &str = include_str!("scripts/windows_client_update.bat");
20#[cfg(windows)]
21const WINDOWS_MSI_INSTALL_SCRIPT: &str = include_str!("scripts/windows_msi_install.bat");
22#[cfg(target_os = "macos")]
23const MACOS_DMG_INSTALL_SCRIPT: &str = include_str!("scripts/macos_dmg_install.sh");
24#[cfg(target_os = "linux")]
25const LINUX_DEB_INSTALL_SCRIPT: &str = include_str!("scripts/linux_deb_install.sh");
26#[cfg(target_os = "linux")]
27const LINUX_RPM_INSTALL_SCRIPT: &str = include_str!("scripts/linux_rpm_install.sh");
28#[cfg(target_os = "linux")]
29const SYSTEMD_ONESHOT_TEMPLATE: &str = include_str!("scripts/systemd_oneshot.service");
30#[cfg(target_os = "macos")]
31const LAUNCHD_ONESHOT_TEMPLATE: &str = include_str!("scripts/launchd_oneshot.plist");
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct PlatformDownloads {
35 pub gui: Option<String>,
36 pub cli: Option<String>,
37 pub rpm: Option<String>,
38 pub deb: Option<String>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct OsDownloads {
43 pub name: String,
44 pub platforms: HashMap<String, PlatformDownloads>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct DownloadConfig {
49 pub macos: OsDownloads,
50 pub windows: OsDownloads,
51 pub linux: OsDownloads,
52}
53
54impl DownloadConfig {
55 pub fn new(base_url: &str, version: &str, branch: &str) -> Self {
56 #[cfg(target_os = "macos")]
57 let macos_platforms = {
58 let mut platforms = HashMap::new();
59 platforms.insert(
60 "aarch64".to_string(),
61 PlatformDownloads {
62 gui: Some(format!(
63 "{base_url}cloudpub-{version}-{branch}-macos-aarch64.dmg"
64 )),
65 cli: Some(format!(
66 "{base_url}clo-{version}-{branch}-macos-aarch64.tar.gz"
67 )),
68 rpm: None,
69 deb: None,
70 },
71 );
72 platforms.insert(
73 "x86_64".to_string(),
74 PlatformDownloads {
75 gui: Some(format!(
76 "{base_url}cloudpub-{version}-{branch}-macos-x86_64.dmg"
77 )),
78 cli: Some(format!(
79 "{base_url}clo-{version}-{branch}-macos-x86_64.tar.gz"
80 )),
81 rpm: None,
82 deb: None,
83 },
84 );
85 platforms
86 };
87
88 #[cfg(target_os = "windows")]
89 let windows_platforms = {
90 let mut platforms = HashMap::new();
91 platforms.insert(
92 "x86_64".to_string(),
93 PlatformDownloads {
94 gui: Some(format!(
95 "{base_url}cloudpub-{version}-{branch}-windows-x86_64.msi"
96 )),
97 cli: Some(format!(
98 "{base_url}clo-{version}-{branch}-windows-x86_64.zip"
99 )),
100 rpm: None,
101 deb: None,
102 },
103 );
104 platforms
105 };
106
107 #[cfg(target_os = "linux")]
108 let linux_platforms = {
109 let mut platforms = HashMap::new();
110 platforms.insert(
111 "arm".to_string(),
112 PlatformDownloads {
113 gui: None,
114 cli: Some(format!("{base_url}clo-{version}-{branch}-linux-arm.tar.gz")),
115 rpm: None,
116 deb: None,
117 },
118 );
119 platforms.insert(
120 "armv5te".to_string(),
121 PlatformDownloads {
122 gui: None,
123 cli: Some(format!(
124 "{base_url}clo-{version}-{branch}-linux-armv5te.tar.gz"
125 )),
126 rpm: None,
127 deb: None,
128 },
129 );
130 platforms.insert(
131 "aarch64".to_string(),
132 PlatformDownloads {
133 gui: None,
134 cli: Some(format!(
135 "{base_url}clo-{version}-{branch}-linux-aarch64.tar.gz"
136 )),
137 rpm: None,
138 deb: None,
139 },
140 );
141 platforms.insert(
142 "mips".to_string(),
143 PlatformDownloads {
144 gui: None,
145 cli: Some(format!(
146 "{base_url}clo-{version}-{branch}-linux-mips.tar.gz"
147 )),
148 rpm: None,
149 deb: None,
150 },
151 );
152 platforms.insert(
153 "mipsel".to_string(),
154 PlatformDownloads {
155 gui: None,
156 cli: Some(format!(
157 "{base_url}clo-{version}-{branch}-linux-mipsel.tar.gz"
158 )),
159 rpm: None,
160 deb: None,
161 },
162 );
163 platforms.insert(
164 "i686".to_string(),
165 PlatformDownloads {
166 gui: None,
167 cli: Some(format!(
168 "{base_url}clo-{version}-{branch}-linux-i686.tar.gz"
169 )),
170 rpm: None,
171 deb: None,
172 },
173 );
174 platforms.insert(
175 "x86_64".to_string(),
176 PlatformDownloads {
177 gui: None,
178 cli: Some(format!(
179 "{base_url}clo-{version}-{branch}-linux-x86_64.tar.gz"
180 )),
181 rpm: Some(format!(
182 "{base_url}cloudpub-{version}-{branch}-linux-x86_64.rpm"
183 )),
184 deb: Some(format!(
185 "{base_url}cloudpub-{version}-{branch}-linux-x86_64.deb"
186 )),
187 },
188 );
189 platforms
190 };
191
192 Self {
193 #[cfg(target_os = "macos")]
194 macos: OsDownloads {
195 name: "macOS".to_string(),
196 platforms: macos_platforms,
197 },
198 #[cfg(not(target_os = "macos"))]
199 macos: OsDownloads {
200 name: "macOS".to_string(),
201 platforms: HashMap::new(),
202 },
203 #[cfg(target_os = "windows")]
204 windows: OsDownloads {
205 name: "Windows".to_string(),
206 platforms: windows_platforms,
207 },
208 #[cfg(not(target_os = "windows"))]
209 windows: OsDownloads {
210 name: "Windows".to_string(),
211 platforms: HashMap::new(),
212 },
213 #[cfg(target_os = "linux")]
214 linux: OsDownloads {
215 name: "Linux".to_string(),
216 platforms: linux_platforms,
217 },
218 #[cfg(not(target_os = "linux"))]
219 linux: OsDownloads {
220 name: "Linux".to_string(),
221 platforms: HashMap::new(),
222 },
223 }
224 }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub enum DownloadType {
229 Gui,
230 Cli,
231 Rpm,
232 Deb,
233}
234
235pub fn get_current_os() -> String {
236 #[cfg(target_os = "macos")]
237 return "macos".to_string();
238 #[cfg(target_os = "windows")]
239 return "windows".to_string();
240 #[cfg(target_os = "linux")]
241 return "linux".to_string();
242 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
243 return std::env::consts::OS.to_string();
244}
245
246pub fn get_current_arch() -> String {
247 #[cfg(target_arch = "x86_64")]
248 return "x86_64".to_string();
249 #[cfg(target_arch = "aarch64")]
250 return "aarch64".to_string();
251 #[cfg(target_arch = "arm")]
252 return "arm".to_string();
253 #[cfg(target_arch = "x86")]
254 return "i686".to_string();
255 #[cfg(not(any(
256 target_arch = "x86_64",
257 target_arch = "aarch64",
258 target_arch = "arm",
259 target_arch = "x86"
260 )))]
261 return std::env::consts::ARCH.to_string();
262}
263
264#[cfg(target_os = "linux")]
265pub fn get_linux_package_type() -> Result<String> {
266 if std::process::Command::new("which")
268 .arg("dpkg")
269 .output()
270 .map(|output| output.status.success())
271 .unwrap_or(false)
272 {
273 return Ok("deb".to_string());
274 }
275
276 if std::process::Command::new("which")
278 .arg("rpm")
279 .output()
280 .map(|output| output.status.success())
281 .unwrap_or(false)
282 {
283 return Ok("rpm".to_string());
284 }
285
286 Err(anyhow::anyhow!("Could not detect package type"))
287}
288
289pub fn get_download_url(
290 base_url: &str,
291 download_type: &DownloadType,
292 version: &str,
293 branch: &str,
294) -> Result<String> {
295 let base_url = format!("{}download/{}/", base_url, branch);
296 let config = DownloadConfig::new(&base_url, version, branch);
297 let os = get_current_os();
298 let arch = get_current_arch();
299
300 let os_downloads = match os.as_str() {
301 "macos" => &config.macos,
302 "windows" => &config.windows,
303 "linux" => &config.linux,
304 _ => {
305 return Err(anyhow::anyhow!("Unsupported OS: {}", os))
306 .context("Failed to get OS downloads")
307 }
308 };
309
310 let platform_downloads = os_downloads
311 .platforms
312 .get(&arch)
313 .with_context(|| format!("Unsupported architecture {} for OS {}", arch, os))?;
314
315 let download_url = match download_type {
316 DownloadType::Gui => platform_downloads
317 .gui
318 .as_ref()
319 .context("GUI download not available for this platform")?,
320 DownloadType::Cli => platform_downloads
321 .cli
322 .as_ref()
323 .context("CLI download not available for this platform")?,
324 DownloadType::Rpm => platform_downloads
325 .rpm
326 .as_ref()
327 .context("RPM download not available for this platform")?,
328 DownloadType::Deb => platform_downloads
329 .deb
330 .as_ref()
331 .context("DEB download not available for this platform")?,
332 };
333
334 Ok(download_url.clone())
335}
336
337fn run_script(
339 script_name: &str,
340 script_template: &str,
341 replacements: Vec<(&str, String)>,
342 is_service: bool,
343) -> Result<()> {
344 use anyhow::Context;
345 use tracing::{debug, info};
346
347 let cache_dir = get_cache_dir("updates")?;
349 std::fs::create_dir_all(&cache_dir).context("Failed to create updates cache directory")?;
350
351 let timestamp = format!(
352 "{}_{}",
353 std::process::id(),
354 std::time::SystemTime::now()
355 .duration_since(std::time::UNIX_EPOCH)
356 .unwrap_or_default()
357 .as_secs()
358 );
359
360 #[cfg(windows)]
361 let script_path = cache_dir.join(format!("{}_{}.bat", script_name, timestamp));
362 #[cfg(unix)]
363 let script_path = cache_dir.join(format!("{}_{}.sh", script_name, timestamp));
364
365 debug!("Creating script: {}", script_path.display());
366
367 let mut script_content = script_template.to_string();
369 for (placeholder, value) in replacements {
370 script_content = script_content.replace(placeholder, &value);
371 }
372
373 std::fs::write(&script_path, script_content).context("Failed to create script")?;
374
375 info!("Script created at: {}", script_path.display());
376
377 #[cfg(unix)]
379 {
380 use std::os::unix::fs::PermissionsExt;
381 let mut perms = std::fs::metadata(&script_path)?.permissions();
382 perms.set_mode(0o755);
383 std::fs::set_permissions(&script_path, perms)?;
384 }
385
386 #[cfg(windows)]
388 {
389 use std::os::windows::process::CommandExt;
390 std::process::Command::new("cmd")
391 .args(&["/C", &script_path.to_string_lossy()])
392 .creation_flags(0x08000000) .spawn()
394 .context("Failed to execute script")?;
395 }
396
397 #[cfg(target_os = "linux")]
398 {
399 if is_service {
400 let service_name = format!("cloudpub-upgrade-{}.service", timestamp);
402 let service_path = format!("/etc/systemd/system/{}", service_name);
403
404 debug!("Creating systemd one-shot service: {}", service_name);
405
406 let service_content = SYSTEMD_ONESHOT_TEMPLATE
407 .replace("{TIMESTAMP}", ×tamp)
408 .replace("{SCRIPT_PATH}", &script_path.display().to_string());
409
410 std::fs::write(&service_path, service_content)
411 .context("Failed to create systemd one-shot service")?;
412
413 std::process::Command::new("systemctl")
414 .arg("daemon-reload")
415 .output()
416 .context("Failed to reload systemd")?;
417
418 std::process::Command::new("systemctl")
419 .arg("start")
420 .arg(&service_name)
421 .spawn()
422 .context("Failed to start upgrade service")?;
423
424 info!(
425 "Upgrade scheduled via systemd one-shot service: {}",
426 service_name
427 );
428 } else {
429 debug!("Executing script with nohup: {}", script_path.display());
431
432 std::process::Command::new("nohup")
433 .arg("/bin/bash")
434 .arg(&script_path)
435 .stdin(std::process::Stdio::null())
436 .stdout(std::process::Stdio::null())
437 .stderr(std::process::Stdio::null())
438 .spawn()
439 .context("Failed to spawn script with nohup")?;
440
441 info!("Script spawned with nohup");
442 }
443 }
444
445 #[cfg(target_os = "macos")]
446 {
447 if is_service {
448 let plist_name = format!("com.cloudpub.upgrade.{}.plist", timestamp);
450 let plist_path = format!("/Library/LaunchDaemons/{}", plist_name);
451
452 debug!("Creating launchd one-shot job: {}", plist_name);
453
454 let plist_content = LAUNCHD_ONESHOT_TEMPLATE
455 .replace("{TIMESTAMP}", ×tamp)
456 .replace("{SCRIPT_PATH}", &script_path.display().to_string());
457
458 std::fs::write(&plist_path, plist_content)
459 .context("Failed to create launchd one-shot plist")?;
460
461 std::process::Command::new("chmod")
462 .args(["644", &plist_path])
463 .output()
464 .context("Failed to set plist permissions")?;
465
466 std::process::Command::new("launchctl")
467 .args(["load", &plist_path])
468 .spawn()
469 .context("Failed to load upgrade job")?;
470
471 info!("Upgrade scheduled via launchd one-shot job: {}", plist_name);
472 } else {
473 debug!("Executing script with nohup: {}", script_path.display());
475
476 std::process::Command::new("nohup")
477 .arg("/bin/bash")
478 .arg(&script_path)
479 .stdin(std::process::Stdio::null())
480 .stdout(std::process::Stdio::null())
481 .stderr(std::process::Stdio::null())
482 .spawn()
483 .context("Failed to spawn script with nohup")?;
484
485 info!("Script spawned with nohup");
486 }
487 }
488
489 info!("Script execution initiated, exiting current process");
490 std::process::exit(0);
491}
492
493fn run_upgrade_script(
495 script_template: &str,
496 new_exe_path: &std::path::Path,
497 current_args: Vec<String>,
498 is_service: bool,
499) -> Result<()> {
500 use tracing::debug;
501
502 let current_exe = std::env::current_exe()?;
503 let cache_dir = get_cache_dir("updates")?;
504 std::fs::create_dir_all(&cache_dir).context("Failed to create updates cache directory")?;
505
506 let timestamp = format!(
507 "{}_{}",
508 std::process::id(),
509 std::time::SystemTime::now()
510 .duration_since(std::time::UNIX_EPOCH)
511 .unwrap_or_default()
512 .as_secs()
513 );
514
515 let log_file = cache_dir.join(format!("cloudpub_update_{}.log", timestamp));
516
517 debug!("Starting update process");
518 debug!("Current executable: {}", current_exe.display());
519 debug!("New executable: {}", new_exe_path.display());
520 debug!("Current args: {:?}", current_args);
521 debug!("Log file location: {}", log_file.display());
522 debug!("Is service: {}", is_service);
523
524 let config_path = if is_service {
527 current_args
528 .windows(2)
529 .find(|w| w[0] == "--conf" || w[0] == "--config")
530 .and_then(|w| w.get(1))
531 .map(|s| s.as_str())
532 .unwrap_or("")
533 .to_string()
534 } else {
535 String::new()
536 };
537
538 let replacements = vec![
540 ("{LOG_FILE}", log_file.display().to_string()),
541 ("{NEW_EXE}", new_exe_path.display().to_string()),
542 ("{CURRENT_EXE}", current_exe.display().to_string()),
543 ("{ARGS}", current_args.join(" ")),
544 ("{CONFIG_PATH}", config_path),
545 ("{SERVICE_ARGS}", current_args.join(" ")),
546 ];
547
548 run_script("cloudpub_update", script_template, replacements, is_service)
549}
550
551#[cfg(windows)]
552pub fn install_msi_package(msi_path: &std::path::Path) -> Result<()> {
553 let current_exe = std::env::current_exe()?;
554
555 run_script(
556 "cloudpub_install_msi",
557 WINDOWS_MSI_INSTALL_SCRIPT,
558 vec![
559 ("{MSI_PATH}", msi_path.display().to_string()),
560 ("{CURRENT_EXE}", current_exe.display().to_string()),
561 ],
562 false,
563 )
564}
565
566#[cfg(windows)]
567pub fn replace_and_restart_windows(
568 new_exe_path: &std::path::Path,
569 current_args: Vec<String>,
570) -> Result<()> {
571 let is_service = current_args.contains(&"--run-as-service".to_string());
572 let script_template = if is_service {
573 WINDOWS_SERVICE_UPDATE_SCRIPT
574 } else {
575 WINDOWS_CLIENT_UPDATE_SCRIPT
576 };
577 run_upgrade_script(script_template, new_exe_path, current_args, is_service)
578}
579
580#[cfg(not(windows))]
581pub fn replace_and_restart_unix(
582 new_exe_path: &std::path::Path,
583 current_args: Vec<String>,
584) -> Result<()> {
585 let is_service = current_args.contains(&"--run-as-service".to_string());
586 let script_template = if is_service {
587 UNIX_SERVICE_UPDATE_SCRIPT
588 } else {
589 UNIX_CLIENT_UPDATE_SCRIPT
590 };
591 run_upgrade_script(script_template, new_exe_path, current_args, is_service)
592}
593
594pub fn apply_update_and_restart(new_exe_path: &std::path::Path) -> Result<()> {
595 let args: Vec<String> = std::env::args().skip(1).collect();
597
598 eprintln!("{}", crate::t!("applying-update"));
599
600 #[cfg(windows)]
601 replace_and_restart_windows(new_exe_path, args)?;
602
603 #[cfg(not(windows))]
604 replace_and_restart_unix(new_exe_path, args)?;
605
606 Ok(())
607}
608
609#[cfg(target_os = "macos")]
610async fn install_dmg_package(package_path: &std::path::Path) -> Result<()> {
611 eprintln!("Installing DMG package...");
612
613 let current_exe = std::env::current_exe()?;
614 let current_args: Vec<String> = std::env::args().skip(1).collect();
615
616 run_script(
617 "cloudpub_install_dmg",
618 MACOS_DMG_INSTALL_SCRIPT,
619 vec![
620 ("{PACKAGE_PATH}", package_path.display().to_string()),
621 ("{CURRENT_EXE}", current_exe.display().to_string()),
622 ("{CURRENT_ARGS}", current_args.join(" ")),
623 ],
624 false,
625 )
626}
627
628#[cfg(target_os = "linux")]
629async fn install_deb_package(package_path: &std::path::Path) -> Result<()> {
630 eprintln!("Installing DEB package...");
631
632 let current_exe = std::env::current_exe()?;
633 let current_args: Vec<String> = std::env::args().skip(1).collect();
634
635 run_script(
636 "cloudpub_install_deb",
637 LINUX_DEB_INSTALL_SCRIPT,
638 vec![
639 ("{PACKAGE_PATH}", package_path.display().to_string()),
640 ("{CURRENT_EXE}", current_exe.display().to_string()),
641 ("{CURRENT_ARGS}", current_args.join(" ")),
642 ],
643 false,
644 )
645}
646
647#[cfg(target_os = "linux")]
648async fn install_rpm_package(package_path: &std::path::Path) -> Result<()> {
649 eprintln!("Installing RPM package...");
650
651 let current_exe = std::env::current_exe()?;
652 let current_args: Vec<String> = std::env::args().skip(1).collect();
653
654 run_script(
655 "cloudpub_install_rpm",
656 LINUX_RPM_INSTALL_SCRIPT,
657 vec![
658 ("{PACKAGE_PATH}", package_path.display().to_string()),
659 ("{CURRENT_EXE}", current_exe.display().to_string()),
660 ("{CURRENT_ARGS}", current_args.join(" ")),
661 ],
662 false,
663 )
664}
665
666async fn handle_cli_update(
667 filename: &str,
668 output_path: &std::path::Path,
669 cache_dir: &std::path::Path,
670 command_rx: &mut mpsc::Receiver<Message>,
671 result_tx: &mpsc::Sender<Message>,
672) -> Result<()> {
673 let unpack_message = "Unpacking update".to_string();
674
675 if filename.ends_with(".zip") {
676 crate::shell::unzip(&unpack_message, output_path, cache_dir, 0, result_tx)
677 .await
678 .context("Failed to unpack zip update")?;
679 } else if filename.ends_with(".tar.gz") {
680 let tar_args = vec![
681 "-xzf".to_string(),
682 output_path.to_string_lossy().to_string(),
683 ];
684
685 let total_files = 1;
686 let progress = Some((unpack_message, result_tx.clone(), total_files));
687
688 crate::shell::execute(
689 std::path::PathBuf::from("tar"),
690 tar_args,
691 Some(cache_dir.to_path_buf()),
692 std::collections::HashMap::new(),
693 progress,
694 command_rx,
695 )
696 .await
697 .context("Failed to unpack tar.gz update")?;
698 } else {
699 return Err(anyhow::anyhow!("Unknown file format: {}", filename));
700 }
701
702 eprintln!(
703 "{}",
704 crate::t!("update-unpacked", "path" => cache_dir.display().to_string())
705 );
706
707 #[cfg(not(windows))]
708 let new_exe_path = cache_dir.join("clo");
709 #[cfg(windows)]
710 let new_exe_path = cache_dir.join("clo.exe");
711
712 apply_update_and_restart(&new_exe_path)?;
713 Ok(())
714}
715
716pub async fn handle_upgrade_download(
717 version: &str,
718 gui: bool,
719 config: Arc<RwLock<ClientConfig>>,
720 command_rx: &mut mpsc::Receiver<Message>,
721 result_tx: &mpsc::Sender<Message>,
722) -> Result<(std::path::PathBuf, DownloadType, String)> {
723 let cache_dir = get_cache_dir("updates").unwrap();
725 std::fs::remove_dir_all(&cache_dir).ok();
726
727 let cache_dir = get_cache_dir("updates").unwrap();
729
730 let download_type = if gui {
731 #[cfg(target_os = "linux")]
732 {
733 match get_linux_package_type() {
735 Ok(pkg_type) if pkg_type == "deb" => DownloadType::Deb,
736 Ok(pkg_type) if pkg_type == "rpm" => DownloadType::Rpm,
737 _ => DownloadType::Cli, }
739 }
740 #[cfg(not(target_os = "linux"))]
741 DownloadType::Gui
742 } else {
743 DownloadType::Cli
744 };
745
746 let base_url = config.read().server.to_string();
748 let download_url =
749 get_download_url(&base_url, &download_type, version, cloudpub_common::BRANCH)?;
750
751 let mut file_extension = std::path::Path::new(&download_url)
753 .extension()
754 .and_then(|ext| ext.to_str())
755 .map(|ext| format!(".{}", ext))
756 .unwrap_or_else(|| ".tar.gz".to_string());
757
758 if file_extension == ".gz" {
759 file_extension = ".tar.gz".to_string();
761 }
762
763 let filename = format!("cloudpub-{}{}", version, file_extension);
764 let output_path = cache_dir.join(&filename);
765
766 let message = crate::t!("downloading-update", "path" => output_path.display().to_string());
767
768 crate::shell::download(
769 &message,
770 config.clone(),
771 &download_url,
772 &output_path,
773 command_rx,
774 result_tx,
775 )
776 .await
777 .with_context(|| format!("Failed to download update ({})", download_url))?;
778
779 eprintln!(
780 "{}",
781 crate::t!("update-downloaded", "path" => output_path.display().to_string())
782 );
783
784 Ok((output_path, download_type, filename))
785}
786
787pub async fn handle_upgrade_install(
788 output_path: std::path::PathBuf,
789 download_type: DownloadType,
790 filename: String,
791 command_rx: &mut mpsc::Receiver<Message>,
792 result_tx: &mpsc::Sender<Message>,
793) -> Result<()> {
794 let cache_dir = output_path.parent().unwrap();
795 std::env::set_current_dir(cache_dir)
797 .context("Failed to change current directory to cache directory")?;
798
799 match download_type {
801 DownloadType::Gui => {
802 #[cfg(target_os = "macos")]
803 {
804 if filename.ends_with(".dmg") {
805 install_dmg_package(&output_path).await?;
806 } else {
807 return Err(anyhow::anyhow!("Unsupported GUI package format"));
808 }
809 }
810 #[cfg(target_os = "windows")]
811 {
812 if filename.ends_with(".msi") {
813 install_msi_package(&output_path)?;
814 } else {
815 return Err(anyhow::anyhow!("Unsupported GUI package format"));
816 }
817 }
818 #[cfg(target_os = "linux")]
819 {
820 return Err(anyhow::anyhow!("GUI packages not supported on Linux"));
821 }
822 }
823 DownloadType::Deb => {
824 #[cfg(target_os = "linux")]
825 {
826 install_deb_package(&output_path).await?;
827 }
828 #[cfg(not(target_os = "linux"))]
829 {
830 return Err(anyhow::anyhow!("DEB packages only supported on Linux"));
831 }
832 }
833 DownloadType::Rpm => {
834 #[cfg(target_os = "linux")]
835 {
836 install_rpm_package(&output_path).await?;
837 }
838 #[cfg(not(target_os = "linux"))]
839 {
840 return Err(anyhow::anyhow!("RPM packages only supported on Linux"));
841 }
842 }
843 _ => {
844 handle_cli_update(&filename, &output_path, cache_dir, command_rx, result_tx).await?;
846 }
847 }
848
849 Ok(())
850}
851
852pub async fn handle_upgrade_available(
853 version: &str,
854 config: Arc<RwLock<ClientConfig>>,
855 gui: bool,
856 command_rx: &mut mpsc::Receiver<Message>,
857 result_tx: &mpsc::Sender<Message>,
858) -> Result<()> {
859 let (output_path, download_type, filename) =
860 handle_upgrade_download(version, gui, config, command_rx, result_tx).await?;
861
862 handle_upgrade_install(output_path, download_type, filename, command_rx, result_tx).await?;
863
864 Ok(())
865}