From 823070e293bf4dcef3d93477c400fc3a2e36f870 Mon Sep 17 00:00:00 2001 From: TowyTowy Date: Wed, 8 Jul 2026 21:21:45 +0200 Subject: [PATCH] fix: reuse stale tailscale.tgz on self-hosted runners On persistent self-hosted runners the XDG cache directory survives between jobs. When GitHub's Actions cache backend doesn't return a hit (no working backend, evicted entry, etc.), the tailscale.tgz left behind by a previous job is still on disk, and tc.downloadTool() refuses to overwrite an existing destination, failing with "Destination file path ... already exists". Mirror the Windows MSI fix (#259): if the tarball already exists, reuse it when its checksum matches, otherwise delete and re-download. Only the cloud-cache-restore path previously avoided this collision, so the Linux download was unconditional. Fixes #294 Signed-off-by: TowyTowy Co-Authored-By: Claude Fable 5 --- dist/index.js | 38 +++++++++++++++++++++++++++++--------- src/main.ts | 45 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/dist/index.js b/dist/index.js index 5bddb73..e59b833 100644 --- a/dist/index.js +++ b/dist/index.js @@ -52515,17 +52515,37 @@ async function installTailscaleLinux(config, toolPath) { } // Download and extract const downloadUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz`; - (0, logging_1.logInfo)(config.logMode, `Downloading ${downloadUrl}`); + const expectedSha = config.sha256Sum.trim().toLowerCase(); const tarDest = path.join(xdgCacheDir(), "tailscale.tgz"); fs.mkdirSync(path.dirname(tarDest), { recursive: true }); - const tarPath = await tc.downloadTool(downloadUrl, tarDest); - // Verify checksum - const actualSha = await calculateFileSha256(tarPath); - const expectedSha = config.sha256Sum.trim().toLowerCase(); - (0, logging_1.logInfo)(config.logMode, `Expected sha256: ${expectedSha}`); - (0, logging_1.logInfo)(config.logMode, `Actual sha256: ${actualSha}`); - if (actualSha !== expectedSha) { - throw new Error("SHA256 checksum mismatch"); + // Check if the tarball already exists with the correct checksum (for + // persistent self-hosted runners). tc.downloadTool refuses to overwrite an + // existing destination, so a tarball leaked by a previous job would + // otherwise fail with "Destination file path already exists" whenever the + // GitHub Actions cache backend doesn't return a hit. + let tarPath = tarDest; + let needsDownload = true; + if (fs.existsSync(tarDest)) { + const existingSha = await calculateFileSha256(tarDest); + if (existingSha === expectedSha) { + (0, logging_1.logInfo)(config.logMode, `Using existing tarball at ${tarDest} (checksum verified)`); + needsDownload = false; + } + else { + (0, logging_1.logInfo)(config.logMode, `Existing tarball checksum mismatch, re-downloading`); + fs.unlinkSync(tarDest); + } + } + if (needsDownload) { + (0, logging_1.logInfo)(config.logMode, `Downloading ${downloadUrl}`); + tarPath = await tc.downloadTool(downloadUrl, tarDest); + // Verify checksum + const actualSha = await calculateFileSha256(tarPath); + (0, logging_1.logInfo)(config.logMode, `Expected sha256: ${expectedSha}`); + (0, logging_1.logInfo)(config.logMode, `Actual sha256: ${actualSha}`); + if (actualSha !== expectedSha) { + throw new Error("SHA256 checksum mismatch"); + } } // Extract to tool path const extractedPath = await tc.extractTar(tarPath, undefined, "xz"); diff --git a/src/main.ts b/src/main.ts index dfb92bb..109d4c1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -472,19 +472,46 @@ async function installTailscaleLinux( // Download and extract const downloadUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz`; - logInfo(config.logMode, `Downloading ${downloadUrl}`); + const expectedSha = config.sha256Sum.trim().toLowerCase(); const tarDest = path.join(xdgCacheDir(), "tailscale.tgz"); fs.mkdirSync(path.dirname(tarDest), { recursive: true }); - const tarPath = await tc.downloadTool(downloadUrl, tarDest); - // Verify checksum - const actualSha = await calculateFileSha256(tarPath); - const expectedSha = config.sha256Sum.trim().toLowerCase(); - logInfo(config.logMode, `Expected sha256: ${expectedSha}`); - logInfo(config.logMode, `Actual sha256: ${actualSha}`); - if (actualSha !== expectedSha) { - throw new Error("SHA256 checksum mismatch"); + // Check if the tarball already exists with the correct checksum (for + // persistent self-hosted runners). tc.downloadTool refuses to overwrite an + // existing destination, so a tarball leaked by a previous job would + // otherwise fail with "Destination file path already exists" whenever the + // GitHub Actions cache backend doesn't return a hit. + let tarPath = tarDest; + let needsDownload = true; + if (fs.existsSync(tarDest)) { + const existingSha = await calculateFileSha256(tarDest); + if (existingSha === expectedSha) { + logInfo( + config.logMode, + `Using existing tarball at ${tarDest} (checksum verified)`, + ); + needsDownload = false; + } else { + logInfo( + config.logMode, + `Existing tarball checksum mismatch, re-downloading`, + ); + fs.unlinkSync(tarDest); + } + } + + if (needsDownload) { + logInfo(config.logMode, `Downloading ${downloadUrl}`); + tarPath = await tc.downloadTool(downloadUrl, tarDest); + + // Verify checksum + const actualSha = await calculateFileSha256(tarPath); + logInfo(config.logMode, `Expected sha256: ${expectedSha}`); + logInfo(config.logMode, `Actual sha256: ${actualSha}`); + if (actualSha !== expectedSha) { + throw new Error("SHA256 checksum mismatch"); + } } // Extract to tool path