feat(desktop): тихая установка обновления по флагу --update-now
Установка обновления всегда спрашивала согласие диалогом, поэтому сквозная проверка «поверх старой версии» не автоматизировалась (в headless-прогоне кликнуть некому), а на управляемых машинах обновление ставит оператор, а не пользователь. С флагом `--update-now` обёртка проверяет обновление сразу при старте (не через 30 секунд) и ставит найденное без диалога, после чего перезапускается; при отсутствии обновления работает как обычно. По умолчанию флаг выключен — трей, страница настроек и фоновая проверка по-прежнему спрашивают согласие. Подпись артефакта проверяется в обоих режимах: флаг убирает вопрос, но не проверку. Ход тихого обновления виден в журнале, а в строку запуска добавлена версия сборки — иначе после перезапуска не понять, какая версия работает. Решение — D-077, приёмка — desktop/README.md («Локальная проверка»).
This commit is contained in:
@@ -120,7 +120,7 @@ pub fn desktop_reload<R: Runtime>(app: AppHandle<R>) -> Result<(), String> {
|
|||||||
/// Проверяет обновления (кнопка на странице настроек).
|
/// Проверяет обновления (кнопка на странице настроек).
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn desktop_check_updates<R: Runtime>(app: AppHandle<R>) {
|
pub async fn desktop_check_updates<R: Runtime>(app: AppHandle<R>) {
|
||||||
updates::check(app, true).await;
|
updates::check(app, true, false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Открывает ссылку в системном браузере.
|
/// Открывает ссылку в системном браузере.
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ struct Cli {
|
|||||||
settings: bool,
|
settings: bool,
|
||||||
/// Принудительно включить или выключить автозапуск (установка скриптом).
|
/// Принудительно включить или выключить автозапуск (установка скриптом).
|
||||||
autostart: Option<bool>,
|
autostart: Option<bool>,
|
||||||
|
/// Проверить обновление сразу при старте и установить его без диалога
|
||||||
|
/// (тихое обновление управляемых машин; по умолчанию выключено).
|
||||||
|
update_now: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
@@ -60,6 +63,7 @@ impl Cli {
|
|||||||
"--instance" => cli.instance = args.next(),
|
"--instance" => cli.instance = args.next(),
|
||||||
"--minimized" => cli.minimized = true,
|
"--minimized" => cli.minimized = true,
|
||||||
"--settings" => cli.settings = true,
|
"--settings" => cli.settings = true,
|
||||||
|
"--update-now" => cli.update_now = true,
|
||||||
"--autostart" => cli.autostart = args.next().as_deref().and_then(parse_switch),
|
"--autostart" => cli.autostart = args.next().as_deref().and_then(parse_switch),
|
||||||
other => {
|
other => {
|
||||||
if let Some(value) = other.strip_prefix("--instance=") {
|
if let Some(value) = other.strip_prefix("--instance=") {
|
||||||
@@ -250,8 +254,11 @@ fn setup(app: &tauri::AppHandle, cli: &Cli) {
|
|||||||
|
|
||||||
deeplink::install(app);
|
deeplink::install(app);
|
||||||
|
|
||||||
|
// Версия в журнале: после автообновления это единственный след того, какая
|
||||||
|
// сборка реально запустилась (проверка обновления сама версию не печатает).
|
||||||
log::info!(
|
log::info!(
|
||||||
"обёртка запущена: адрес={}, окно={}, автозапуск={}",
|
"обёртка запущена: версия={}, адрес={}, окно={}, автозапуск={}",
|
||||||
|
app.package_info().version,
|
||||||
current.instance_url,
|
current.instance_url,
|
||||||
if cli.minimized {
|
if cli.minimized {
|
||||||
"свёрнуто"
|
"свёрнуто"
|
||||||
@@ -267,16 +274,27 @@ fn setup(app: &tauri::AppHandle, cli: &Cli) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn_update_check(app.clone());
|
spawn_update_check(app.clone(), cli.update_now);
|
||||||
spawn_autostart_prompt(app.clone());
|
spawn_autostart_prompt(app.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Фоновая проверка обновлений при старте (без диалогов).
|
/// Проверка обновлений при старте.
|
||||||
fn spawn_update_check(app: tauri::AppHandle) {
|
///
|
||||||
|
/// Обычный режим — фоновая проверка через [`UPDATE_CHECK_DELAY`], найденное
|
||||||
|
/// обновление ставится только после подтверждения пользователя. С флагом
|
||||||
|
/// `--update-now` проверка идёт сразу, а установка — без диалога: это режим
|
||||||
|
/// управляемых машин, где обновление ставит оператор, а не пользователь
|
||||||
|
/// (desktop/README.md, «Автообновление»). Подпись проверяется в обоих режимах:
|
||||||
|
/// флаг убирает только вопрос, а не проверку артефакта.
|
||||||
|
fn spawn_update_check(app: tauri::AppHandle, update_now: bool) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
if update_now {
|
||||||
|
log::info!("тихое обновление по флагу --update-now: проверка сразу при старте");
|
||||||
|
} else {
|
||||||
std::thread::sleep(UPDATE_CHECK_DELAY);
|
std::thread::sleep(UPDATE_CHECK_DELAY);
|
||||||
|
}
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
updates::check(app, false).await;
|
updates::check(app, false, update_now).await;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -332,6 +350,9 @@ mod tests {
|
|||||||
assert!(cli.minimized);
|
assert!(cli.minimized);
|
||||||
assert!(cli.settings);
|
assert!(cli.settings);
|
||||||
assert_eq!(cli.autostart, None);
|
assert_eq!(cli.autostart, None);
|
||||||
|
// Тихая установка обновления включается только явным флагом.
|
||||||
|
assert!(!cli.update_now);
|
||||||
|
assert!(parse(&["--update-now"]).update_now);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ fn on_menu_event<R: Runtime>(app: &AppHandle<R>, event: tauri::menu::MenuEvent)
|
|||||||
"updates" => {
|
"updates" => {
|
||||||
let handle = app.clone();
|
let handle = app.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
updates::check(handle, true).await;
|
updates::check(handle, true, false).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Выход из трея — такой же выход, как закрытие окна: если в клиенте
|
// Выход из трея — такой же выход, как закрытие окна: если в клиенте
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ const UPDATE_PATH: &str = "/updates/{{target}}/{{arch}}/{{current_version}}";
|
|||||||
|
|
||||||
/// Проверяет обновления. `interactive` — вызов из меню трея или настроек,
|
/// Проверяет обновления. `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() {
|
if UPDATER_PUBKEY.is_empty() {
|
||||||
log::info!("автообновление не настроено: в сборке нет публичного ключа подписи");
|
log::info!("автообновление не настроено: в сборке нет публичного ключа подписи");
|
||||||
if interactive {
|
if interactive {
|
||||||
@@ -82,7 +84,7 @@ pub async fn check<R: Runtime>(app: AppHandle<R>, interactive: bool) {
|
|||||||
version: version.clone(),
|
version: version.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
prompt_install(app, update, version).await;
|
prompt_install(app, update, version, auto_install).await;
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
log::info!("обновлений нет");
|
log::info!("обновлений нет");
|
||||||
@@ -108,13 +110,22 @@ pub async fn check<R: Runtime>(app: AppHandle<R>, interactive: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Спрашивает согласие и устанавливает обновление.
|
/// Спрашивает согласие и устанавливает обновление (в тихом режиме — сразу).
|
||||||
async fn prompt_install<R: Runtime>(
|
async fn prompt_install<R: Runtime>(
|
||||||
app: AppHandle<R>,
|
app: AppHandle<R>,
|
||||||
update: tauri_plugin_updater::Update,
|
update: tauri_plugin_updater::Update,
|
||||||
version: String,
|
version: String,
|
||||||
|
auto_install: bool,
|
||||||
) {
|
) {
|
||||||
let handle = app.clone();
|
let handle = app.clone();
|
||||||
|
if auto_install {
|
||||||
|
// Тихая установка: подпись артефакта проверяет плагин до распаковки —
|
||||||
|
// флаг убирает вопрос пользователю, но не проверку релиза.
|
||||||
|
log::info!(
|
||||||
|
"тихое обновление по флагу: {} → {version}",
|
||||||
|
app.package_info().version
|
||||||
|
);
|
||||||
|
} else {
|
||||||
let question = format!(
|
let question = format!(
|
||||||
"Доступна версия {version}. Скачать и установить сейчас? Приложение будет перезапущено, \
|
"Доступна версия {version}. Скачать и установить сейчас? Приложение будет перезапущено, \
|
||||||
активный голосовой звонок прервётся."
|
активный голосовой звонок прервётся."
|
||||||
@@ -135,6 +146,7 @@ async fn prompt_install<R: Runtime>(
|
|||||||
if !confirmed {
|
if !confirmed {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match update
|
match update
|
||||||
.download_and_install(|_chunk, _total| {}, || {})
|
.download_and_install(|_chunk, _total| {}, || {})
|
||||||
@@ -146,6 +158,7 @@ async fn prompt_install<R: Runtime>(
|
|||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::error!("установка обновления не удалась: {error}");
|
log::error!("установка обновления не удалась: {error}");
|
||||||
|
if !auto_install {
|
||||||
app.dialog()
|
app.dialog()
|
||||||
.message(format!(
|
.message(format!(
|
||||||
"Обновление не установлено: {error}. Скачать установщик вручную можно на \
|
"Обновление не установлено: {error}. Скачать установщик вручную можно на \
|
||||||
@@ -157,6 +170,7 @@ async fn prompt_install<R: Runtime>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|||||||
Reference in New Issue
Block a user