From fba0fe59d204f11311c4daa1a8c7367c631ce94e Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 28 Oct 2024 11:32:03 +0100 Subject: [PATCH 1/6] fix(sea): windows `rm` fails --- test/test-00-sea/main.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/test/test-00-sea/main.js b/test/test-00-sea/main.js index 7daf2a2e..e9e23ef7 100644 --- a/test/test-00-sea/main.js +++ b/test/test-00-sea/main.js @@ -34,18 +34,11 @@ if (process.platform === 'linux') { 'Output matches', ); } else if (process.platform === 'win32') { - // FIXME: output doesn't match on windows - // assert.equal( - // utils.spawn.sync('./test-sea-win.exe', []), - // 'Hello world\n', - // 'Output matches', - // ); + assert.equal( + utils.spawn.sync('./test-sea-win.exe', []), + 'Hello world\n', + 'Output matches', + ); } -try { - // FIXME: on windows this throws - // Error: EBUSY: resource busy or locked, rmdir 'C:\Users\RUNNER~1\AppData\Local\Temp\pkg-sea\1729696609242' - utils.filesAfter(before, newcomers); -} catch (error) { - // noop -} +utils.filesAfter(before, newcomers); From b1ba10bfccbd8d4852932a34b3f15b745ac1c3c7 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 28 Oct 2024 11:51:34 +0100 Subject: [PATCH 2/6] fix: add sign/unsign --- lib/mach-o.ts | 15 ++++++++++ lib/sea.ts | 76 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/lib/mach-o.ts b/lib/mach-o.ts index 9993457b..85e3c515 100644 --- a/lib/mach-o.ts +++ b/lib/mach-o.ts @@ -78,8 +78,23 @@ function removeMachOExecutableSignature(executable: string) { }); } +function signWindowsExecutable(executable: string) { + execFileSync('signtool', ['sign', '/fd', 'SHA256', '/a', executable], { + stdio: 'inherit', + }); +} + +function removeWindowsExecutableSignature(executable: string) { + execFileSync('signtool', ['remove', '/s', executable], { + stdio: 'inherit', + }); +} + + export { patchMachOExecutable, removeMachOExecutableSignature, signMachOExecutable, + signWindowsExecutable, + removeWindowsExecutableSignature, }; diff --git a/lib/sea.ts b/lib/sea.ts index d028c54d..73e67549 100644 --- a/lib/sea.ts +++ b/lib/sea.ts @@ -14,7 +14,9 @@ import { NodeTarget, Target } from './types'; import { patchMachOExecutable, removeMachOExecutableSignature, + removeWindowsExecutableSignature, signMachOExecutable, + signWindowsExecutable, } from './mach-o'; const exec = util.promisify(cExec); @@ -281,16 +283,28 @@ async function bake( await copyFile(nodePath, outPath); log.info(`Injecting the blob into ${outPath}...`); + + let command = `npx postject "${outPath}" NODE_SEA_BLOB "${blobPath}" --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2`; + if (target.platform === 'macos') { removeMachOExecutableSignature(outPath); - await exec( - `npx postject "${outPath}" NODE_SEA_BLOB "${blobPath}" --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --macho-segment-name NODE_SEA`, - ); - } else { - await exec( - `npx postject "${outPath}" NODE_SEA_BLOB "${blobPath}" --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2`, - ); + command = `${command} --macho-segment-name NODE_SEA`; + } else if (target.platform === 'win') { + try { + removeWindowsExecutableSignature(outPath); + } catch (error) { + log.warn('Unable to remove the signature from the Windows executable', [ + 'To sign the executable, you need to have signtool.exe installed.', + 'You can install it by installing Windows SDK.', + 'After that, you can run pkg with --signature option again.', + ]); + } + } + + log.info(`Running command: ${command}`); + + await exec(command); } /** Create NodeJS executable using sea */ @@ -346,24 +360,36 @@ export default async function sea(entryPoint: string, opts: SeaOptions) { const target = opts.targets[i]; await bake(nodePath, target, blobPath); const output = target.output!; - if (opts.signature && target.platform === 'macos') { - const buf = patchMachOExecutable(await readFile(output)); - await writeFile(output, buf); - - try { - // sign executable ad-hoc to workaround the new mandatory signing requirement - // users can always replace the signature if necessary - signMachOExecutable(output); - } catch { - if (target.arch === 'arm64') { - log.warn('Unable to sign the macOS executable', [ - 'Due to the mandatory code signing requirement, before the', - 'executable is distributed to end users, it must be signed.', - 'Otherwise, it will be immediately killed by kernel on launch.', - 'An ad-hoc signature is sufficient.', - 'To do that, run pkg on a Mac, or transfer the executable to a Mac', - 'and run "codesign --sign - ", or (if you use Linux)', - 'install "ldid" utility to PATH and then run pkg again', + if (opts.signature) { + if (target.platform === 'macos') { + const buf = patchMachOExecutable(await readFile(output)); + await writeFile(output, buf); + + try { + // sign executable ad-hoc to workaround the new mandatory signing requirement + // users can always replace the signature if necessary + signMachOExecutable(output); + } catch { + if (target.arch === 'arm64') { + log.warn('Unable to sign the macOS executable', [ + 'Due to the mandatory code signing requirement, before the', + 'executable is distributed to end users, it must be signed.', + 'Otherwise, it will be immediately killed by kernel on launch.', + 'An ad-hoc signature is sufficient.', + 'To do that, run pkg on a Mac, or transfer the executable to a Mac', + 'and run "codesign --sign - ", or (if you use Linux)', + 'install "ldid" utility to PATH and then run pkg again', + ]); + } + } + } else if (target.platform === 'win') { + try { + signWindowsExecutable(output); + } catch { + log.warn('Unable to sign the Windows executable', [ + 'To sign the executable, you need to have signtool.exe installed.', + 'You can install it by installing Windows SDK.', + 'After that, you can run pkg with --signature option again.', ]); } } From 7e47f7c84ffd3d589928484d4d85fb9f96ec81ba Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 28 Oct 2024 11:59:37 +0100 Subject: [PATCH 3/6] fix: sign command --- lib/mach-o.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mach-o.ts b/lib/mach-o.ts index 85e3c515..6710df18 100644 --- a/lib/mach-o.ts +++ b/lib/mach-o.ts @@ -79,7 +79,7 @@ function removeMachOExecutableSignature(executable: string) { } function signWindowsExecutable(executable: string) { - execFileSync('signtool', ['sign', '/fd', 'SHA256', '/a', executable], { + execFileSync('signtool', ['sign', '/fd', 'SHA256', executable], { stdio: 'inherit', }); } From 28b80343c74909f04e96735f9d00238a818d756c Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 28 Oct 2024 12:02:08 +0100 Subject: [PATCH 4/6] fix: add force options to remove --- lib/sea.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sea.ts b/lib/sea.ts index 73e67549..a1aaee33 100644 --- a/lib/sea.ts +++ b/lib/sea.ts @@ -400,7 +400,7 @@ export default async function sea(entryPoint: string, opts: SeaOptions) { throw new Error(`Error while creating the executable: ${error}`); } finally { // cleanup the temp directory - await rm(tmpDir, { recursive: true }).catch(() => { + await rm(tmpDir, { recursive: true, force: true }).catch(() => { log.warn(`Failed to cleanup the temp directory ${tmpDir}`); }); } From e871c248590df3189fd6ad681caf2a2543e2be56 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 4 Nov 2024 10:28:24 +0100 Subject: [PATCH 5/6] style: fix lint --- lib/mach-o.ts | 1 - lib/sea.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/mach-o.ts b/lib/mach-o.ts index 6710df18..2a721b8e 100644 --- a/lib/mach-o.ts +++ b/lib/mach-o.ts @@ -90,7 +90,6 @@ function removeWindowsExecutableSignature(executable: string) { }); } - export { patchMachOExecutable, removeMachOExecutableSignature, diff --git a/lib/sea.ts b/lib/sea.ts index a1aaee33..1dc84fa3 100644 --- a/lib/sea.ts +++ b/lib/sea.ts @@ -285,7 +285,7 @@ async function bake( log.info(`Injecting the blob into ${outPath}...`); let command = `npx postject "${outPath}" NODE_SEA_BLOB "${blobPath}" --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2`; - + if (target.platform === 'macos') { removeMachOExecutableSignature(outPath); command = `${command} --macho-segment-name NODE_SEA`; @@ -299,7 +299,6 @@ async function bake( 'After that, you can run pkg with --signature option again.', ]); } - } log.info(`Running command: ${command}`); From 9d3dacd824af526328446ca20d8fa50f63adfa25 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 4 Nov 2024 10:36:57 +0100 Subject: [PATCH 6/6] fix: remove with attemps --- lib/sea.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/sea.ts b/lib/sea.ts index 1dc84fa3..4f0e2a64 100644 --- a/lib/sea.ts +++ b/lib/sea.ts @@ -9,6 +9,7 @@ import { createHash } from 'crypto'; import { homedir, tmpdir } from 'os'; import unzipper from 'unzipper'; import { extract as tarExtract } from 'tar'; +import { setTimeout } from 'timers/promises'; import { log } from './log'; import { NodeTarget, Target } from './types'; import { @@ -306,6 +307,24 @@ async function bake( await exec(command); } +async function rmRf(path: string, attemps = 1) { + // prevent EBUSY error on Windows + if (process.platform === 'win32') { + await setTimeout(100); + } + + attemps -= 1; + + // cleanup the temp directory + await rm(path, { recursive: true, force: true }).catch((err) => { + if (attemps <= 0) { + throw new Error(`Failed to remove directory ${path}: ${err}`); + } else { + return rmRf(path, attemps); + } + }); +} + /** Create NodeJS executable using sea */ export default async function sea(entryPoint: string, opts: SeaOptions) { entryPoint = resolve(process.cwd(), entryPoint); @@ -398,9 +417,6 @@ export default async function sea(entryPoint: string, opts: SeaOptions) { } catch (error) { throw new Error(`Error while creating the executable: ${error}`); } finally { - // cleanup the temp directory - await rm(tmpDir, { recursive: true, force: true }).catch(() => { - log.warn(`Failed to cleanup the temp directory ${tmpDir}`); - }); + await rmRf(tmpDir, 3); } }