From b95d651489bcdbf566c4d54779ddacc84c9874fc Mon Sep 17 00:00:00 2001 From: Niklas Wenzel Date: Wed, 11 Mar 2026 14:10:17 +0100 Subject: [PATCH] revert: fix(squirrel.mac): clean up old staged updates before downloading new update (#49365) This reverts commit 233caf84695d56b6ffad83461e2728fe7588cddb. --- patches/squirrel.mac/.patches | 1 - ...pdates_before_downloading_new_update.patch | 64 ------------- spec/api-autoupdater-darwin-spec.ts | 94 ------------------- 3 files changed, 159 deletions(-) delete mode 100644 patches/squirrel.mac/fix_clean_up_old_staged_updates_before_downloading_new_update.patch diff --git a/patches/squirrel.mac/.patches b/patches/squirrel.mac/.patches index 5f259cbeea..f3825e6d07 100644 --- a/patches/squirrel.mac/.patches +++ b/patches/squirrel.mac/.patches @@ -9,5 +9,4 @@ refactor_use_non-deprecated_nskeyedarchiver_apis.patch chore_turn_off_launchapplicationaturl_deprecation_errors_in_squirrel.patch fix_crash_when_process_to_extract_zip_cannot_be_launched.patch use_uttype_class_instead_of_deprecated_uttypeconformsto.patch -fix_clean_up_old_staged_updates_before_downloading_new_update.patch fix_add_explicit_json_property_mappings_for_shipit_request_model.patch diff --git a/patches/squirrel.mac/fix_clean_up_old_staged_updates_before_downloading_new_update.patch b/patches/squirrel.mac/fix_clean_up_old_staged_updates_before_downloading_new_update.patch deleted file mode 100644 index 3114490a15..0000000000 --- a/patches/squirrel.mac/fix_clean_up_old_staged_updates_before_downloading_new_update.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andy Locascio -Date: Tue, 6 Jan 2026 08:23:03 -0800 -Subject: fix: clean up old staged updates before downloading new update - -When checkForUpdates() is called while an update is already staged, -Squirrel creates a new temporary directory for the download without -cleaning up the old one. This can lead to significant disk usage if -the app keeps checking for updates without restarting. - -This change adds a force parameter to pruneUpdateDirectories that -bypasses the AwaitingRelaunch state check. This is called before -creating a new temp directory, ensuring old staged updates are -cleaned up when a new download starts. - -diff --git a/Squirrel/SQRLUpdater.m b/Squirrel/SQRLUpdater.m -index d156616e81e6f25a3bded30e6216b8fc311f31bc..6cd4346bf43b191147aff819cb93387e71275a46 100644 ---- a/Squirrel/SQRLUpdater.m -+++ b/Squirrel/SQRLUpdater.m -@@ -543,11 +543,17 @@ - (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL - #pragma mark File Management - - - (RACSignal *)uniqueTemporaryDirectoryForUpdate { -- return [[[RACSignal -+ // Clean up any old staged update directories before creating a new one. -+ // This prevents disk usage from growing when checkForUpdates() is called -+ // multiple times without the app restarting. -+ return [[[[[self -+ pruneUpdateDirectoriesWithForce:YES] -+ ignoreValues] -+ concat:[RACSignal - defer:^{ - SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel]; - return [directoryManager storageURL]; -- }] -+ }]] - flattenMap:^(NSURL *storageURL) { - NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]]; - char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation); -@@ -643,7 +649,7 @@ - (BOOL)isRunningOnReadOnlyVolume { - - - (RACSignal *)performHousekeeping { - return [[RACSignal -- merge:@[ [self pruneUpdateDirectories], [self truncateLogs] ]] -+ merge:@[ [self pruneUpdateDirectoriesWithForce:NO], [self truncateLogs] ]] - catch:^(NSError *error) { - NSLog(@"Error doing housekeeping: %@", error); - return [RACSignal empty]; -@@ -658,11 +664,12 @@ - (RACSignal *)performHousekeeping { - /// - /// Sends each removed directory then completes, or errors, on an unspecified - /// thread. --- (RACSignal *)pruneUpdateDirectories { -+- (RACSignal *)pruneUpdateDirectoriesWithForce:(BOOL)force { - return [[[RACSignal - defer:^{ -- // If we already have updates downloaded we don't wanna prune them. -- if (self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty]; -+ // If we already have updates downloaded we don't wanna prune them, -+ // unless force is YES (used when starting a new download). -+ if (!force && self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty]; - - SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel]; - return [directoryManager storageURL]; diff --git a/spec/api-autoupdater-darwin-spec.ts b/spec/api-autoupdater-darwin-spec.ts index 4a5d3904f7..ffda7040be 100644 --- a/spec/api-autoupdater-darwin-spec.ts +++ b/spec/api-autoupdater-darwin-spec.ts @@ -9,7 +9,6 @@ import * as cp from 'node:child_process'; import * as fs from 'node:fs'; import * as http from 'node:http'; import { AddressInfo } from 'node:net'; -import * as os from 'node:os'; import * as path from 'node:path'; import { copyMacOSFixtureApp, getCodesignIdentity, shouldRunCodesignTests, signApp, spawn, unsignApp } from './lib/codesign-helpers'; @@ -68,38 +67,6 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () { } }; - // Squirrel stores update directories in ~/Library/Caches/com.github.Electron.ShipIt/ - // as subdirectories named like update.XXXXXXX - const getSquirrelCacheDirectory = () => { - return path.join(os.homedir(), 'Library', 'Caches', 'com.github.Electron.ShipIt'); - }; - - const getUpdateDirectoriesInCache = async () => { - const cacheDir = getSquirrelCacheDirectory(); - try { - const entries = await fs.promises.readdir(cacheDir, { withFileTypes: true }); - return entries - .filter(entry => entry.isDirectory() && entry.name.startsWith('update.')) - .map(entry => path.join(cacheDir, entry.name)); - } catch { - return []; - } - }; - - const cleanSquirrelCache = async () => { - const cacheDir = getSquirrelCacheDirectory(); - try { - const entries = await fs.promises.readdir(cacheDir, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory() && entry.name.startsWith('update.')) { - await fs.promises.rm(path.join(cacheDir, entry.name), { recursive: true, force: true }); - } - } - } catch { - // Cache dir may not exist yet - } - }; - const cachedZips: Record = {}; type Mutation = { @@ -403,67 +370,6 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () { }); }); - it('should clean up old staged update directories when a new update is downloaded', async () => { - // Clean up any existing update directories before the test - await cleanSquirrelCache(); - - await withUpdatableApp({ - nextVersion: '2.0.0', - startFixture: 'update-stack', - endFixture: 'update-stack' - }, async (appPath, updateZipPath2) => { - await withUpdatableApp({ - nextVersion: '3.0.0', - startFixture: 'update-stack', - endFixture: 'update-stack' - }, async (_, updateZipPath3) => { - let updateCount = 0; - let downloadCount = 0; - let directoriesDuringSecondDownload: string[] = []; - - server.get('/update-file', async (req, res) => { - downloadCount++; - // When the second download request arrives, Squirrel has already - // called uniqueTemporaryDirectoryForUpdate which (with our patch) - // cleans up old directories before creating the new one. - // Without the patch, both directories would exist at this point. - if (downloadCount === 2) { - directoriesDuringSecondDownload = await getUpdateDirectoriesInCache(); - } - res.download(updateCount > 1 ? updateZipPath3 : updateZipPath2); - }); - server.get('/update-check', (req, res) => { - updateCount++; - res.json({ - url: `http://localhost:${port}/update-file`, - name: 'My Release Name', - notes: 'Theses are some release notes innit', - pub_date: (new Date()).toString() - }); - }); - const relaunchPromise = new Promise((resolve) => { - server.get('/update-check/updated/:version', (req, res) => { - res.status(204).send(); - resolve(); - }); - }); - const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]); - logOnError(launchResult, () => { - expect(launchResult).to.have.property('code', 0); - expect(launchResult.out).to.include('Update Downloaded'); - }); - - await relaunchPromise; - - // During the second download, the old staged update directory should - // have been cleaned up. With our patch, there should be exactly 1 - // directory (the new one). Without the patch, there would be 2. - expect(directoriesDuringSecondDownload).to.have.lengthOf(1, - `Expected 1 update directory during second download but found ${directoriesDuringSecondDownload.length}: ${directoriesDuringSecondDownload.join(', ')}`); - }); - }); - }); - it('should update to lower version numbers', async () => { await withUpdatableApp({ nextVersion: '0.0.1',