From 49c356059ae0dcd8da5f26adb931e5a5ffc0ef7b Mon Sep 17 00:00:00 2001 From: Lorenzo Blasa Date: Wed, 31 May 2023 01:06:17 -0700 Subject: [PATCH] Use a direct download from Node distribution instead of relying on pkg-fetch Summary: It seems the available binary for macOS arm64 is not signed: ``` codesign -dv --verbose=4 ./node-v16.16.0-macos-arm64 ./node-v16.16.0-macos-arm64: code object is not signed ``` This is an issue as it crashes for our flipper server releases. This can be compared to a binary downloaded from the Node distribution page: ``` codesign -dv --verbose=4 ./node Executable=/Users/realpassy/Downloads/node-v16.15.0-darwin-arm64/bin/node Identifier=node Format=Mach-O thin (arm64) CodeDirectory v=20500 size=597360 flags=0x10000(runtime) hashes=18657+7 location=embedded VersionPlatform=1 VersionMin=720896 VersionSDK=721152 Hash type=sha256 size=32 CandidateCDHash sha256=31cdf84cac42a622c1a68558376700a2dd12d40d CandidateCDHashFull sha256=31cdf84cac42a622c1a68558376700a2dd12d40d81c5118f3b0e0370c414eb69 Hash choices=sha256 CMSDigest=31cdf84cac42a622c1a68558376700a2dd12d40d81c5118f3b0e0370c414eb69 CMSDigestType=2 Executable Segment base=0 Executable Segment limit=56082432 Executable Segment flags=0x1 Page size=4096 Launch Constraints: None CDHash=31cdf84cac42a622c1a68558376700a2dd12d40d Signature size=8986 Authority=Developer ID Application: Node.js Foundation (HX7739G8FX) Authority=Developer ID Certification Authority Authority=Apple Root CA Timestamp=26 Apr 2022 at 23:00:57 Info.plist=not bound TeamIdentifier=HX7739G8FX Runtime Version=11.1.0 Sealed Resources=none Internal requirements count=1 size=164 ``` For additional context: Node binary by using pkg-fetch. We just get the binary from this release page: https://github.com/vercel/pkg-fetch/releases/tag/v3.4 The exact binary we're downloading is https://github.com/vercel/pkg-fetch/releases/download/v3.4/node-v16.15.0-macos-arm64 If you just download that via Chrome, it will trigger Gatekeeper and you need to manually click "Allow" in the system privacy settings. You can also do the same by just running: xattr -c ./node-v16* Afterwards you can: chmod +x ./node* It will crash in the same way that it does after our distribution. Reviewed By: passy Differential Revision: D46225503 fbshipit-source-id: f0ae2d5101b99c9db7fe80333573caef52c787a2 --- .../scripts/build-flipper-server-release.tsx | 130 ++++++++++++++++-- desktop/scripts/package.json | 1 + desktop/yarn.lock | 17 +++ 3 files changed, 140 insertions(+), 8 deletions(-) diff --git a/desktop/scripts/build-flipper-server-release.tsx b/desktop/scripts/build-flipper-server-release.tsx index 05256d4aa..acb23ab22 100644 --- a/desktop/scripts/build-flipper-server-release.tsx +++ b/desktop/scripts/build-flipper-server-release.tsx @@ -9,6 +9,9 @@ const dotenv = require('dotenv').config(); import path from 'path'; +import https from 'https'; +import os from 'os'; +import tar from 'tar'; import { buildBrowserBundle, buildFolder, @@ -35,6 +38,9 @@ import {need as pkgFetch} from 'pkg-fetch'; // This needs to be tested individually. As of 2022Q2, node17 is not supported. const SUPPORTED_NODE_PLATFORM = 'node16'; +// Node version below is only used for macOS AARCH64 builds as we download +// the binary directly from Node distribution site instead of relying on pkg-fetch. +const NODE_VERSION = 'v16.15.0'; enum BuildPlatform { LINUX = 'linux', @@ -461,6 +467,69 @@ function nodeArchFromBuildPlatform(platform: BuildPlatform): string { return 'x64'; } +/** + * Downloads a file located at the given URL and saves it to the destination path.. + * @param url - URL of the file to download. + * @param dest - Destination path for the downloaded file. + * @returns - A promise that resolves when the file is downloaded. + * If the file can't be downloaded, it rejects with an error. + */ +async function download(url: string, dest: string): Promise { + // First, check if the file already exists and remove it. + try { + await fs.access(dest, fs.constants.F_OK); + await fs.unlink(dest); + } catch (err) {} + + return new Promise((resolve, reject) => { + // Then, download the file and save it to the destination path. + const file: fs.WriteStream = fs.createWriteStream(dest); + https + .get(url, (response) => { + response.pipe(file); + file.on('finish', () => { + file.close(); + console.log(`✅ Download successful ${url}.`); + resolve(); + }); + }) + .on('error', (error: Error) => { + fs.unlink(dest); + reject(error); + }); + }); +} + +/** + * Unpacks a tarball and extracts the contents to a directory. + * @param source - Source tarball. + * @param dest - Destination directory for the extracted contents. + */ +async function unpack(source: string, destination: string) { + console.log(`⚙️ Extracting ${source}.`); + + try { + await fs.access(destination, fs.constants.F_OK); + await fs.rm(destination, {recursive: true, force: true}); + } catch (err) {} + + await fs.mkdir(destination); + + try { + await tar.x({ + file: source, + strip: 1, + cwd: destination, + }); + + console.log(`✅ Extraction completed.`); + } catch (error) { + console.error( + `⚙️ Error found whilst trying to extract '${source}'. Found: ${error}`, + ); + } +} + function nodePlatformFromBuildPlatform(platform: BuildPlatform): string { switch (platform) { case BuildPlatform.LINUX: @@ -476,14 +545,59 @@ function nodePlatformFromBuildPlatform(platform: BuildPlatform): string { } async function installNodeBinary(outputPath: string, platform: BuildPlatform) { - console.log(`⚙️ Downloading node version for ${platform} using pkg-fetch`); - const path = await pkgFetch({ - arch: nodeArchFromBuildPlatform(platform), - platform: nodePlatformFromBuildPlatform(platform), - nodeRange: SUPPORTED_NODE_PLATFORM, - }); - console.log(`⚙️ Copying node binary from ${path} to ${outputPath}`); - await fs.copyFile(path, outputPath); + /** + * Below is a temporary patch that doesn't use pkg-fetch to + * download a node binary for macOS arm64. + * This will be removed once there's a properly + * signed binary for macOS arm64 architecture. + */ + if (platform === BuildPlatform.MAC_AARCH64) { + const temporaryDirectory = os.tmpdir(); + const name = `node-${NODE_VERSION}-darwin-arm64`; + const downloadOutputPath = path.resolve( + temporaryDirectory, + `${name}.tar.gz`, + ); + const unpackedOutputPath = path.resolve(temporaryDirectory, name); + let nodePath = path.resolve(unpackedOutputPath, 'bin', 'node'); + console.log( + `⚙️ Downloading node version for ${platform} using temporary patch.`, + ); + + // Check local cache. + let cached = false; + try { + const cachePath = path.join(homedir(), '.node', name); + await fs.access(cachePath, fs.constants.F_OK); + console.log(`⚙️ Cached artifact found, skip download.`); + nodePath = path.resolve(cachePath, 'bin', 'node'); + cached = true; + } catch (err) {} + if (!cached) { + // Download node tarball from the distribution site. + await download( + `https://nodejs.org/dist/${NODE_VERSION}/${name}.tar.gz`, + downloadOutputPath, + ); + // Finally, unpack the tarball to a local folder i.e. outputPath. + await unpack(downloadOutputPath, unpackedOutputPath); + console.log(`✅ Node successfully downloaded and unpacked.`); + } + + console.log(`⚙️ Copying node binary from ${nodePath} to ${outputPath}`); + await fs.copyFile(nodePath, outputPath); + } else { + console.log(`⚙️ Downloading node version for ${platform} using pkg-fetch`); + const nodePath = await pkgFetch({ + arch: nodeArchFromBuildPlatform(platform), + platform: nodePlatformFromBuildPlatform(platform), + nodeRange: SUPPORTED_NODE_PLATFORM, + }); + + console.log(`⚙️ Copying node binary from ${nodePath} to ${outputPath}`); + await fs.copyFile(nodePath, outputPath); + } + // Set +x on the binary as copyFile doesn't maintain the bit. await fs.chmod(outputPath, 0o755); } diff --git a/desktop/scripts/package.json b/desktop/scripts/package.json index 8bf66ae2e..47f178a84 100644 --- a/desktop/scripts/package.json +++ b/desktop/scripts/package.json @@ -34,6 +34,7 @@ "pkg-fetch": "3.4.1", "promisify-child-process": "^4.1.0", "socket.io": "^4.5.0", + "tar": "6.1.15", "tmp": "^0.2.1", "uuid": "^8.3.2", "yargs": "^17.6.0" diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 2fea644d7..279696b6b 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -11453,6 +11453,11 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -14526,6 +14531,18 @@ tar-stream@^2.0.0, tar-stream@^2.1.4, tar-stream@^2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar@6.1.15: + version "6.1.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" + integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + tar@^6.1.10, tar@^6.1.11: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"