mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-10 20:32:21 +08:00
feat(linux): show when an upgrade could not restore the Service state
Refs #8515
This commit is contained in:
@@ -14,8 +14,19 @@ UPGRADE_MARKER="/run/clash-verge-service-upgrade"
|
||||
# Read by /usr/bin/clash-verge-service-uninstall to keep the Service through the previous %preun.
|
||||
TRANSACTION_RECORD="/run/clash-verge-service-rpm-upgrade"
|
||||
|
||||
# Set to the previous state when restoring it fails; the app shows it even if the Service is ready.
|
||||
UNRESTORED_STATE=""
|
||||
|
||||
write_upgrade_marker() {
|
||||
(umask 022 && printf 'gid=%s\nid=%s\n' "$1" "$(date +%s%N)" >"$UPGRADE_MARKER")
|
||||
(
|
||||
umask 022
|
||||
{
|
||||
printf 'gid=%s\nid=%s\n' "$1" "$(date +%s%N)"
|
||||
if [ -n "$UNRESTORED_STATE" ]; then
|
||||
printf 'restore=%s\n' "$UNRESTORED_STATE"
|
||||
fi
|
||||
} >"$UPGRADE_MARKER"
|
||||
)
|
||||
}
|
||||
|
||||
# Scriptlets run as children of the process running the rpm transaction; its start time
|
||||
@@ -62,7 +73,8 @@ reinstall_existing_service() {
|
||||
systemctl stop "$SERVICE_UNIT" || restored=no
|
||||
fi
|
||||
if [ "$restored" = "no" ]; then
|
||||
echo "Warning: restoring the previous Clash Verge Service state (${enabled:-unknown}, active=$active) failed; check it with systemctl." >&2
|
||||
UNRESTORED_STATE="${enabled:-unknown},$([ "$active" = yes ] && echo active || echo inactive)"
|
||||
echo "Warning: restoring the previous Clash Verge Service state ($UNRESTORED_STATE) failed; check it with systemctl." >&2
|
||||
status=1
|
||||
fi
|
||||
return "$status"
|
||||
|
||||
@@ -10,8 +10,19 @@ SERVICE_UNIT_FILE="/etc/systemd/system/$SERVICE_UNIT"
|
||||
# Read by the app, which offers a reinstall when the package could not keep the Service.
|
||||
UPGRADE_MARKER="/run/clash-verge-service-upgrade"
|
||||
|
||||
# Set to the previous state when restoring it fails; the app shows it even if the Service is ready.
|
||||
UNRESTORED_STATE=""
|
||||
|
||||
write_upgrade_marker() {
|
||||
(umask 022 && printf 'gid=%s\nid=%s\n' "$1" "$(date +%s%N)" >"$UPGRADE_MARKER")
|
||||
(
|
||||
umask 022
|
||||
{
|
||||
printf 'gid=%s\nid=%s\n' "$1" "$(date +%s%N)"
|
||||
if [ -n "$UNRESTORED_STATE" ]; then
|
||||
printf 'restore=%s\n' "$UNRESTORED_STATE"
|
||||
fi
|
||||
} >"$UPGRADE_MARKER"
|
||||
)
|
||||
}
|
||||
|
||||
existing_service_gid() {
|
||||
@@ -49,7 +60,8 @@ reinstall_existing_service() {
|
||||
systemctl stop "$SERVICE_UNIT" || restored=no
|
||||
fi
|
||||
if [ "$restored" = "no" ]; then
|
||||
echo "Warning: restoring the previous Clash Verge Service state (${enabled:-unknown}, active=$active) failed; check it with systemctl." >&2
|
||||
UNRESTORED_STATE="${enabled:-unknown},$([ "$active" = yes ] && echo active || echo inactive)"
|
||||
echo "Warning: restoring the previous Clash Verge Service state ($UNRESTORED_STATE) failed; check it with systemctl." >&2
|
||||
status=1
|
||||
fi
|
||||
return "$status"
|
||||
|
||||
@@ -24,6 +24,9 @@ pub enum NoticeStatus {
|
||||
#[serde(rename = "service_core::sidecar_fallback")]
|
||||
#[cfg_attr(not(any(target_os = "windows", target_os = "linux")), allow(dead_code))]
|
||||
ServiceCoreSidecarFallback,
|
||||
#[serde(rename = "service_core::upgrade_state_not_restored")]
|
||||
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
ServiceCoreUpgradeStateNotRestored,
|
||||
#[serde(rename = "dns_override::auto_disabled")]
|
||||
DnsOverrideAutoDisabled,
|
||||
#[serde(rename = "enhance::discarded_keys")]
|
||||
|
||||
@@ -92,16 +92,42 @@ pub(crate) async fn notify_incomplete_service_upgrade() {
|
||||
return;
|
||||
}
|
||||
let status = SERVICE_MANAGER.current().await;
|
||||
if !matches!(status, ServiceStatus::Ready) {
|
||||
logging!(warn, Type::Service, "安装包升级后服务未就绪: {status:?}");
|
||||
*PENDING_SERVICE_FALLBACK_NOTICE.lock() = Some(SERVICE_UPGRADE_INCOMPLETE.to_owned());
|
||||
Handle::notice(NoticeStatus::ServiceCoreSidecarFallback, "");
|
||||
match service_upgrade_notice(&marker, matches!(status, ServiceStatus::Ready)) {
|
||||
Some(ServiceUpgradeNotice::Reinstall) => {
|
||||
logging!(warn, Type::Service, "安装包升级后服务未就绪: {status:?}");
|
||||
*PENDING_SERVICE_FALLBACK_NOTICE.lock() = Some(SERVICE_UPGRADE_INCOMPLETE.to_owned());
|
||||
Handle::notice(NoticeStatus::ServiceCoreSidecarFallback, "");
|
||||
}
|
||||
Some(ServiceUpgradeNotice::StateNotRestored(state)) => {
|
||||
logging!(warn, Type::Service, "安装包升级后未能恢复服务原状态: {state}");
|
||||
Handle::notice(NoticeStatus::ServiceCoreUpgradeStateNotRestored, state);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
if let Err(error) = std::fs::write(&ack, id) {
|
||||
logging!(warn, Type::Service, "无法记录服务升级确认: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum ServiceUpgradeNotice<'a> {
|
||||
Reinstall,
|
||||
/// A reinstall would enable and start the Service again, so it cannot restore this state.
|
||||
StateNotRestored(&'a str),
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn service_upgrade_notice(marker: &str, ready: bool) -> Option<ServiceUpgradeNotice<'_>> {
|
||||
if !ready {
|
||||
return Some(ServiceUpgradeNotice::Reinstall);
|
||||
}
|
||||
marker
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("restore="))
|
||||
.map(ServiceUpgradeNotice::StateNotRestored)
|
||||
}
|
||||
|
||||
pub(crate) fn take_service_fallback_notice() -> Option<ServiceFallbackNotice> {
|
||||
let reason = PENDING_SERVICE_FALLBACK_NOTICE.lock().take()?;
|
||||
Some(if reason == SERVICE_UPGRADE_INCOMPLETE {
|
||||
@@ -2167,6 +2193,8 @@ mod tests {
|
||||
generate_service_session_token, mark_service_unavailable_after_owner_loss, owner_recovery_policy,
|
||||
service_core_path_for, session_matches_status,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
use super::{ServiceUpgradeNotice, service_upgrade_notice};
|
||||
#[cfg(unix)]
|
||||
use super::{service_core_path_for_with_publisher, service_tool_path_for};
|
||||
use crate::core::runstate::{FakeEnv, OwnerRecoveryReason, PendingAction, RunStateStore};
|
||||
@@ -2824,4 +2852,24 @@ mod tests {
|
||||
assert!(!store.state().sidecar_allowed);
|
||||
assert_eq!(status_of(&store), ServiceStatus::Ready);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn upgrade_marker_reports_unrestored_state_only_for_a_ready_service() {
|
||||
let restored = "gid=995\nid=1\n";
|
||||
let unrestored = "gid=995\nid=1\nrestore=disabled,inactive\n";
|
||||
assert_eq!(
|
||||
service_upgrade_notice(restored, false),
|
||||
Some(ServiceUpgradeNotice::Reinstall)
|
||||
);
|
||||
assert_eq!(
|
||||
service_upgrade_notice(unrestored, false),
|
||||
Some(ServiceUpgradeNotice::Reinstall)
|
||||
);
|
||||
assert_eq!(service_upgrade_notice(restored, true), None);
|
||||
assert_eq!(
|
||||
service_upgrade_notice(unrestored, true),
|
||||
Some(ServiceUpgradeNotice::StateNotRestored("disabled,inactive"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "تعذّر على الخدمة التأكد من أن ملفات النواة ومجلداتها آمنة للتشغيل بصلاحيات مرتفعة.",
|
||||
"permissionRepairGuide": "عرض خطوات الإصلاح",
|
||||
"sidecarFallback": "تعذّر على الخدمة تشغيل النواة. تم التبديل إلى وضع Sidecar؛ ولا تزال الخدمة غير متاحة.",
|
||||
"upgradeStateNotRestored": "أعادت الترقية تثبيت خدمة Clash Verge لكنها لم تتمكن من استعادة حالة بدء التشغيل والتشغيل السابقة ({{state}}). لن تؤدي إعادة تثبيت الخدمة إلى استعادتها؛ تحقق منها باستخدام systemctl status clash-verge-service واضبطها باستخدام systemctl.",
|
||||
"uninstallSuccess": "تم إلغاء تثبيت الخدمة بنجاح"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "Der Dienst konnte nicht bestätigen, dass die Kerndateien und Ordner für die Ausführung mit erhöhten Rechten sicher sind.",
|
||||
"permissionRepairGuide": "Reparaturanleitung öffnen",
|
||||
"sidecarFallback": "Der Dienst konnte den Kern nicht starten. Die Anwendung läuft im Sidecar-Modus weiter; der Dienst bleibt nicht verfügbar.",
|
||||
"upgradeStateNotRestored": "Das Upgrade hat den Clash Verge-Dienst neu installiert, konnte aber seinen vorherigen Autostart- und Laufzustand ({{state}}) nicht wiederherstellen. Eine Neuinstallation des Dienstes stellt ihn nicht wieder her; prüfen Sie ihn mit systemctl status clash-verge-service und passen Sie ihn mit systemctl an.",
|
||||
"uninstallSuccess": "Service erfolgreich deinstalliert"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "The service could not confirm that the core files and folders are safe to run with elevated privileges.",
|
||||
"permissionRepairGuide": "View repair instructions",
|
||||
"sidecarFallback": "The service could not start the core. Switched to Sidecar mode; the service remains unavailable.",
|
||||
"upgradeStateNotRestored": "The upgrade reinstalled the Clash Verge service but could not restore its previous startup and running state ({{state}}). Reinstalling the service will not restore it; check it with systemctl status clash-verge-service and adjust it with systemctl.",
|
||||
"uninstallSuccess": "Service Uninstalled Successfully"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "El servicio no pudo confirmar que los archivos y carpetas del núcleo sean seguros para ejecutarlos con privilegios elevados.",
|
||||
"permissionRepairGuide": "Ver instrucciones de reparación",
|
||||
"sidecarFallback": "El servicio no pudo iniciar el núcleo. Se ha cambiado al modo Sidecar; el servicio sigue sin estar disponible.",
|
||||
"upgradeStateNotRestored": "La actualización reinstaló el servicio de Clash Verge, pero no pudo restaurar su estado anterior de inicio y ejecución ({{state}}). Reinstalar el servicio no lo restaurará; compruébelo con systemctl status clash-verge-service y ajústelo con systemctl.",
|
||||
"uninstallSuccess": "Servicio desinstalado con éxito"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "سرویس نتوانست ایمن بودن فایلها و پوشههای هسته را برای اجرا با دسترسی بالا تأیید کند.",
|
||||
"permissionRepairGuide": "مشاهدهٔ راهنمای رفع مشکل",
|
||||
"sidecarFallback": "سرویس نتوانست هسته را اجرا کند. اجرا در حالت Sidecar ادامه مییابد؛ سرویس همچنان در دسترس نیست.",
|
||||
"upgradeStateNotRestored": "ارتقا سرویس Clash Verge را دوباره نصب کرد اما نتوانست وضعیت قبلی راهاندازی خودکار و اجرای آن ({{state}}) را بازگرداند. نصب دوباره سرویس آن را بازنمیگرداند؛ با systemctl status clash-verge-service آن را بررسی و با systemctl تنظیم کنید.",
|
||||
"uninstallSuccess": "سرویس با موفقیت حذف نصب شد"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "Layanan tidak dapat memastikan bahwa berkas dan folder inti aman dijalankan dengan hak istimewa.",
|
||||
"permissionRepairGuide": "Lihat panduan perbaikan",
|
||||
"sidecarFallback": "Layanan tidak dapat memulai inti. Beralih ke mode Sidecar; layanan masih tidak tersedia.",
|
||||
"upgradeStateNotRestored": "Pembaruan telah memasang ulang layanan Clash Verge tetapi tidak dapat memulihkan status mulai otomatis dan berjalan sebelumnya ({{state}}). Memasang ulang layanan tidak akan memulihkannya; periksa dengan systemctl status clash-verge-service dan sesuaikan dengan systemctl.",
|
||||
"uninstallSuccess": "Layanan Berhasil Dicopot"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "コアファイルとフォルダーを高い権限で安全に実行できるか確認できませんでした。",
|
||||
"permissionRepairGuide": "修復方法を見る",
|
||||
"sidecarFallback": "サービスでコアを起動できなかったため、Sidecar モードで動作を継続しています。サービスは引き続き利用できません。",
|
||||
"upgradeStateNotRestored": "アップグレードで Clash Verge サービスを再インストールしましたが、以前の自動起動と実行状態({{state}})を復元できませんでした。サービスを再インストールしても復元されません。systemctl status clash-verge-service で状態を確認し、systemctl で調整してください。",
|
||||
"uninstallSuccess": "サービスのアンインストールに成功しました。"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "서비스가 코어 파일과 폴더를 높은 권한으로 안전하게 실행할 수 있는지 확인하지 못했습니다.",
|
||||
"permissionRepairGuide": "해결 방법 보기",
|
||||
"sidecarFallback": "서비스가 코어를 시작하지 못해 Sidecar 모드로 계속 실행합니다. 서비스는 여전히 사용할 수 없습니다.",
|
||||
"upgradeStateNotRestored": "업그레이드 중 Clash Verge 서비스를 다시 설치했지만 이전 부팅 시 시작 및 실행 상태({{state}})를 복원하지 못했습니다. 서비스를 다시 설치해도 복원되지 않습니다. systemctl status clash-verge-service로 상태를 확인하고 systemctl로 조정하세요.",
|
||||
"uninstallSuccess": "서비스가 제거되었습니다"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "O serviço não conseguiu confirmar que os arquivos e pastas do núcleo são seguros para execução com privilégios elevados.",
|
||||
"permissionRepairGuide": "Ver instruções de reparo",
|
||||
"sidecarFallback": "O serviço não conseguiu iniciar o núcleo. Mudou para o modo Sidecar; o serviço continua indisponível.",
|
||||
"upgradeStateNotRestored": "A atualização reinstalou o serviço do Clash Verge, mas não conseguiu restaurar o estado anterior de inicialização e execução ({{state}}). Reinstalar o serviço não o restaurará; verifique-o com systemctl status clash-verge-service e ajuste-o com systemctl.",
|
||||
"uninstallSuccess": "Serviço Desinstalado com Sucesso"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "Служба не смогла подтвердить безопасность файлов и папок ядра для запуска с повышенными правами.",
|
||||
"permissionRepairGuide": "Инструкция по исправлению",
|
||||
"sidecarFallback": "Служба не смогла запустить ядро. Работа продолжена в режиме Sidecar; служба по-прежнему недоступна.",
|
||||
"upgradeStateNotRestored": "Обновление переустановило службу Clash Verge, но не смогло восстановить её прежнее состояние автозапуска и работы ({{state}}). Переустановка службы его не восстановит; проверьте её командой systemctl status clash-verge-service и настройте через systemctl.",
|
||||
"uninstallSuccess": "Служба успешно удалена"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "Hizmet, çekirdek dosya ve klasörlerinin yükseltilmiş yetkilerle güvenle çalıştırılabileceğini doğrulayamadı.",
|
||||
"permissionRepairGuide": "Düzeltme adımlarını görüntüle",
|
||||
"sidecarFallback": "Hizmet çekirdeği başlatamadı. Sidecar moduna geçildi; hizmet hâlâ kullanılamıyor.",
|
||||
"upgradeStateNotRestored": "Yükseltme Clash Verge hizmetini yeniden kurdu ancak önceki açılışta başlatma ve çalışma durumunu ({{state}}) geri yükleyemedi. Hizmeti yeniden kurmak bunu geri yüklemez; systemctl status clash-verge-service ile kontrol edin ve systemctl ile ayarlayın.",
|
||||
"uninstallSuccess": "Hizmet Başarıyla Kaldırıldı"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "Хезмәт төш файлларын һәм папкаларын югары хокуклар белән имин эшләтеп булачагын раслый алмады.",
|
||||
"permissionRepairGuide": "Төзәтү күрсәтмәләрен карау",
|
||||
"sidecarFallback": "Хезмәт төшне эшләтеп җибәрә алмады. Эш Sidecar режимында дәвам итә; хезмәт һаман да эшләми.",
|
||||
"upgradeStateNotRestored": "Яңарту Clash Verge хезмәтен яңадан урнаштырды, ләкин аның элекке автоҗибәрү һәм эшләү халәтен ({{state}}) кайтара алмады. Хезмәтне яңадан урнаштыру аны кайтармый; аны systemctl status clash-verge-service белән тикшерегез һәм systemctl белән көйләгез.",
|
||||
"uninstallSuccess": "Сервис уңышлы салдырылды"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "服务无法确认内核文件及所在目录是否安全,已拒绝高权限运行。",
|
||||
"permissionRepairGuide": "查看修复方法",
|
||||
"sidecarFallback": "服务无法启动内核,已降级为 Sidecar 模式运行,服务仍不可用。",
|
||||
"upgradeStateNotRestored": "升级已重装 Clash Verge 服务,但未能恢复其原有的开机启动和运行状态({{state}})。重新安装服务也无法恢复该状态;请用 systemctl status clash-verge-service 检查服务状态,并用 systemctl 调整。",
|
||||
"uninstallSuccess": "已成功卸载服务"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -697,6 +697,7 @@
|
||||
"permissionRejectedReason": "服務無法確認核心檔案及所在目錄是否安全,已拒絕以高權限執行。",
|
||||
"permissionRepairGuide": "查看修復方法",
|
||||
"sidecarFallback": "服務無法啟動核心,已降級為 Sidecar 模式執行,服務仍無法使用。",
|
||||
"upgradeStateNotRestored": "升級已重新安裝 Clash Verge 服務,但未能恢復其原有的開機啟動和執行狀態({{state}})。重新安裝服務也無法恢復該狀態;請用 systemctl status clash-verge-service 檢查服務狀態,並用 systemctl 調整。",
|
||||
"uninstallSuccess": "已成功解除安裝服務"
|
||||
},
|
||||
"updater": {
|
||||
|
||||
@@ -618,6 +618,7 @@ export type NoticeStatus =
|
||||
| 'service_core::repair_required'
|
||||
| 'service_core::app_data_not_owned'
|
||||
| 'service_core::sidecar_fallback'
|
||||
| 'service_core::upgrade_state_not_restored'
|
||||
| 'dns_override::auto_disabled'
|
||||
| 'enhance::discarded_keys'
|
||||
| 'tun_mode::auto_disabled'
|
||||
|
||||
@@ -140,6 +140,12 @@ const handlers: Record<
|
||||
)
|
||||
})
|
||||
},
|
||||
'service_core::upgrade_state_not_restored': (msg) =>
|
||||
showNotice.warning(
|
||||
'settings.feedback.notifications.clashService.upgradeStateNotRestored',
|
||||
{ state: msg },
|
||||
0,
|
||||
),
|
||||
'dns_override::auto_disabled': () => {
|
||||
void takeDnsOverrideNotice()
|
||||
.then((pending) => {
|
||||
|
||||
@@ -821,6 +821,7 @@ export const translationKeys = [
|
||||
'settings.feedback.notifications.clashService.permissionRejectedReason',
|
||||
'settings.feedback.notifications.clashService.permissionRepairGuide',
|
||||
'settings.feedback.notifications.clashService.sidecarFallback',
|
||||
'settings.feedback.notifications.clashService.upgradeStateNotRestored',
|
||||
'settings.feedback.notifications.clashService.uninstallSuccess',
|
||||
'settings.feedback.notifications.updater.withClashProxySuccess',
|
||||
'settings.feedback.notifications.updater.withClashProxyFailed',
|
||||
|
||||
@@ -858,6 +858,7 @@ export interface TranslationResources {
|
||||
permissionWritableReason: string
|
||||
sidecarFallback: string
|
||||
uninstallSuccess: string
|
||||
upgradeStateNotRestored: string
|
||||
}
|
||||
updater: {
|
||||
withClashProxyFailed: string
|
||||
|
||||
Reference in New Issue
Block a user