feat: add remote daemon self-update from client (#1513)
* feat: add remote daemon self-update from client
Adds a daemon.update.request RPC that lets the client trigger a remote
daemon update over WebSocket. The daemon runs npm update -g @getpaseo/cli,
reports progress via daemon_update_progress status events, then restarts
through the supervisor to load the new code.
New feature flag server_info.features.daemonSelfUpdate gates the UI.
The UpdateDaemonCard appears on the host settings page when the daemon
supports self-update and the app/daemon versions differ.
* fix: clean up progress subscription on unmount, use typed phase cast
Address Greptile review:
- P1: store unsubscribe ref and clean up on component unmount to prevent
listener leak during in-flight update
- P2: use DaemonUpdateProgressStatusPayload type instead of { phase?: string }
* fix: use npm install@latest and global concurrency guard
Address Greptile review:
- npm update -g can silently exit 0 without resolving major-version
gaps; switch to npm install -g @getpaseo/cli@latest
- Move daemonUpdateInProgress from per-session instance to module-level
variable so concurrent clients share the same guard
* feat(daemon): harden remote self-update
Probe the global npm CLI install before updating, keep request handling in a daemon self-update session controller, and emit scoped daemon.update.progress messages.
* test(daemon): make self-update paths platform-stable
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
@@ -1745,6 +1745,30 @@ export const ar: TranslationResources = {
|
||||
"فشل في إرسال طلب إعادة التشغيل. يقوم Paseo بإعادة الاتصال تلقائيًا - حاول مرة أخرى بمجرد ظهور المضيف على أنه متصل بالإنترنت.",
|
||||
dialogFailedMessage: "غير قادر على فتح مربع حوار تأكيد إعادة التشغيل.",
|
||||
},
|
||||
update: {
|
||||
title: "Update daemon",
|
||||
hint: "Update the daemon to the latest version and restart it",
|
||||
confirm: "Update",
|
||||
confirmTitle: "Update {{name}}",
|
||||
confirmMessage:
|
||||
"This will update the daemon to the latest version and restart it. Running agents will be briefly interrupted.",
|
||||
updating: "Updating...",
|
||||
phaseStarting: "Preparing update...",
|
||||
phaseDownloading: "Downloading packages...",
|
||||
phaseInstalling: "Installing...",
|
||||
phaseComplete: "Update complete, restarting...",
|
||||
unableToReconnectTitle: "Unable to reconnect",
|
||||
unableToReconnectMessage:
|
||||
"{{name}} did not come back online after updating. Please verify the daemon restarted.",
|
||||
unavailableTitle: "Host unavailable",
|
||||
unavailableMessage:
|
||||
"This host is not connected. Wait for it to come online before updating.",
|
||||
offlineTitle: "Host offline",
|
||||
offlineMessage: "This host is offline. Wait until it is back online before updating.",
|
||||
requestFailedTitle: "Update failed",
|
||||
requestFailedMessage: "Failed to update the daemon: {{error}}",
|
||||
dialogFailedMessage: "Unable to open the update confirmation dialog.",
|
||||
},
|
||||
dangerZone: "منطقة الخطر",
|
||||
remove: {
|
||||
title: "إزالة المضيف",
|
||||
|
||||
@@ -1753,6 +1753,30 @@ export const en = {
|
||||
"Failed to send the restart request. Paseo reconnects automatically-try again once the host shows as online.",
|
||||
dialogFailedMessage: "Unable to open the restart confirmation dialog.",
|
||||
},
|
||||
update: {
|
||||
title: "Update daemon",
|
||||
hint: "Update the daemon to the latest version and restart it",
|
||||
confirm: "Update",
|
||||
confirmTitle: "Update {{name}}",
|
||||
confirmMessage:
|
||||
"This will update the daemon to the latest version and restart it. Running agents will be briefly interrupted.",
|
||||
updating: "Updating...",
|
||||
phaseStarting: "Preparing update...",
|
||||
phaseDownloading: "Downloading packages...",
|
||||
phaseInstalling: "Installing...",
|
||||
phaseComplete: "Update complete, restarting...",
|
||||
unableToReconnectTitle: "Unable to reconnect",
|
||||
unableToReconnectMessage:
|
||||
"{{name}} did not come back online after updating. Please verify the daemon restarted.",
|
||||
unavailableTitle: "Host unavailable",
|
||||
unavailableMessage:
|
||||
"This host is not connected. Wait for it to come online before updating.",
|
||||
offlineTitle: "Host offline",
|
||||
offlineMessage: "This host is offline. Wait until it is back online before updating.",
|
||||
requestFailedTitle: "Update failed",
|
||||
requestFailedMessage: "Failed to update the daemon: {{error}}",
|
||||
dialogFailedMessage: "Unable to open the update confirmation dialog.",
|
||||
},
|
||||
dangerZone: "Danger zone",
|
||||
remove: {
|
||||
title: "Remove host",
|
||||
|
||||
@@ -1786,6 +1786,30 @@ export const es: TranslationResources = {
|
||||
dialogFailedMessage:
|
||||
"No se puede abrir el cuadro de diálogo de confirmación de reinicio.",
|
||||
},
|
||||
update: {
|
||||
title: "Update daemon",
|
||||
hint: "Update the daemon to the latest version and restart it",
|
||||
confirm: "Update",
|
||||
confirmTitle: "Update {{name}}",
|
||||
confirmMessage:
|
||||
"This will update the daemon to the latest version and restart it. Running agents will be briefly interrupted.",
|
||||
updating: "Updating...",
|
||||
phaseStarting: "Preparing update...",
|
||||
phaseDownloading: "Downloading packages...",
|
||||
phaseInstalling: "Installing...",
|
||||
phaseComplete: "Update complete, restarting...",
|
||||
unableToReconnectTitle: "Unable to reconnect",
|
||||
unableToReconnectMessage:
|
||||
"{{name}} did not come back online after updating. Please verify the daemon restarted.",
|
||||
unavailableTitle: "Host unavailable",
|
||||
unavailableMessage:
|
||||
"This host is not connected. Wait for it to come online before updating.",
|
||||
offlineTitle: "Host offline",
|
||||
offlineMessage: "This host is offline. Wait until it is back online before updating.",
|
||||
requestFailedTitle: "Update failed",
|
||||
requestFailedMessage: "Failed to update the daemon: {{error}}",
|
||||
dialogFailedMessage: "Unable to open the update confirmation dialog.",
|
||||
},
|
||||
dangerZone: "Zona de peligro",
|
||||
remove: {
|
||||
title: "Eliminar host",
|
||||
|
||||
@@ -1791,6 +1791,30 @@ export const fr: TranslationResources = {
|
||||
dialogFailedMessage:
|
||||
"Impossible d'ouvrir la boîte de dialogue de confirmation de redémarrage.",
|
||||
},
|
||||
update: {
|
||||
title: "Update daemon",
|
||||
hint: "Update the daemon to the latest version and restart it",
|
||||
confirm: "Update",
|
||||
confirmTitle: "Update {{name}}",
|
||||
confirmMessage:
|
||||
"This will update the daemon to the latest version and restart it. Running agents will be briefly interrupted.",
|
||||
updating: "Updating...",
|
||||
phaseStarting: "Preparing update...",
|
||||
phaseDownloading: "Downloading packages...",
|
||||
phaseInstalling: "Installing...",
|
||||
phaseComplete: "Update complete, restarting...",
|
||||
unableToReconnectTitle: "Unable to reconnect",
|
||||
unableToReconnectMessage:
|
||||
"{{name}} did not come back online after updating. Please verify the daemon restarted.",
|
||||
unavailableTitle: "Host unavailable",
|
||||
unavailableMessage:
|
||||
"This host is not connected. Wait for it to come online before updating.",
|
||||
offlineTitle: "Host offline",
|
||||
offlineMessage: "This host is offline. Wait until it is back online before updating.",
|
||||
requestFailedTitle: "Update failed",
|
||||
requestFailedMessage: "Failed to update the daemon: {{error}}",
|
||||
dialogFailedMessage: "Unable to open the update confirmation dialog.",
|
||||
},
|
||||
dangerZone: "Zone dangereuse",
|
||||
remove: {
|
||||
title: "Supprimer l'hôte",
|
||||
|
||||
@@ -1765,6 +1765,31 @@ export const ja: TranslationResources = {
|
||||
"再起動リクエストの送信に失敗しました。Paseoは自動的に再接続します。ホストがオンラインになったら再試行してください。",
|
||||
dialogFailedMessage: "再起動確認ダイアログを開けませんでした。",
|
||||
},
|
||||
update: {
|
||||
title: "デーモンを更新",
|
||||
hint: "デーモンを最新バージョンに更新して再起動します",
|
||||
confirm: "更新",
|
||||
confirmTitle: "{{name}}を更新",
|
||||
confirmMessage:
|
||||
"これによりデーモンが最新バージョンに更新され、再起動されます。実行中のエージェントは一時的に中断されます。",
|
||||
updating: "更新中...",
|
||||
phaseStarting: "更新を準備中...",
|
||||
phaseDownloading: "パッケージをダウンロード中...",
|
||||
phaseInstalling: "インストール中...",
|
||||
phaseComplete: "更新が完了しました。再起動中...",
|
||||
unableToReconnectTitle: "再接続できません",
|
||||
unableToReconnectMessage:
|
||||
"{{name}}が更新後にオンラインに戻りませんでした。デーモンが再起動されたことを確認してください。",
|
||||
unavailableTitle: "ホストが利用できません",
|
||||
unavailableMessage:
|
||||
"このホストは接続されていません。更新する前にオンラインになるまでお待ちください。",
|
||||
offlineTitle: "ホストオフライン",
|
||||
offlineMessage:
|
||||
"このホストはオフラインです。オンラインに戻るまで待ってから更新してください。",
|
||||
requestFailedTitle: "更新に失敗しました",
|
||||
requestFailedMessage: "デーモンの更新に失敗しました: {{error}}",
|
||||
dialogFailedMessage: "更新確認ダイアログを開けませんでした。",
|
||||
},
|
||||
dangerZone: "危険ゾーン",
|
||||
remove: {
|
||||
title: "ホストを削除",
|
||||
|
||||
@@ -1771,6 +1771,31 @@ export const ptBR: TranslationResources = {
|
||||
"Falha ao enviar a solicitação de reinício. O Paseo reconecta automaticamente. Tente novamente quando o host aparecer como online.",
|
||||
dialogFailedMessage: "Não foi possível abrir o diálogo de confirmação de reinício.",
|
||||
},
|
||||
update: {
|
||||
title: "Atualizar daemon",
|
||||
hint: "Atualiza o daemon para a versão mais recente e o reinicia",
|
||||
confirm: "Atualizar",
|
||||
confirmTitle: "Atualizar {{name}}",
|
||||
confirmMessage:
|
||||
"Isso vai atualizar o daemon para a versão mais recente e reiniciá-lo. Agentes em execução serão interrompidos brevemente.",
|
||||
updating: "Atualizando...",
|
||||
phaseStarting: "Preparando atualização...",
|
||||
phaseDownloading: "Baixando pacotes...",
|
||||
phaseInstalling: "Instalando...",
|
||||
phaseComplete: "Atualização concluída, reiniciando...",
|
||||
unableToReconnectTitle: "Não foi possível reconectar",
|
||||
unableToReconnectMessage:
|
||||
"{{name}} não voltou a ficar online após a atualização. Verifique se o daemon reiniciou.",
|
||||
unavailableTitle: "Host indisponível",
|
||||
unavailableMessage:
|
||||
"Este host não está conectado. Aguarde até ele ficar online antes de atualizar.",
|
||||
offlineTitle: "Host offline",
|
||||
offlineMessage:
|
||||
"Este host está offline. Aguarde até ele voltar a ficar online antes de atualizar.",
|
||||
requestFailedTitle: "Falha na atualização",
|
||||
requestFailedMessage: "Falha ao atualizar o daemon: {{error}}",
|
||||
dialogFailedMessage: "Não foi possível abrir o diálogo de confirmação da atualização.",
|
||||
},
|
||||
dangerZone: "Zona de perigo",
|
||||
remove: {
|
||||
title: "Remover host",
|
||||
|
||||
@@ -1777,6 +1777,30 @@ export const ru: TranslationResources = {
|
||||
"Не удалось отправить запрос на перезапуск.Paseo автоматически повторно подключается. Повторите попытку, как только хост окажется в сети.",
|
||||
dialogFailedMessage: "Невозможно открыть диалоговое окно подтверждения перезапуска.",
|
||||
},
|
||||
update: {
|
||||
title: "Update daemon",
|
||||
hint: "Update the daemon to the latest version and restart it",
|
||||
confirm: "Update",
|
||||
confirmTitle: "Update {{name}}",
|
||||
confirmMessage:
|
||||
"This will update the daemon to the latest version and restart it. Running agents will be briefly interrupted.",
|
||||
updating: "Updating...",
|
||||
phaseStarting: "Preparing update...",
|
||||
phaseDownloading: "Downloading packages...",
|
||||
phaseInstalling: "Installing...",
|
||||
phaseComplete: "Update complete, restarting...",
|
||||
unableToReconnectTitle: "Unable to reconnect",
|
||||
unableToReconnectMessage:
|
||||
"{{name}} did not come back online after updating. Please verify the daemon restarted.",
|
||||
unavailableTitle: "Host unavailable",
|
||||
unavailableMessage:
|
||||
"This host is not connected. Wait for it to come online before updating.",
|
||||
offlineTitle: "Host offline",
|
||||
offlineMessage: "This host is offline. Wait until it is back online before updating.",
|
||||
requestFailedTitle: "Update failed",
|
||||
requestFailedMessage: "Failed to update the daemon: {{error}}",
|
||||
dialogFailedMessage: "Unable to open the update confirmation dialog.",
|
||||
},
|
||||
dangerZone: "Опасная зона",
|
||||
remove: {
|
||||
title: "Удалить хост",
|
||||
|
||||
@@ -1722,6 +1722,30 @@ export const zhCN: TranslationResources = {
|
||||
requestFailedMessage: "发送重启请求失败。Paseo 会自动重连,请在 Host 显示在线后重试。",
|
||||
dialogFailedMessage: "无法打开重启确认对话框。",
|
||||
},
|
||||
update: {
|
||||
title: "Update daemon",
|
||||
hint: "Update the daemon to the latest version and restart it",
|
||||
confirm: "Update",
|
||||
confirmTitle: "Update {{name}}",
|
||||
confirmMessage:
|
||||
"This will update the daemon to the latest version and restart it. Running agents will be briefly interrupted.",
|
||||
updating: "Updating...",
|
||||
phaseStarting: "Preparing update...",
|
||||
phaseDownloading: "Downloading packages...",
|
||||
phaseInstalling: "Installing...",
|
||||
phaseComplete: "Update complete, restarting...",
|
||||
unableToReconnectTitle: "Unable to reconnect",
|
||||
unableToReconnectMessage:
|
||||
"{{name}} did not come back online after updating. Please verify the daemon restarted.",
|
||||
unavailableTitle: "Host unavailable",
|
||||
unavailableMessage:
|
||||
"This host is not connected. Wait for it to come online before updating.",
|
||||
offlineTitle: "Host offline",
|
||||
offlineMessage: "This host is offline. Wait until it is back online before updating.",
|
||||
requestFailedTitle: "Update failed",
|
||||
requestFailedMessage: "Failed to update the daemon: {{error}}",
|
||||
dialogFailedMessage: "Unable to open the update confirmation dialog.",
|
||||
},
|
||||
dangerZone: "危险区域",
|
||||
remove: {
|
||||
title: "移除 Host",
|
||||
|
||||
Reference in New Issue
Block a user