update.rs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. #![allow(dead_code, unused_imports)]
  5. use std::{
  6. collections::HashMap,
  7. fs::File,
  8. path::{Path, PathBuf},
  9. process::Command,
  10. };
  11. use serde::Serialize;
  12. const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg==";
  13. #[derive(Serialize)]
  14. struct PackageConfig {
  15. version: &'static str,
  16. }
  17. #[derive(Serialize)]
  18. struct Config {
  19. package: PackageConfig,
  20. }
  21. #[derive(Serialize)]
  22. struct PlatformUpdate {
  23. signature: String,
  24. url: &'static str,
  25. with_elevated_task: bool,
  26. }
  27. #[derive(Serialize)]
  28. struct Update {
  29. version: &'static str,
  30. date: String,
  31. platforms: HashMap<String, PlatformUpdate>,
  32. }
  33. fn get_cli_bin_path(cli_dir: &Path, debug: bool) -> Option<PathBuf> {
  34. let mut cli_bin_path = cli_dir.join(format!(
  35. "target/{}/cargo-tauri",
  36. if debug { "debug" } else { "release" }
  37. ));
  38. if cfg!(windows) {
  39. cli_bin_path.set_extension("exe");
  40. }
  41. if cli_bin_path.exists() {
  42. Some(cli_bin_path)
  43. } else {
  44. None
  45. }
  46. }
  47. fn build_app(cli_bin_path: &Path, cwd: &Path, config: &Config, bundle_updater: bool) {
  48. let mut command = Command::new(&cli_bin_path);
  49. command
  50. .args(["build", "--debug", "--verbose"])
  51. .arg("--config")
  52. .arg(serde_json::to_string(config).unwrap())
  53. .current_dir(&cwd);
  54. #[cfg(windows)]
  55. command.args(["--bundles", "msi"]);
  56. #[cfg(target_os = "linux")]
  57. command.args(["--bundles", "appimage"]);
  58. #[cfg(target_os = "macos")]
  59. command.args(["--bundles", "app"]);
  60. if bundle_updater {
  61. command
  62. .env("TAURI_PRIVATE_KEY", UPDATER_PRIVATE_KEY)
  63. .env("TAURI_KEY_PASSWORD", "")
  64. .args(["--bundles", "updater"]);
  65. }
  66. let status = command
  67. .status()
  68. .expect("failed to run Tauri CLI to bundle app");
  69. if !status.code().map(|c| c == 0).unwrap_or(true) {
  70. panic!("failed to bundle app {:?}", status.code());
  71. }
  72. }
  73. #[cfg(target_os = "linux")]
  74. fn bundle_path(root_dir: &Path, version: &str) -> PathBuf {
  75. root_dir.join(format!(
  76. "target/debug/bundle/appimage/app-updater_{}_amd64.AppImage",
  77. version
  78. ))
  79. }
  80. #[cfg(target_os = "macos")]
  81. fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf {
  82. root_dir.join(format!("target/debug/bundle/macos/app-updater.app"))
  83. }
  84. #[cfg(windows)]
  85. fn bundle_path(root_dir: &Path, version: &str) -> PathBuf {
  86. root_dir.join(format!(
  87. "target/debug/bundle/msi/app-updater_{}_x64_en-US.msi",
  88. version
  89. ))
  90. }
  91. #[test]
  92. #[ignore]
  93. fn update_app() {
  94. let target = tauri::updater::target().expect("running updater test in an unsupported platform");
  95. let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
  96. let root_dir = manifest_dir.join("../../..");
  97. let cli_dir = root_dir.join("tooling/cli");
  98. let cli_bin_path = if let Some(p) = get_cli_bin_path(&cli_dir, false) {
  99. p
  100. } else if let Some(p) = get_cli_bin_path(&cli_dir, true) {
  101. p
  102. } else {
  103. let status = Command::new("cargo")
  104. .arg("build")
  105. .current_dir(&cli_dir)
  106. .status()
  107. .expect("failed to run cargo");
  108. if !status.success() {
  109. panic!("failed to build CLI");
  110. }
  111. get_cli_bin_path(&cli_dir, true).expect("cargo did not build the Tauri CLI")
  112. };
  113. let mut config = Config {
  114. package: PackageConfig { version: "1.0.0" },
  115. };
  116. // bundle app update
  117. build_app(&cli_bin_path, &manifest_dir, &config, true);
  118. let updater_ext = if cfg!(windows) { "zip" } else { "tar.gz" };
  119. let out_bundle_path = bundle_path(&root_dir, "1.0.0");
  120. let signature_path = out_bundle_path.with_extension(format!(
  121. "{}.{}.sig",
  122. out_bundle_path.extension().unwrap().to_str().unwrap(),
  123. updater_ext
  124. ));
  125. let signature = std::fs::read_to_string(&signature_path)
  126. .unwrap_or_else(|_| panic!("failed to read signature file {}", signature_path.display()));
  127. let out_updater_path = out_bundle_path.with_extension(format!(
  128. "{}.{}",
  129. out_bundle_path.extension().unwrap().to_str().unwrap(),
  130. updater_ext
  131. ));
  132. let updater_path = root_dir.join(format!(
  133. "target/debug/{}",
  134. out_updater_path.file_name().unwrap().to_str().unwrap()
  135. ));
  136. std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");
  137. std::thread::spawn(move || {
  138. // start the updater server
  139. let server = tiny_http::Server::http("localhost:3007").expect("failed to start updater server");
  140. loop {
  141. if let Ok(request) = server.recv() {
  142. match request.url() {
  143. "/" => {
  144. let mut platforms = HashMap::new();
  145. platforms.insert(
  146. target.clone(),
  147. PlatformUpdate {
  148. signature: signature.clone(),
  149. url: "http://localhost:3007/download",
  150. with_elevated_task: false,
  151. },
  152. );
  153. let body = serde_json::to_vec(&Update {
  154. version: "1.0.0",
  155. date: time::OffsetDateTime::now_utc()
  156. .format(&time::format_description::well_known::Rfc3339)
  157. .unwrap(),
  158. platforms,
  159. })
  160. .unwrap();
  161. let len = body.len();
  162. let response = tiny_http::Response::new(
  163. tiny_http::StatusCode(200),
  164. Vec::new(),
  165. std::io::Cursor::new(body),
  166. Some(len),
  167. None,
  168. );
  169. let _ = request.respond(response);
  170. }
  171. "/download" => {
  172. let _ = request.respond(tiny_http::Response::from_file(
  173. File::open(&updater_path).unwrap_or_else(|_| {
  174. panic!("failed to open updater bundle {}", updater_path.display())
  175. }),
  176. ));
  177. }
  178. _ => (),
  179. }
  180. }
  181. }
  182. });
  183. config.package.version = "0.1.0";
  184. // bundle initial app version
  185. build_app(&cli_bin_path, &manifest_dir, &config, false);
  186. let mut binary_cmd = if cfg!(windows) {
  187. Command::new(root_dir.join("target/debug/app-updater.exe"))
  188. } else if cfg!(target_os = "macos") {
  189. Command::new(bundle_path(&root_dir, "0.1.0").join("Contents/MacOS/app-updater"))
  190. } else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
  191. let mut c = Command::new("xvfb-run");
  192. c.arg("--auto-servernum")
  193. .arg(bundle_path(&root_dir, "0.1.0"));
  194. c
  195. } else {
  196. Command::new(bundle_path(&root_dir, "0.1.0"))
  197. };
  198. let status = binary_cmd.status().expect("failed to run app");
  199. if !status.success() {
  200. panic!("failed to run app");
  201. }
  202. }