From 419692d5487807ae39869a8284fc7fc2a26af822 Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:46:37 -0800 Subject: [PATCH] Show update UI on asar changes on Linux --- ACKNOWLEDGMENTS.md | 23 ++++++++++++++++ package.json | 4 +-- pnpm-lock.yaml | 19 ++++++++++--- ts/updater/index.ts | 24 +++++----------- ts/updater/linux.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 113 insertions(+), 24 deletions(-) diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 30b888c1fc..f66aa623b5 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -5,6 +5,29 @@ Signal Desktop makes use of the following open source projects. +## @electron/asar + + Copyright (c) 2014 GitHub Inc. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ## @formatjs/fast-memoize MIT License diff --git a/package.json b/package.json index a1170631f6..0d6ea002e9 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "fs-xattr": "0.3.0" }, "dependencies": { + "@electron/asar": "3.3.1", "@formatjs/fast-memoize": "2.2.3", "@formatjs/icu-messageformat-parser": "2.9.3", "@formatjs/intl-localematcher": "0.2.32", @@ -110,8 +111,8 @@ "@indutny/simple-windows-notifications": "2.0.7", "@indutny/sneequals": "4.0.0", "@popperjs/core": "2.11.8", - "@react-aria/utils": "3.25.3", "@react-aria/focus": "3.19.1", + "@react-aria/utils": "3.25.3", "@react-spring/web": "9.7.5", "@signalapp/better-sqlite3": "9.0.13", "@signalapp/libsignal-client": "0.67.1", @@ -211,7 +212,6 @@ "@babel/plugin-transform-typescript": "7.25.9", "@babel/preset-react": "7.25.9", "@babel/preset-typescript": "7.26.0", - "@electron/asar": "3.2.17", "@electron/fuses": "1.5.0", "@electron/notarize": "2.1.0", "@electron/symbolicate-mac": "2.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2fafbfec4..fb6f43ff6b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -82,6 +82,9 @@ importers: .: dependencies: + '@electron/asar': + specifier: 3.3.1 + version: 3.3.1 '@formatjs/fast-memoize': specifier: 2.2.3 version: 2.2.3 @@ -407,9 +410,6 @@ importers: '@babel/preset-typescript': specifier: 7.26.0 version: 7.26.0(@babel/core@7.26.0) - '@electron/asar': - specifier: 3.2.17 - version: 3.2.17 '@electron/fuses': specifier: 1.5.0 version: 1.5.0 @@ -1130,6 +1130,11 @@ packages: engines: {node: '>=10.12.0'} hasBin: true + '@electron/asar@3.3.1': + resolution: {integrity: sha512-WtpC/+34p0skWZiarRjLAyqaAX78DofhDxnREy/V5XHfu1XEXbFCSSMcDQ6hNCPJFaPy8/NnUgYuf9uiCkvKPg==} + engines: {node: '>=10.12.0'} + hasBin: true + '@electron/fuses@1.5.0': resolution: {integrity: sha512-2NPJdQTPoERxcIWsTAG9ZKay0KsORfs973dbkdksNEeBAfjdyPBNaUrh/DAlpUdAnFmPl+Cs9gIZibRYiOrvqQ==} hasBin: true @@ -10206,6 +10211,12 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + '@electron/asar@3.3.1': + dependencies: + commander: 5.1.0 + glob: 7.2.3 + minimatch: 3.1.2 + '@electron/fuses@1.5.0': dependencies: chalk: 4.1.2 @@ -10304,7 +10315,7 @@ snapshots: '@electron/universal@2.0.1': dependencies: - '@electron/asar': 3.2.17 + '@electron/asar': 3.3.1 '@malept/cross-spawn-promise': 2.0.0 debug: 4.3.7(supports-color@8.1.1) dir-compare: 4.2.0 diff --git a/ts/updater/index.ts b/ts/updater/index.ts index 02b60e24e9..950a9de5d6 100644 --- a/ts/updater/index.ts +++ b/ts/updater/index.ts @@ -2,12 +2,11 @@ // SPDX-License-Identifier: AGPL-3.0-only import config from 'config'; - +import { app } from 'electron'; import type { Updater, UpdaterOptionsType } from './common'; import { MacOSUpdater } from './macos'; import { WindowsUpdater } from './windows'; -import { isLinuxVersionSupported } from './linux'; -import { DialogType } from '../types/Dialogs'; +import { initLinux } from './linux'; let initialized = false; @@ -15,7 +14,7 @@ let updater: Updater | undefined; export async function start(options: UpdaterOptionsType): Promise { const { platform } = process; - const { logger, getMainWindow } = options; + const { logger } = options; if (initialized) { throw new Error('updater/start: Updates have already been initialized!'); @@ -26,15 +25,6 @@ export async function start(options: UpdaterOptionsType): Promise { throw new Error('updater/start: Must provide logger!'); } - if (platform === 'linux') { - if (!isLinuxVersionSupported(logger)) { - getMainWindow()?.webContents.send( - 'show-update-dialog', - DialogType.UnsupportedOS - ); - } - } - if (autoUpdateDisabled()) { logger.info( 'updater/start: Updates disabled - not starting new version checks' @@ -47,8 +37,10 @@ export async function start(options: UpdaterOptionsType): Promise { updater = new WindowsUpdater(options); } else if (platform === 'darwin') { updater = new MacOSUpdater(options); + } else if (platform === 'linux') { + initLinux(options); } else { - throw new Error('updater/start: Unsupported platform'); + throw new Error(`updater/start: Unsupported platform ${platform}`); } await updater?.start(); @@ -71,7 +63,5 @@ export function onRestartCancelled(): void { } function autoUpdateDisabled() { - return ( - process.platform === 'linux' || process.mas || !config.get('updatesEnabled') - ); + return !app.isPackaged || process.mas || !config.get('updatesEnabled'); } diff --git a/ts/updater/linux.ts b/ts/updater/linux.ts index a0f42b8bc5..39a01a8310 100644 --- a/ts/updater/linux.ts +++ b/ts/updater/linux.ts @@ -1,12 +1,25 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { version as osVersion } from 'os'; +import { version as osVersion } from 'node:os'; +import { watch } from 'node:fs'; +import { join } from 'node:path'; +import { app, ipcMain } from 'electron'; +import { extractFile } from '@electron/asar'; +import z from 'zod'; +import { markShouldQuit } from '../../app/window_state'; import type { LoggerType } from '../types/Logging'; +import { DialogType } from '../types/Dialogs'; +import * as Errors from '../types/errors'; +import type { UpdaterOptionsType } from './common'; const MIN_UBUNTU_VERSION = '20.04'; +const PackageSchema = z.object({ + version: z.string(), +}); + export function getUbuntuVersion(): string | undefined { if (process.platform !== 'linux') { return undefined; @@ -31,3 +44,55 @@ export function isLinuxVersionSupported(logger?: LoggerType): boolean { return true; } + +export function initLinux({ logger, getMainWindow }: UpdaterOptionsType): void { + if (!app.isPackaged) { + throw new Error('Linux updates are not supported in development'); + } + + if (!isLinuxVersionSupported(logger)) { + getMainWindow()?.webContents.send( + 'show-update-dialog', + DialogType.UnsupportedOS + ); + } + + ipcMain.handle('start-update', () => { + logger?.info('updater/linux: restarting'); + markShouldQuit(); + app.relaunch(); + app.quit(); + }); + + const asarPath = join(__dirname, '..', '..'); + if (!asarPath.endsWith('.asar')) { + throw new Error('updater/linux: not running from ASAR'); + } + + watch(asarPath, event => { + if (event !== 'change') { + return; + } + + let version: string; + try { + const file = extractFile(asarPath, 'package.json').toString(); + ({ version } = PackageSchema.parse(JSON.parse(file))); + } catch (error) { + logger?.error( + 'updater/linux: failed to parse updated asar', + Errors.toLogFormat(error) + ); + return; + } + + logger?.info(`updater/linux: asar updated to version=${version}`); + getMainWindow()?.webContents.send( + 'show-update-dialog', + DialogType.AutoUpdate, + { + version, + } + ); + }).unref(); +}