Skip to content

Commit

Permalink
fix(linux): AppImage doesn't update when filename contains spaces (#8802
Browse files Browse the repository at this point in the history
)
  • Loading branch information
erijo authored Jan 25, 2025
1 parent d954c40 commit 4a68fd2
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-pillows-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"electron-updater": patch
---

fix(linux): AppImage update fails when filename contains spaces
11 changes: 8 additions & 3 deletions packages/electron-updater/src/AppImageUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,21 @@ export class AppImageUpdater extends BaseUpdater {

let destination: string
const existingBaseName = path.basename(appImageFile)
const installerPath = this.installerPath
if (installerPath == null) {
this.dispatchError(new Error("No valid update available, can't quit and install"))
return false
}
// https://github.com/electron-userland/electron-builder/issues/2964
// if no version in existing file name, it means that user wants to preserve current custom name
if (path.basename(options.installerPath) === existingBaseName || !/\d+\.\d+\.\d+/.test(existingBaseName)) {
if (path.basename(installerPath) === existingBaseName || !/\d+\.\d+\.\d+/.test(existingBaseName)) {
// no version in the file name, overwrite existing
destination = appImageFile
} else {
destination = path.join(path.dirname(appImageFile), path.basename(options.installerPath))
destination = path.join(path.dirname(appImageFile), path.basename(installerPath))
}

execFileSync("mv", ["-f", options.installerPath, destination])
execFileSync("mv", ["-f", installerPath, destination])
if (destination !== appImageFile) {
this.emit("appimage-filename-updated", destination)
}
Expand Down
18 changes: 5 additions & 13 deletions packages/electron-updater/src/BaseUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export abstract class BaseUpdater extends AppUpdater {
})
}

protected get installerPath(): string | null {
return this.downloadedUpdateHelper == null ? null : this.downloadedUpdateHelper.file
}

// must be sync
protected abstract doInstall(options: InstallOptions): boolean

Expand All @@ -48,17 +52,7 @@ export abstract class BaseUpdater extends AppUpdater {
}

const downloadedUpdateHelper = this.downloadedUpdateHelper

// Get the installer path, ensuring spaces are escaped on Linux
// 1. Check if downloadedUpdateHelper is not null
// 2. Check if downloadedUpdateHelper.file is not null
// 3. If both checks pass:
// a. If the platform is Linux, replace spaces with '\ ' for shell compatibility
// b. If the platform is not Linux, use the original path
// 4. If any check fails, set installerPath to null
const installerPath =
downloadedUpdateHelper && downloadedUpdateHelper.file ? (process.platform === "linux" ? downloadedUpdateHelper.file.replace(/ /g, "\\ ") : downloadedUpdateHelper.file) : null

