build(hololake): add verified Windows x64 distribution path
This commit is contained in:
parent
c471453abf
commit
39fc36f35b
13 changed files with 275 additions and 33 deletions
|
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::Value;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::Write;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Output};
|
||||
|
|
@ -15,6 +16,18 @@ use tauri::{AppHandle, Manager};
|
|||
use tauri_plugin_updater::{Update, UpdaterExt};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(windows)]
|
||||
trait OpenOptionsModeExt {
|
||||
fn mode(&mut self, mode: u32) -> &mut Self;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl OpenOptionsModeExt for OpenOptions {
|
||||
fn mode(&mut self, _mode: u32) -> &mut Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
const CANDIDATE_SCHEMA: &str = "hololake.release-candidate/v1";
|
||||
const RECEIPT_SCHEMA: &str = "hololake.release-install-receipt/v1";
|
||||
const RECOVERY_SCHEMA: &str = "hololake.release-recovery/v1";
|
||||
|
|
@ -257,18 +270,17 @@ pub async fn confirm_hololake_update_install(
|
|||
{
|
||||
return Err("HOLOLAKE_RELEASE_PACKAGE_DIGEST_OR_SIZE_MISMATCH".into());
|
||||
}
|
||||
let current_bundle = current_app_bundle_path()?;
|
||||
let backup = prepare_last_known_good_at(&root, ¤t_bundle)?;
|
||||
let platform_recovery = prepare_platform_recovery_at(&root)?;
|
||||
let mut recovery = ReleaseRecoveryRecord {
|
||||
schema: RECOVERY_SCHEMA.into(),
|
||||
state: "LAST_KNOWN_GOOD_VERIFIED_INSTALLING".into(),
|
||||
state: platform_recovery.installing_state.into(),
|
||||
release_id: snapshot.envelope.release_id.clone(),
|
||||
previous_version: snapshot.current_version.clone(),
|
||||
installed_version: snapshot.envelope.version.clone(),
|
||||
backup_bundle_path: backup.0.to_string_lossy().into_owned(),
|
||||
backup_identifier: backup.1.identifier,
|
||||
backup_team_identifier: backup.1.team_identifier,
|
||||
backup_cdhash: backup.1.cdhash,
|
||||
backup_bundle_path: platform_recovery.backup_bundle_path,
|
||||
backup_identifier: platform_recovery.backup_identifier,
|
||||
backup_team_identifier: platform_recovery.backup_team_identifier,
|
||||
backup_cdhash: platform_recovery.backup_cdhash,
|
||||
failed_bundle_path: None,
|
||||
observed_at_unix_ms: now_unix_ms()?,
|
||||
receipt_id: sha256_hex(
|
||||
|
|
@ -293,7 +305,8 @@ pub async fn confirm_hololake_update_install(
|
|||
package_size: snapshot.package_size,
|
||||
automatic_restart: false,
|
||||
health_receipt_required: snapshot.envelope.hololake.rollback.health_receipt_required,
|
||||
rollback_declared_by_broadcast: snapshot.envelope.hololake.rollback.supported,
|
||||
rollback_declared_by_broadcast: snapshot.envelope.hololake.rollback.supported
|
||||
&& platform_recovery.local_rollback_available,
|
||||
receipt_id: sha256_hex(
|
||||
format!(
|
||||
"{}\n{}\n{}\n{}",
|
||||
|
|
@ -311,13 +324,13 @@ pub async fn confirm_hololake_update_install(
|
|||
"RECEIPT",
|
||||
)?;
|
||||
if let Err(error) = mark_candidate_installing_at(&root, &record.candidate_id) {
|
||||
recovery.state = "INSTALL_FAILED_BACKUP_RETAINED".into();
|
||||
recovery.state = platform_install_failed_state().into();
|
||||
recovery.observed_at_unix_ms = now_unix_ms()?;
|
||||
write_json_atomic(&recovery_path(&root), &recovery, "RECOVERY")?;
|
||||
return Err(error);
|
||||
}
|
||||
if let Err(error) = update.install(&bytes) {
|
||||
recovery.state = "INSTALL_FAILED_BACKUP_RETAINED".into();
|
||||
recovery.state = platform_install_failed_state().into();
|
||||
recovery.observed_at_unix_ms = now_unix_ms()?;
|
||||
write_json_atomic(&recovery_path(&root), &recovery, "RECOVERY")?;
|
||||
return Err(format!("HOLOLAKE_RELEASE_INSTALL_FAILED: {error}"));
|
||||
|
|
@ -347,6 +360,8 @@ pub fn confirm_hololake_release_health(app: AppHandle) -> Result<ReleaseRecovery
|
|||
| "ROLLED_BACK_AWAITING_HUMAN_ACKNOWLEDGEMENT"
|
||||
| "INSTALL_FAILED_BACKUP_RETAINED"
|
||||
| "INSTALL_INTERRUPTED_BACKUP_RETAINED"
|
||||
| "INSTALL_FAILED_NO_LOCAL_BACKUP"
|
||||
| "INSTALL_INTERRUPTED_NO_LOCAL_BACKUP"
|
||||
) {
|
||||
return Err("HOLOLAKE_RELEASE_HEALTH_CONFIRMATION_NOT_AVAILABLE".into());
|
||||
}
|
||||
|
|
@ -359,13 +374,14 @@ pub fn confirm_hololake_release_health(app: AppHandle) -> Result<ReleaseRecovery
|
|||
if &running_version != expected {
|
||||
return Err("HOLOLAKE_RELEASE_RUNNING_VERSION_MISMATCH".into());
|
||||
}
|
||||
remove_owned_bundle_path(&root, Path::new(&record.backup_bundle_path))?;
|
||||
if let Some(failed) = record.failed_bundle_path.as_deref() {
|
||||
remove_owned_failed_bundle(Path::new(failed), ¤t_app_parent()?)?;
|
||||
}
|
||||
finalize_platform_recovery_artifacts(&root, &record)?;
|
||||
record.state = match record.state.as_str() {
|
||||
"AWAITING_HUMAN_HEALTH_CONFIRMATION" if record.backup_bundle_path.is_empty() => {
|
||||
"HEALTH_CONFIRMED_NO_LOCAL_BACKUP"
|
||||
}
|
||||
"AWAITING_HUMAN_HEALTH_CONFIRMATION" => "HEALTH_CONFIRMED_BACKUP_REMOVED",
|
||||
"ROLLED_BACK_AWAITING_HUMAN_ACKNOWLEDGEMENT" => "ROLLBACK_CONFIRMED_BACKUP_REMOVED",
|
||||
_ if record.backup_bundle_path.is_empty() => "INSTALL_FAILURE_ACKNOWLEDGED_NO_LOCAL_BACKUP",
|
||||
_ => "INSTALL_FAILURE_ACKNOWLEDGED_BACKUP_REMOVED",
|
||||
}
|
||||
.into();
|
||||
|
|
@ -374,6 +390,7 @@ pub fn confirm_hololake_release_health(app: AppHandle) -> Result<ReleaseRecovery
|
|||
Ok(recovery_receipt(&record))
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[tauri::command]
|
||||
pub fn rollback_hololake_update(app: AppHandle) -> Result<ReleaseRecoveryReceipt, String> {
|
||||
let root = release_update_root(&app)?;
|
||||
|
|
@ -396,6 +413,12 @@ pub fn rollback_hololake_update(app: AppHandle) -> Result<ReleaseRecoveryReceipt
|
|||
Ok(recovery_receipt(&record))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tauri::command]
|
||||
pub fn rollback_hololake_update(_app: AppHandle) -> Result<ReleaseRecoveryReceipt, String> {
|
||||
Err("HOLOLAKE_RELEASE_WINDOWS_LOCAL_ROLLBACK_NOT_AVAILABLE".into())
|
||||
}
|
||||
|
||||
pub(crate) fn observe_release_startup(app: &AppHandle) -> Result<(), String> {
|
||||
let root = release_update_root(app)?;
|
||||
let path = recovery_path(&root);
|
||||
|
|
@ -422,6 +445,16 @@ pub(crate) fn observe_release_startup(app: &AppHandle) -> Result<(), String> {
|
|||
"LAST_KNOWN_GOOD_VERIFIED_INSTALLING" if version == record.installed_version => {
|
||||
Some("AWAITING_HUMAN_HEALTH_CONFIRMATION")
|
||||
}
|
||||
"SIGNED_PACKAGE_VERIFIED_INSTALLING_NO_LOCAL_ROLLBACK"
|
||||
if version == record.previous_version =>
|
||||
{
|
||||
Some("INSTALL_INTERRUPTED_NO_LOCAL_BACKUP")
|
||||
}
|
||||
"SIGNED_PACKAGE_VERIFIED_INSTALLING_NO_LOCAL_ROLLBACK"
|
||||
if version == record.installed_version =>
|
||||
{
|
||||
Some("AWAITING_HUMAN_HEALTH_CONFIRMATION")
|
||||
}
|
||||
"AWAITING_HUMAN_HEALTH_CONFIRMATION" if version == record.previous_version => {
|
||||
Some("ROLLED_BACK_AWAITING_HUMAN_ACKNOWLEDGEMENT")
|
||||
}
|
||||
|
|
@ -615,8 +648,10 @@ fn ensure_no_unresolved_recovery_at(root: &Path) -> Result<(), String> {
|
|||
if matches!(
|
||||
record.state.as_str(),
|
||||
"HEALTH_CONFIRMED_BACKUP_REMOVED"
|
||||
| "HEALTH_CONFIRMED_NO_LOCAL_BACKUP"
|
||||
| "ROLLBACK_CONFIRMED_BACKUP_REMOVED"
|
||||
| "INSTALL_FAILURE_ACKNOWLEDGED_BACKUP_REMOVED"
|
||||
| "INSTALL_FAILURE_ACKNOWLEDGED_NO_LOCAL_BACKUP"
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
|
|
@ -643,8 +678,11 @@ fn release_recovery_status_at(root: &Path) -> Result<ReleaseRecoveryStatus, Stri
|
|||
| "ROLLED_BACK_AWAITING_HUMAN_ACKNOWLEDGEMENT"
|
||||
| "INSTALL_FAILED_BACKUP_RETAINED"
|
||||
| "INSTALL_INTERRUPTED_BACKUP_RETAINED"
|
||||
| "INSTALL_FAILED_NO_LOCAL_BACKUP"
|
||||
| "INSTALL_INTERRUPTED_NO_LOCAL_BACKUP"
|
||||
);
|
||||
let backup_ready = Path::new(&record.backup_bundle_path).exists();
|
||||
let backup_ready =
|
||||
!record.backup_bundle_path.is_empty() && Path::new(&record.backup_bundle_path).exists();
|
||||
Ok(ReleaseRecoveryStatus {
|
||||
schema: RECOVERY_SCHEMA,
|
||||
state: record.state,
|
||||
|
|
@ -672,6 +710,74 @@ fn recovery_receipt(record: &ReleaseRecoveryRecord) -> ReleaseRecoveryReceipt {
|
|||
}
|
||||
}
|
||||
|
||||
struct PlatformRecoveryPreparation {
|
||||
installing_state: &'static str,
|
||||
backup_bundle_path: String,
|
||||
backup_identifier: String,
|
||||
backup_team_identifier: String,
|
||||
backup_cdhash: String,
|
||||
local_rollback_available: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn prepare_platform_recovery_at(root: &Path) -> Result<PlatformRecoveryPreparation, String> {
|
||||
let current_bundle = current_app_bundle_path()?;
|
||||
let (backup, evidence) = prepare_last_known_good_at(root, ¤t_bundle)?;
|
||||
Ok(PlatformRecoveryPreparation {
|
||||
installing_state: "LAST_KNOWN_GOOD_VERIFIED_INSTALLING",
|
||||
backup_bundle_path: backup.to_string_lossy().into_owned(),
|
||||
backup_identifier: evidence.identifier,
|
||||
backup_team_identifier: evidence.team_identifier,
|
||||
backup_cdhash: evidence.cdhash,
|
||||
local_rollback_available: true,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn prepare_platform_recovery_at(_root: &Path) -> Result<PlatformRecoveryPreparation, String> {
|
||||
Ok(PlatformRecoveryPreparation {
|
||||
installing_state: "SIGNED_PACKAGE_VERIFIED_INSTALLING_NO_LOCAL_ROLLBACK",
|
||||
backup_bundle_path: String::new(),
|
||||
backup_identifier: String::new(),
|
||||
backup_team_identifier: String::new(),
|
||||
backup_cdhash: String::new(),
|
||||
local_rollback_available: false,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn platform_install_failed_state() -> &'static str {
|
||||
"INSTALL_FAILED_BACKUP_RETAINED"
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn platform_install_failed_state() -> &'static str {
|
||||
"INSTALL_FAILED_NO_LOCAL_BACKUP"
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn finalize_platform_recovery_artifacts(
|
||||
root: &Path,
|
||||
record: &ReleaseRecoveryRecord,
|
||||
) -> Result<(), String> {
|
||||
remove_owned_bundle_path(root, Path::new(&record.backup_bundle_path))?;
|
||||
if let Some(failed) = record.failed_bundle_path.as_deref() {
|
||||
remove_owned_failed_bundle(Path::new(failed), ¤t_app_parent()?)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn finalize_platform_recovery_artifacts(
|
||||
_root: &Path,
|
||||
record: &ReleaseRecoveryRecord,
|
||||
) -> Result<(), String> {
|
||||
if !record.backup_bundle_path.is_empty() || record.failed_bundle_path.is_some() {
|
||||
return Err("HOLOLAKE_RELEASE_WINDOWS_RECOVERY_BOUNDARY_INVALID".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_last_known_good_at(
|
||||
root: &Path,
|
||||
current_bundle: &Path,
|
||||
|
|
|
|||
Loading…
Reference in a new issue