|
|
|
@ -211,206 +211,213 @@ fn update_app() {
|
|
|
|
|
Updater::String(V1Compatible::V1Compatible)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// bundle app update
|
|
|
|
|
build_app(&manifest_dir, &config, true, Default::default());
|
|
|
|
|
|
|
|
|
|
let updater_zip_ext = if v1_compatible {
|
|
|
|
|
if cfg!(windows) {
|
|
|
|
|
Some("zip")
|
|
|
|
|
} else {
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
let bundle_targets = vec![BundleTarget::AppImage, BundleTarget::Deb];
|
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
|
|
|
let bundle_targets = vec![BundleTarget::default()];
|
|
|
|
|
|
|
|
|
|
for bundle_target in bundle_targets {
|
|
|
|
|
// bundle app update
|
|
|
|
|
build_app(&manifest_dir, &config, true, bundle_target);
|
|
|
|
|
|
|
|
|
|
let updater_zip_ext = if v1_compatible {
|
|
|
|
|
if cfg!(windows) {
|
|
|
|
|
Some("zip")
|
|
|
|
|
} else {
|
|
|
|
|
Some("tar.gz")
|
|
|
|
|
}
|
|
|
|
|
} else if cfg!(target_os = "macos") {
|
|
|
|
|
Some("tar.gz")
|
|
|
|
|
}
|
|
|
|
|
} else if cfg!(target_os = "macos") {
|
|
|
|
|
Some("tar.gz")
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
|
|
|
|
|
let bundle_updater_ext = if v1_compatible {
|
|
|
|
|
out_bundle_path
|
|
|
|
|
.extension()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_str()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.replace("exe", "nsis")
|
|
|
|
|
} else {
|
|
|
|
|
out_bundle_path
|
|
|
|
|
.extension()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_str()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_string()
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
|
|
|
|
|
format!("{bundle_updater_ext}.{updater_zip_ext}")
|
|
|
|
|
} else {
|
|
|
|
|
bundle_updater_ext
|
|
|
|
|
};
|
|
|
|
|
let signature_extension = format!("{updater_extension}.sig");
|
|
|
|
|
let signature_path = out_bundle_path.with_extension(signature_extension);
|
|
|
|
|
let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| {
|
|
|
|
|
panic!("failed to read signature file {}", signature_path.display())
|
|
|
|
|
});
|
|
|
|
|
let out_updater_path = out_bundle_path.with_extension(updater_extension);
|
|
|
|
|
let updater_path = root_dir.join(format!(
|
|
|
|
|
"target/debug/{}",
|
|
|
|
|
out_updater_path.file_name().unwrap().to_str().unwrap()
|
|
|
|
|
));
|
|
|
|
|
std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");
|
|
|
|
|
|
|
|
|
|
let target = target.clone();
|
|
|
|
|
|
|
|
|
|
// start the updater server
|
|
|
|
|
let server = Arc::new(
|
|
|
|
|
tiny_http::Server::http("localhost:3007")
|
|
|
|
|
.expect("failed to start updater server"),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let server_ = server.clone();
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
for request in server_.incoming_requests() {
|
|
|
|
|
match request.url() {
|
|
|
|
|
"/" => {
|
|
|
|
|
let mut platforms = HashMap::new();
|
|
|
|
|
|
|
|
|
|
platforms.insert(
|
|
|
|
|
target.clone(),
|
|
|
|
|
PlatformUpdate {
|
|
|
|
|
signature: signature.clone(),
|
|
|
|
|
url: "http://localhost:3007/download",
|
|
|
|
|
with_elevated_task: false,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
let body = serde_json::to_vec(&Update {
|
|
|
|
|
version: "1.0.0",
|
|
|
|
|
date: time::OffsetDateTime::now_utc()
|
|
|
|
|
.format(&time::format_description::well_known::Rfc3339)
|
|
|
|
|
.unwrap(),
|
|
|
|
|
platforms,
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
let len = body.len();
|
|
|
|
|
let response = tiny_http::Response::new(
|
|
|
|
|
tiny_http::StatusCode(200),
|
|
|
|
|
Vec::new(),
|
|
|
|
|
std::io::Cursor::new(body),
|
|
|
|
|
Some(len),
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
let _ = request.respond(response);
|
|
|
|
|
}
|
|
|
|
|
"/download" => {
|
|
|
|
|
let _ = request.respond(tiny_http::Response::from_file(
|
|
|
|
|
File::open(&updater_path).unwrap_or_else(|_| {
|
|
|
|
|
panic!(
|
|
|
|
|
"failed to open updater bundle {}",
|
|
|
|
|
updater_path.display()
|
|
|
|
|
)
|
|
|
|
|
}),
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
|
|
|
|
|
let bundle_updater_ext = if v1_compatible {
|
|
|
|
|
out_bundle_path
|
|
|
|
|
.extension()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_str()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.replace("exe", "nsis")
|
|
|
|
|
} else {
|
|
|
|
|
out_bundle_path
|
|
|
|
|
.extension()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_str()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.to_string()
|
|
|
|
|
};
|
|
|
|
|
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
|
|
|
|
|
format!("{bundle_updater_ext}.{updater_zip_ext}")
|
|
|
|
|
} else {
|
|
|
|
|
bundle_updater_ext
|
|
|
|
|
};
|
|
|
|
|
let signature_extension = format!("{updater_extension}.sig");
|
|
|
|
|
let signature_path = out_bundle_path.with_extension(signature_extension);
|
|
|
|
|
let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| {
|
|
|
|
|
panic!("failed to read signature file {}", signature_path.display())
|
|
|
|
|
});
|
|
|
|
|
let out_updater_path = out_bundle_path.with_extension(updater_extension);
|
|
|
|
|
let updater_path = root_dir.join(format!(
|
|
|
|
|
"target/debug/{}",
|
|
|
|
|
out_updater_path.file_name().unwrap().to_str().unwrap()
|
|
|
|
|
));
|
|
|
|
|
std::fs::rename(&out_updater_path, &updater_path)
|
|
|
|
|
.expect("failed to rename bundle");
|
|
|
|
|
|
|
|
|
|
let target = target.clone();
|
|
|
|
|
|
|
|
|
|
// start the updater server
|
|
|
|
|
let server = Arc::new(
|
|
|
|
|
tiny_http::Server::http("localhost:3007")
|
|
|
|
|
.expect("failed to start updater server"),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let server_ = server.clone();
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
for request in server_.incoming_requests() {
|
|
|
|
|
match request.url() {
|
|
|
|
|
"/" => {
|
|
|
|
|
let mut platforms = HashMap::new();
|
|
|
|
|
|
|
|
|
|
platforms.insert(
|
|
|
|
|
target.clone(),
|
|
|
|
|
PlatformUpdate {
|
|
|
|
|
signature: signature.clone(),
|
|
|
|
|
url: "http://localhost:3007/download",
|
|
|
|
|
with_elevated_task: false,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
let body = serde_json::to_vec(&Update {
|
|
|
|
|
version: "1.0.0",
|
|
|
|
|
date: time::OffsetDateTime::now_utc()
|
|
|
|
|
.format(&time::format_description::well_known::Rfc3339)
|
|
|
|
|
.unwrap(),
|
|
|
|
|
platforms,
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
let len = body.len();
|
|
|
|
|
let response = tiny_http::Response::new(
|
|
|
|
|
tiny_http::StatusCode(200),
|
|
|
|
|
Vec::new(),
|
|
|
|
|
std::io::Cursor::new(body),
|
|
|
|
|
Some(len),
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
let _ = request.respond(response);
|
|
|
|
|
}
|
|
|
|
|
"/download" => {
|
|
|
|
|
let _ = request.respond(tiny_http::Response::from_file(
|
|
|
|
|
File::open(&updater_path).unwrap_or_else(|_| {
|
|
|
|
|
panic!(
|
|
|
|
|
"failed to open updater bundle {}",
|
|
|
|
|
updater_path.display()
|
|
|
|
|
)
|
|
|
|
|
}),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
config.version = "0.1.0";
|
|
|
|
|
|
|
|
|
|
// bundle initial app version
|
|
|
|
|
build_app(&manifest_dir, &config, false, bundle_target);
|
|
|
|
|
|
|
|
|
|
// Set appropriate permissions and install package if needed
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
{
|
|
|
|
|
let paths = bundle_paths(&root_dir, "0.1.0");
|
|
|
|
|
let bundle_path = &paths.first().unwrap().1;
|
|
|
|
|
|
|
|
|
|
if bundle_target == BundleTarget::AppImage {
|
|
|
|
|
std::process::Command::new("sudo")
|
|
|
|
|
.arg("chmod")
|
|
|
|
|
.arg("+x")
|
|
|
|
|
.arg(bundle_path)
|
|
|
|
|
.status()
|
|
|
|
|
.expect("failed to change permissions");
|
|
|
|
|
} else if bundle_target == BundleTarget::Deb {
|
|
|
|
|
// Install the .deb package
|
|
|
|
|
let install_status = std::process::Command::new("sudo")
|
|
|
|
|
.arg("dpkg")
|
|
|
|
|
.arg("-i")
|
|
|
|
|
.arg(bundle_path)
|
|
|
|
|
.status()
|
|
|
|
|
.expect("failed to install .deb package");
|
|
|
|
|
|
|
|
|
|
if !install_status.success() {
|
|
|
|
|
panic!("Failed to install .deb package");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
config.version = "0.1.0";
|
|
|
|
|
|
|
|
|
|
// bundle initial app version
|
|
|
|
|
build_app(&manifest_dir, &config, false, bundle_target);
|
|
|
|
|
|
|
|
|
|
// Set appropriate permissions and install package if needed
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
{
|
|
|
|
|
let bundle_path = &out_bundle_path;
|
|
|
|
|
|
|
|
|
|
if bundle_target == BundleTarget::AppImage {
|
|
|
|
|
std::process::Command::new("sudo")
|
|
|
|
|
.arg("chmod")
|
|
|
|
|
.arg("+x")
|
|
|
|
|
.arg(bundle_path)
|
|
|
|
|
.status()
|
|
|
|
|
.expect("failed to change permissions");
|
|
|
|
|
} else if bundle_target == BundleTarget::Deb {
|
|
|
|
|
// Install the .deb package
|
|
|
|
|
let install_status = std::process::Command::new("sudo")
|
|
|
|
|
.arg("dpkg")
|
|
|
|
|
.arg("-i")
|
|
|
|
|
.arg(bundle_path)
|
|
|
|
|
.status()
|
|
|
|
|
.expect("failed to install .deb package");
|
|
|
|
|
|
|
|
|
|
if !install_status.success() {
|
|
|
|
|
panic!("Failed to install .deb package");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let status_checks = if matches!(bundle_target, BundleTarget::Msi) {
|
|
|
|
|
vec![UPDATED_EXIT_CODE]
|
|
|
|
|
} else {
|
|
|
|
|
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for expected_exit_code in status_checks {
|
|
|
|
|
let mut binary_cmd = if cfg!(windows) {
|
|
|
|
|
Command::new(root_dir.join("target/debug/app-updater.exe"))
|
|
|
|
|
} else if cfg!(target_os = "macos") {
|
|
|
|
|
Command::new(
|
|
|
|
|
bundle_paths(&root_dir, "0.1.0")
|
|
|
|
|
.first()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.1
|
|
|
|
|
.join("Contents/MacOS/app-updater"),
|
|
|
|
|
)
|
|
|
|
|
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
|
|
|
|
|
let mut c = Command::new("xvfb-run");
|
|
|
|
|
c.arg("--auto-servernum");
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
if bundle_target == BundleTarget::Deb {
|
|
|
|
|
c.arg("/usr/bin/app-updater");
|
|
|
|
|
} else {
|
|
|
|
|
c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
|
|
|
|
|
}
|
|
|
|
|
c
|
|
|
|
|
let status_checks = if matches!(bundle_target, BundleTarget::Msi) {
|
|
|
|
|
vec![UPDATED_EXIT_CODE]
|
|
|
|
|
} else {
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
{
|
|
|
|
|
let mut c = Command::new("sudo");
|
|
|
|
|
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for expected_exit_code in status_checks {
|
|
|
|
|
let mut binary_cmd = if cfg!(windows) {
|
|
|
|
|
Command::new(root_dir.join("target/debug/app-updater.exe"))
|
|
|
|
|
} else if cfg!(target_os = "macos") {
|
|
|
|
|
Command::new(
|
|
|
|
|
bundle_paths(&root_dir, "0.1.0")
|
|
|
|
|
.first()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.1
|
|
|
|
|
.join("Contents/MacOS/app-updater"),
|
|
|
|
|
)
|
|
|
|
|
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
|
|
|
|
|
let mut c = Command::new("xvfb-run");
|
|
|
|
|
c.arg("--auto-servernum");
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
if bundle_target == BundleTarget::Deb {
|
|
|
|
|
c.arg("/usr/bin/app-updater");
|
|
|
|
|
} else {
|
|
|
|
|
c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
|
|
|
|
|
}
|
|
|
|
|
c
|
|
|
|
|
}
|
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
|
|
|
{
|
|
|
|
|
Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
{
|
|
|
|
|
let mut c = Command::new("sudo");
|
|
|
|
|
if bundle_target == BundleTarget::Deb {
|
|
|
|
|
c.arg("/usr/bin/app-updater");
|
|
|
|
|
} else {
|
|
|
|
|
c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
|
|
|
|
|
}
|
|
|
|
|
c
|
|
|
|
|
}
|
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
|
|
|
{
|
|
|
|
|
Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
binary_cmd.env("TARGET", bundle_target.name());
|
|
|
|
|
binary_cmd.env("TARGET", bundle_target.name());
|
|
|
|
|
|
|
|
|
|
let status = binary_cmd.status().expect("failed to run app");
|
|
|
|
|
let code = status.code().unwrap_or(-1);
|
|
|
|
|
let status = binary_cmd.status().expect("failed to run app");
|
|
|
|
|
let code = status.code().unwrap_or(-1);
|
|
|
|
|
|
|
|
|
|
if code != expected_exit_code {
|
|
|
|
|
panic!(
|
|
|
|
|
"failed to run app, expected exit code {expected_exit_code}, got {code}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
|
if code == UPDATED_EXIT_CODE {
|
|
|
|
|
// wait for the update to finish
|
|
|
|
|
std::thread::sleep(std::time::Duration::from_secs(5));
|
|
|
|
|
if code != expected_exit_code {
|
|
|
|
|
panic!(
|
|
|
|
|
"failed to run app, expected exit code {expected_exit_code}, got {code}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
|
if code == UPDATED_EXIT_CODE {
|
|
|
|
|
// wait for the update to finish
|
|
|
|
|
std::thread::sleep(std::time::Duration::from_secs(5));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// graceful shutdown
|
|
|
|
|
server.unblock();
|
|
|
|
|
// graceful shutdown
|
|
|
|
|
server.unblock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|