From 2dea157233d82a0bf44b34a04b2b39e34a112e5d Mon Sep 17 00:00:00 2001 From: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:15:17 -0700 Subject: [PATCH] Linux: Auto migrate safeStorage backend between kwallet5 and kwallet6 --- app/main.main.ts | 12 ++++ ts/util/linuxPasswordStoreMigration.main.ts | 68 +++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 ts/util/linuxPasswordStoreMigration.main.ts diff --git a/app/main.main.ts b/app/main.main.ts index c9eaff594e..723ab094ff 100644 --- a/app/main.main.ts +++ b/app/main.main.ts @@ -139,6 +139,7 @@ import { appRelaunch } from '../ts/util/relaunch.main.ts'; import { getAppRootDir } from '../ts/util/appRootDir.main.ts'; import { trackHeapSize } from '../ts/util/oomNotifier.node.ts'; import { sendDummyKeystroke } from './WindowsNotifications.main.ts'; +import { maybeMigrateSafeStorageBackend } from '../ts/util/linuxPasswordStoreMigration.main.ts'; const { chmod, realpath, writeFile } = fsExtra; const { get, pick, isNumber, isBoolean, some, debounce, noop } = lodash; @@ -1892,6 +1893,17 @@ const onDatabaseInitializationError = async (error: Error) => { defaultButtonId = copyErrorAndQuitButtonIndex; } else if (error instanceof SafeStorageBackendChangeError) { const { currentBackend, previousBackend } = error; + + // Attempt automatic migration + if (await maybeMigrateSafeStorageBackend(previousBackend, currentBackend)) { + log.info( + `Performed auto migration of safeStorage backend from ${previousBackend} to ${currentBackend}. Restarting.` + ); + app.relaunch(); + app.exit(1); + return; + } + const previousBackendFlag = getOwn( LINUX_PASSWORD_STORE_FLAGS, previousBackend diff --git a/ts/util/linuxPasswordStoreMigration.main.ts b/ts/util/linuxPasswordStoreMigration.main.ts new file mode 100644 index 0000000000..df10cb6ce3 --- /dev/null +++ b/ts/util/linuxPasswordStoreMigration.main.ts @@ -0,0 +1,68 @@ +// Copyright 2026 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +/* oxlint-disable no-console */ + +import { exec } from 'node:child_process'; +import { userConfig } from '../../app/user_config.main.ts'; + +export async function maybeMigrateSafeStorageBackend( + previousBackend: string, + newBackend: string +): Promise { + if (previousBackend === 'kwallet5' && newBackend === 'kwallet6') { + return upgradeKWallet5To6(); + } + + if (previousBackend === 'kwallet6' && newBackend === 'kwallet5') { + return downgradeKWallet6To5(); + } + + return false; +} + +async function upgradeKWallet5To6(): Promise { + if (await checkCommandSuccess('kwalletd5 --version')) { + console.log( + 'kwalletd5 --version check did not fail, so it may still be installed. Aborting upgrade.' + ); + return false; + } + + if (!(await checkCommandSuccess('kwalletd6 --version'))) { + console.log( + 'kwalletd6 --version check did not succeed, so it may not be installed. Aborting upgrade' + ); + return false; + } + + userConfig.set('safeStorageBackend', 'kwallet6'); + return true; +} + +async function downgradeKWallet6To5(): Promise { + if (await checkCommandSuccess('kwalletd6 --version')) { + console.log( + 'kwalletd6 --version check did not fail, so it may still be installed. Aborting upgrade.' + ); + return false; + } + + if (!(await checkCommandSuccess('kwalletd5 --version'))) { + console.log( + 'kwalletd5 --version check did not succeed, so it may not be installed. Aborting upgrade' + ); + return false; + } + + userConfig.set('safeStorageBackend', 'kwallet5'); + return true; +} + +function checkCommandSuccess(command: string): Promise { + return new Promise(resolve => { + exec(command).on('exit', code => { + resolve(code === 0); + }); + }); +}