feat(desktop): тихая установка обновления по флагу --update-now

Установка обновления всегда спрашивала согласие диалогом, поэтому сквозная
проверка «поверх старой версии» не автоматизировалась (в headless-прогоне
кликнуть некому), а на управляемых машинах обновление ставит оператор, а не
пользователь. С флагом `--update-now` обёртка проверяет обновление сразу при
старте (не через 30 секунд) и ставит найденное без диалога, после чего
перезапускается; при отсутствии обновления работает как обычно.

По умолчанию флаг выключен — трей, страница настроек и фоновая проверка
по-прежнему спрашивают согласие. Подпись артефакта проверяется в обоих
режимах: флаг убирает вопрос, но не проверку. Ход тихого обновления виден в
журнале, а в строку запуска добавлена версия сборки — иначе после перезапуска
не понять, какая версия работает.

Решение — D-077, приёмка — desktop/README.md («Локальная проверка»).
This commit is contained in:
2026-09-26 18:12:35 +03:00
parent 7512857363
commit b53cf9d6e7
4 changed files with 72 additions and 37 deletions
+43 -29
View File
@@ -28,7 +28,9 @@ const UPDATE_PATH: &str = "/updates/{{target}}/{{arch}}/{{current_version}}";
/// Проверяет обновления. `interactive` — вызов из меню трея или настроек,
/// тогда пользователь видит результат в диалоге; фоновая проверка молчит.
pub async fn check<R: Runtime>(app: AppHandle<R>, interactive: bool) {
/// `auto_install` — тихий режим (`--update-now`): найденное обновление ставится
/// без вопроса, диалогов нет вовсе.
pub async fn check<R: Runtime>(app: AppHandle<R>, interactive: bool, auto_install: bool) {
if UPDATER_PUBKEY.is_empty() {
log::info!("автообновление не настроено: в сборке нет публичного ключа подписи");
if interactive {
@@ -82,7 +84,7 @@ pub async fn check<R: Runtime>(app: AppHandle<R>, interactive: bool) {
version: version.clone(),
},
);
prompt_install(app, update, version).await;
prompt_install(app, update, version, auto_install).await;
}
Ok(None) => {
log::info!("обновлений нет");
@@ -108,32 +110,42 @@ pub async fn check<R: Runtime>(app: AppHandle<R>, interactive: bool) {
}
}
/// Спрашивает согласие и устанавливает обновление.
/// Спрашивает согласие и устанавливает обновление (в тихом режиме — сразу).
async fn prompt_install<R: Runtime>(
app: AppHandle<R>,
update: tauri_plugin_updater::Update,
version: String,
auto_install: bool,
) {
let handle = app.clone();
let question = format!(
"Доступна версия {version}. Скачать и установить сейчас? Приложение будет перезапущено, \
активный голосовой звонок прервётся."
);
let (tx, rx) = std::sync::mpsc::channel::<bool>();
app.dialog()
.message(question)
.title("Обновление glchat")
.buttons(MessageDialogButtons::YesNo)
.show(move |confirmed| {
let _ = tx.send(confirmed);
});
if auto_install {
// Тихая установка: подпись артефакта проверяет плагин до распаковки —
// флаг убирает вопрос пользователю, но не проверку релиза.
log::info!(
"тихое обновление по флагу: {} → {version}",
app.package_info().version
);
} else {
let question = format!(
"Доступна версия {version}. Скачать и установить сейчас? Приложение будет перезапущено, \
активный голосовой звонок прервётся."
);
let (tx, rx) = std::sync::mpsc::channel::<bool>();
app.dialog()
.message(question)
.title("Обновление glchat")
.buttons(MessageDialogButtons::YesNo)
.show(move |confirmed| {
let _ = tx.send(confirmed);
});
// Диалог асинхронный: ждём ответ, не блокируя основной поток.
let confirmed = tauri::async_runtime::spawn_blocking(move || rx.recv().unwrap_or(false))
.await
.unwrap_or(false);
if !confirmed {
return;
// Диалог асинхронный: ждём ответ, не блокируя основной поток.
let confirmed = tauri::async_runtime::spawn_blocking(move || rx.recv().unwrap_or(false))
.await
.unwrap_or(false);
if !confirmed {
return;
}
}
match update
@@ -146,14 +158,16 @@ async fn prompt_install<R: Runtime>(
}
Err(error) => {
log::error!("установка обновления не удалась: {error}");
app.dialog()
.message(format!(
"Обновление не установлено: {error}. Скачать установщик вручную можно на \
странице инстанса."
))
.title("Обновление glchat")
.buttons(MessageDialogButtons::Ok)
.show(|_| {});
if !auto_install {
app.dialog()
.message(format!(
"Обновление не установлено: {error}. Скачать установщик вручную можно на \
странице инстанса."
))
.title("Обновление glchat")
.buttons(MessageDialogButtons::Ok)
.show(|_| {});
}
}
}
}