const installerPath = this.installerPath
const downloadedFileInfo = downloadedUpdateHelper == null ? null : downloadedUpdateHelper.downloadedFileInfo
if (installerPath == null || downloadedFileInfo == null) {
this.dispatchError(new Error("No valid update available, can't quit and install"))
Expand All @@ -71,7 +65,6 @@ export abstract class BaseUpdater extends AppUpdater {
try {
this._logger.info(`Install: isSilent: ${isSilent}, isForceRunAfter: ${isForceRunAfter}`)
return this.doInstall({
installerPath,
isSilent,
isForceRunAfter,
isAdminRightsRequired: downloadedFileInfo.isAdminRightsRequired,
Expand Down Expand Up @@ -164,7 +157,6 @@ export abstract class BaseUpdater extends AppUpdater {
}

export interface InstallOptions {
readonly installerPath: string
readonly isSilent: boolean
readonly isForceRunAfter: boolean
readonly isAdminRightsRequired: boolean
Expand Down
11 changes: 10 additions & 1 deletion packages/electron-updater/src/DebUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,20 @@ export class DebUpdater extends BaseUpdater {
})
}

protected get installerPath(): string | null {
return super.installerPath?.replace(/ /g, "\\ ") ?? null
}

protected doInstall(options: InstallOptions): boolean {
const sudo = this.wrapSudo()
// pkexec doesn't want the command to be wrapped in " quotes
const wrapper = /pkexec/i.test(sudo) ? "" : `"`
const cmd = ["dpkg", "-i", options.installerPath, "||", "apt-get", "install", "-f", "-y"]
const installerPath = this.installerPath
if (installerPath == null) {
this.dispatchError(new Error("No valid update available, can't quit and install"))
return false
}
const cmd = ["dpkg", "-i", installerPath, "||", "apt-get", "install", "-f", "-y"]
this.spawnSyncLog(sudo, [`${wrapper}/bin/bash`, "-c", `'${cmd.join(" ")}'${wrapper}`])
if (options.isForceRunAfter) {
this.app.relaunch()
Expand Down
12 changes: 9 additions & 3 deletions packages/electron-updater/src/NsisUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ export class NsisUpdater extends BaseUpdater {
}

protected doInstall(options: InstallOptions): boolean {
const installerPath = this.installerPath
if (installerPath == null) {
this.dispatchError(new Error("No valid update available, can't quit and install"))
return false
}

const args = ["--updated"]
if (options.isSilent) {
args.push("/S")
Expand All @@ -144,7 +150,7 @@ export class NsisUpdater extends BaseUpdater {
}

const callUsingElevation = (): void => {
this.spawnLog(path.join(process.resourcesPath, "elevate.exe"), [options.installerPath].concat(args)).catch(e => this.dispatchError(e))
this.spawnLog(path.join(process.resourcesPath, "elevate.exe"), [installerPath].concat(args)).catch(e => this.dispatchError(e))
}

if (options.isAdminRightsRequired) {
Expand All @@ -153,7 +159,7 @@ export class NsisUpdater extends BaseUpdater {
return true
}

this.spawnLog(options.installerPath, args).catch((e: Error) => {
this.spawnLog(installerPath, args).catch((e: Error) => {
// https://github.com/electron-userland/electron-builder/issues/1129
// Node 8 sends errors: https://nodejs.org/dist/latest-v8.x/docs/api/errors.html#errors_common_system_errors
const errorCode = (e as NodeJS.ErrnoException).code
Expand All @@ -164,7 +170,7 @@ export class NsisUpdater extends BaseUpdater {
callUsingElevation()
} else if (errorCode === "ENOENT") {
require("electron")
.shell.openPath(options.installerPath)
.shell.openPath(installerPath)
.catch((err: Error) => this.dispatchError(err))
} else {
this.dispatchError(e)
Expand Down
11 changes: 10 additions & 1 deletion packages/electron-updater/src/PacmanUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,20 @@ export class PacmanUpdater extends BaseUpdater {
})
}

protected get installerPath(): string | null {
return super.installerPath?.replace(/ /g, "\\ ") ?? null
}

protected doInstall(options: InstallOptions): boolean {
const sudo = this.wrapSudo()
// pkexec doesn't want the command to be wrapped in " quotes
const wrapper = /pkexec/i.test(sudo) ? "" : `"`
const cmd = ["pacman", "-U", "--noconfirm", options.installerPath]
const installerPath = this.installerPath
if (installerPath == null) {
this.dispatchError(new Error("No valid update available, can't quit and install"))
return false
}
const cmd = ["pacman", "-U", "--noconfirm", installerPath]
this.spawnSyncLog(sudo, [`${wrapper}/bin/bash`, "-c", `'${cmd.join(" ")}'${wrapper}`])
if (options.isForceRunAfter) {
this.app.relaunch()
Expand Down
14 changes: 11 additions & 3 deletions packages/electron-updater/src/RpmUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,26 @@ export class RpmUpdater extends BaseUpdater {
})
}

protected get installerPath(): string | null {
return super.installerPath?.replace(/ /g, "\\ ") ?? null
}

protected doInstall(options: InstallOptions): boolean {
const upgradePath = options.installerPath
const sudo = this.wrapSudo()
// pkexec doesn't want the command to be wrapped in " quotes
const wrapper = /pkexec/i.test(sudo) ? "" : `"`
const packageManager = this.spawnSyncLog("which zypper")
const installerPath = this.installerPath
if (installerPath == null) {
this.dispatchError(new Error("No valid update available, can't quit and install"))
return false
}
let cmd: string[]
if (!packageManager) {
const packageManager = this.spawnSyncLog("which dnf || which yum")
cmd = [packageManager, "-y", "install", upgradePath]
cmd = [packageManager, "-y", "install", installerPath]
} else {
cmd = [packageManager, "--no-refresh", "install", "--allow-unsigned-rpm", "-y", "-f", upgradePath]
cmd = [packageManager, "--no-refresh", "install", "--allow-unsigned-rpm", "-y", "-f", installerPath]
}
this.spawnSyncLog(sudo, [`${wrapper}/bin/bash`, "-c", `'${cmd.join(" ")}'${wrapper}`])
if (options.isForceRunAfter) {
Expand Down

0 comments on commit 4a68fd2

Please sign in to comment.