2import * as _ from 'lodash-es' 30const ensureLocalDockerAuth = async ({dockreg_host, dockLanHost}: {dockreg_host: string, dockLanHost?: string}) => { 32 const dockerConfigJson = JSON.parse(await read1File(dockConfigPath)) 34 _.set(dockerConfigJson, ['auths', dockreg_host], authH) 35 if (dockLanHost) _.set(dockerConfigJson, ['auths', dockLanHost], authH) 36 dockerConfigJson.credsStore = '' 37 await write1File(dockConfigPath, JSON.stringify(dockerConfigJson, null, 2)) 40const computeNixContentHash = ({nixExprContent, git_sha}: {nixExprContent: string, git_sha: string}) => { 41 const stripped = nixExprContent.replace(/outputHash\s*=\s*"[^"]*"/, 'outputHash=""') 42 return calcReqHash([stripped, git_sha], 'sha256').substring(0, 12) 45const getCachedPnpmDepsHash = async () => { 47 const cached = JSON.parse(await read1File(pnpmDepsHashCachePath)) 49 if (cached.lockHash === currentLockHash) return cached.pnpmDepsHash as string | null 54const cachePnpmDepsHash = async (pnpmDepsHash: string) => { 57 await write1File(pnpmDepsHashCachePath, JSON.stringify({pnpmDepsHash, lockHash: currentLockHash})) 60const generateNixExpr = ({srcFilterPaths, appPath, svcPortNo, appPkgName, envJsonPath}: {srcFilterPaths: string[], appPath: string, svcPortNo: number, appPkgName: string, envJsonPath: string}) => { 61 const filterPaths = srcFilterPaths.map((p: string) => ` "${p}"`).join('\n') 63 return `{ pkgs ? import <nixpkgs> { system = "x86_64-linux"; } }: 65 nodejs = pkgs.nodejs_24; 68 srcFiltered = builtins.path { 73 relPath = pkgs.lib.removePrefix "${ptDir}/" (builtins.toString path); 74 isDir = type == "directory"; 78 isAllowedFile = builtins.elem relPath allowedFiles; 79 isParentOfAllowed = isDir && (builtins.any (f: pkgs.lib.hasPrefix (relPath + "/") f) allowedFiles); 80 in isAllowedFile || isParentOfAllowed; 83 dockerEnv = builtins.fromJSON (builtins.readFile ${envJsonPath}); 86 nodejs pnpm pkgs.python3 pkgs.pkg-config pkgs.cacert 87 pkgs.vips pkgs.cairo pkgs.pango pkgs.libjpeg pkgs.giflib pkgs.librsvg pkgs.pixman 91 pkgConfigPath = builtins.concatStringsSep ":" [ 92 "\${pkgs.cairo.dev}/lib/pkgconfig" 93 "\${pkgs.pango.dev}/lib/pkgconfig" 94 "\${pkgs.pixman}/lib/pkgconfig" 95 "\${pkgs.libjpeg.dev}/lib/pkgconfig" 96 "\${pkgs.giflib}/lib/pkgconfig" 97 "\${pkgs.librsvg.dev}/lib/pkgconfig" 98 "\${pkgs.glib.dev}/lib/pkgconfig" 99 "\${pkgs.freetype.dev}/lib/pkgconfig" 100 "\${pkgs.libpng.dev}/lib/pkgconfig" 101 "\${pkgs.harfbuzz.dev}/lib/pkgconfig" 104 # phase 1: pnpm install with network access (__noChroot) 105 # pnpm install output is non-deterministic (timestamps, ordering) so fixed-output hashing doesn't work 106 # __noChroot allows network in a regular derivation when using --impure 107 pnpmDeps = pkgs.stdenv.mkDerivation { 108 name = "${appPkgName}-pnpm-deps"; 110 nativeBuildInputs = nativeDeps; 115 export SSL_CERT_FILE=\${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt 116 export COREPACK_ENABLE_STRICT=0 117 export COREPACK_ENABLE_AUTO_PIN=0 118 export PATH="\${pnpm}/bin:$PATH" 119 export PKG_CONFIG_PATH="${"$"}{pkgConfigPath}:$PKG_CONFIG_PATH" 120 pnpm install --frozen-lockfile --filter ${appPkgName}... 127 # phase 2: regular derivation for next build — no network, no output hash 128 # non-deterministic output (webpack chunk hashes, timestamps) is fine here 129 builtApp = pkgs.stdenv.mkDerivation { 130 name = "${appPkgName}-built"; 132 nativeBuildInputs = nativeDeps; 136 export COREPACK_ENABLE_STRICT=0 137 export COREPACK_ENABLE_AUTO_PIN=0 138 export PATH="\${pnpm}/bin:$PATH" 139 export PKG_CONFIG_PATH="${"$"}{pkgConfigPath}:$PKG_CONFIG_PATH" 145 export NODE_ENV=production 146 export NODE_OPTIONS="--no-warnings=ExperimentalWarning --max-old-space-size=${getAppCfg().nextBuildHeapMb || 2048}" 147 export NEXT_TELEMETRY_DISABLED=1 149 cd $NIX_BUILD_TOP/$sourceRoot 152 mkdir -p $out/approot 153 cp -r . $out/approot/ 157in pkgs.dockerTools.buildLayeredImage { 160 contents = [ nodejs ]; 161 fakeRootCommands = '' 163 cp -a \${builtApp}/approot/* ./approot/ 164 mkdir -p ./approot/data 169 WorkingDir = "/approot/${appPath}"; 170 ExposedPorts = { "${svcPortNo}/tcp" = {}; }; 171 Cmd = [ "\${nodejs}/bin/node" "--enable-source-maps" "runnext.mjs" ]; 177const runNixBuild = async ({nixExprPath}: {nixExprPath: string}) => { 178 const cmd = `nix build --impure --file ${nixExprPath} --no-link --print-out-paths` 180 const ret = await liveSpawn({cmd: cmd + ' 2>&1', isQuiet: false, timeoutAfterSec: 1800}) 181 if (!ret.isSuccess) { 182 console.error('nix build failed. output:', ((ret.stderr || '') + (ret.stdout || '')).slice(-2000)) 183 throw new Error('nix build failed') 185 const nixStorePath = (ret.stdout || '').trim().split('\n').filter(l => l.startsWith('/nix/store/')).pop() 190export const nixBuildNextImg = async ({action, dockerEnv, git_sha, afterYarnInstallDockPathsA}: {action: string, dockerEnv: Record<string, string>, git_sha: string, afterYarnInstallDockPathsA: absFileDirPath[]}) => { 191 const {appPath, name, dockreg_host, dockLanHost} = getReqNextDeployCtx() as {appPath: string, name: string, dockreg_host: string, dockLanHost?: string} 195 const {pathsThatDontImportDevA, pathsThatImportDevDecA} = await filterDevOnlyPaths(afterYarnInstallDockPathsA) 196 const filteredPaths = [...pathsThatDontImportDevA, ...pathsThatImportDevDecA.map(d => d.path)] 199 const srcFilterPaths = _.uniq([...filteredPaths, ...pkgJsonPtPathsA, ...globalDependPaths]) as string[] 204 fs.mkdirSync(nixExprDir, {recursive: true}) 207 await write1File(envJsonPath, JSON.stringify(_.map(dockerEnv, (v, k) => `${k}=${v}`))) 209 const nixExpr = generateNixExpr({srcFilterPaths, appPath, svcPortNo, appPkgName, envJsonPath}) 210 const contentHash = computeNixContentHash({nixExprContent: nixExpr, git_sha}) 211 const syntheticDfContent = `nix:${contentHash}` 213 const {repo_name_tag, push_repo_name_tag} = await get2RepoNameTag({git_sha, dockerfileContent: syntheticDfContent}) 215 if (await doesDockImg2Exist({git_sha, dockerfileContent: syntheticDfContent, reqDockPush: true})) { 216 console.log(chalkGreen(`nix: image exists ${repo_name_tag}`)) 217 return [mkNixDockFile({repo_name_tag})] 223 console.log(`nix: building ${repo_name_tag}`) 225 const tarPath = await runNixBuild({nixExprPath}) 226 console.log(`nix: loading image...`) 227 await ensureLocalDockerAuth({dockreg_host, dockLanHost}) 229 await liveSpawnThrow({cmd: `${dockName} tag nextjs-nix:latest ${push_repo_name_tag}`, isQuiet: true}) 231 console.log(`nix: pushing ${push_repo_name_tag}`) 232 await liveSpawnThrow({cmd: `${dockName} push ${push_repo_name_tag}`, isQuiet: false, timeoutAfterSec: 900}) 234 console.log(chalkGreen(`nix: image pushed ${repo_name_tag}`)) 236 return [mkNixDockFile({repo_name_tag})] 239const mkNixDockFile = ({repo_name_tag}: {repo_name_tag: string}) => ({ 241 getRnt: async () => ({repo_name_tag, getRnt: async function() { return this }}),