In-app updates on Windows
The Microsoft Store doesn't update EXE installs, so the app updates itself with Tauri's updater. It reads updater/latest.json from the Kestrove Gitea a minute after startup and once a day. A new version shows as a banner and in Settings > Updates, and installs only on "Update and restart". The vault locks before the installer takes over. The installer must be signed with the Kestrove updater key, with the signature bound to its version (requireSignedVersion), so a compromised download server can hold updates back but can't ship one or replay an old installer. Signing is a release step, after Authenticode: gitea-release.sh signs, uploads the .sig, and writes latest.json last. Plain builds need no key. Checks reveal only the IP address and app version, and can be turned off in Settings. endpoints is a list, so a mirror can be added. Linux packages update through their package manager. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+59
-7
@@ -17,6 +17,7 @@ use kestrove_desktop::sync::{
|
||||
self, AddDeviceView, DeviceView, JoinConfirmView, JoinPollView, JoinView, SetupView, SignIn, SyncService,
|
||||
SyncStatus,
|
||||
};
|
||||
use kestrove_desktop::updates::{UpdateInfo, Updates};
|
||||
use kestrove_desktop::{ipc, private_file};
|
||||
use kv_ffi::{generate_password as core_generate_password, totp_code, CoreError, ItemDto, ItemKindDto, VaultCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -37,6 +38,7 @@ struct App {
|
||||
idle_minutes: Arc<AtomicU64>,
|
||||
/// Windows Hello is set up on this computer (checked off the main thread; see `refresh_hello`).
|
||||
hello_available: Arc<AtomicBool>,
|
||||
updates: Arc<Updates>,
|
||||
// Kept alive: on X11 the clipboard owner must outlive the copy.
|
||||
clipboard: Mutex<Option<arboard::Clipboard>>,
|
||||
}
|
||||
@@ -46,6 +48,15 @@ impl App {
|
||||
self.data_dir.join("settings.json")
|
||||
}
|
||||
|
||||
fn save_settings(&self) -> CmdResult<()> {
|
||||
let s = Settings {
|
||||
idle_minutes: self.idle_minutes.load(Ordering::Relaxed),
|
||||
check_updates: self.updates.automatic(),
|
||||
};
|
||||
let bytes = serde_json::to_vec(&s).map_err(|e| e.to_string())?;
|
||||
private_file::write(&self.settings_path(), &bytes).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Re-checks Windows Hello in the background (it can be set up or removed while Kestrove runs).
|
||||
fn refresh_hello(&self) {
|
||||
let available = self.hello_available.clone();
|
||||
@@ -58,6 +69,13 @@ impl App {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Settings {
|
||||
idle_minutes: u64,
|
||||
/// Check for a new version once a day (Windows; see `updates`).
|
||||
#[serde(default = "yes")]
|
||||
check_updates: bool,
|
||||
}
|
||||
|
||||
fn yes() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -280,8 +298,32 @@ fn touch(app: State<'_, App>) {
|
||||
fn set_idle_minutes(app: State<'_, App>, minutes: u64) -> CmdResult<()> {
|
||||
let minutes = minutes.clamp(1, 240);
|
||||
app.idle_minutes.store(minutes, Ordering::Relaxed);
|
||||
let bytes = serde_json::to_vec(&Settings { idle_minutes: minutes }).map_err(|e| e.to_string())?;
|
||||
private_file::write(&app.settings_path(), &bytes).map_err(|e| e.to_string())
|
||||
app.save_settings()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- updates (Windows; see `updates`)
|
||||
|
||||
#[tauri::command]
|
||||
fn update_info(app: State<'_, App>, handle: AppHandle) -> UpdateInfo {
|
||||
app.updates.info(&handle)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn check_update(app: State<'_, App>, handle: AppHandle) -> CmdResult<UpdateInfo> {
|
||||
Ok(app.updates.check(&handle).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn set_update_checks(app: State<'_, App>, enabled: bool) -> CmdResult<()> {
|
||||
app.updates.set_automatic(enabled);
|
||||
app.save_settings()
|
||||
}
|
||||
|
||||
/// "Update and restart": returns only if it failed; on success the installer replaces and restarts the app.
|
||||
#[tauri::command]
|
||||
async fn install_update(app: State<'_, App>, handle: AppHandle) -> CmdResult<()> {
|
||||
let core = app.core.clone();
|
||||
app.updates.install(&handle, move || core.lock()).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -687,12 +729,14 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let ui = Arc::new(TauriUi { handle: app.handle().clone(), activity: activity.clone() });
|
||||
let broker = Broker::new(core.clone(), ui, PairingStore::open(data_dir.join("pairings.json"))?);
|
||||
|
||||
let idle_minutes = private_file::read(&data_dir.join("settings.json"))
|
||||
let settings = private_file::read(&data_dir.join("settings.json"))
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|b| serde_json::from_slice::<Settings>(&b).ok())
|
||||
.map_or(autolock::DEFAULT_IDLE.as_secs() / 60, |s| s.idle_minutes);
|
||||
.and_then(|b| serde_json::from_slice::<Settings>(&b).ok());
|
||||
let idle_minutes = settings.as_ref().map_or(autolock::DEFAULT_IDLE.as_secs() / 60, |s| s.idle_minutes);
|
||||
let idle_minutes = Arc::new(AtomicU64::new(idle_minutes));
|
||||
let updates = Updates::new(settings.as_ref().is_none_or(|s| s.check_updates));
|
||||
updates.clone().spawn(app.handle().clone());
|
||||
|
||||
let ipc_error = start_ipc(broker.clone()).err().map(|e| {
|
||||
eprintln!("kestrove: browser integration disabled: {e}");
|
||||
@@ -762,6 +806,7 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
ipc_error: Mutex::new(ipc_error),
|
||||
idle_minutes,
|
||||
hello_available: Arc::new(AtomicBool::new(false)),
|
||||
updates,
|
||||
clipboard: Mutex::new(None),
|
||||
});
|
||||
app.state::<App>().refresh_hello();
|
||||
@@ -769,7 +814,10 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
let builder = tauri::Builder::default();
|
||||
#[cfg(windows)]
|
||||
let builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
|
||||
builder
|
||||
.setup(setup)
|
||||
.on_window_event(|window, event| {
|
||||
if let WindowEvent::CloseRequested { api, .. } = event {
|
||||
@@ -826,7 +874,11 @@ fn main() {
|
||||
cancel_add_device,
|
||||
approve_join,
|
||||
send_join,
|
||||
kit_qr
|
||||
kit_qr,
|
||||
update_info,
|
||||
check_update,
|
||||
set_update_checks,
|
||||
install_update
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running Kestrove");
|
||||
|
||||
Reference in New Issue
Block a user