From de75459e970471e5c25a41dc605950ddb9396574 Mon Sep 17 00:00:00 2001 From: k2patel <1+k2patel@git.k2patel.in> Date: Wed, 30 Sep 2026 21:14:48 -0400 Subject: [PATCH] Fix Flood 4.7 Node runtime compatibility and gate publishing on startup --- .gitea/workflows/build-push.yml | 1 + Dockerfile.client | 5 +++- README.md | 6 +++- tests/startup.cjs | 52 +++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/startup.cjs diff --git a/.gitea/workflows/build-push.yml b/.gitea/workflows/build-push.yml index 83c6d5b..26f54b9 100644 --- a/.gitea/workflows/build-push.yml +++ b/.gitea/workflows/build-push.yml @@ -32,6 +32,7 @@ jobs: git -C flood-repo fetch --depth=1 https://github.com/jesec/flood.git "refs/tags/$FLOOD_VERSION" git -C flood-repo checkout --detach FETCH_HEAD cp Dockerfile.client flood-repo/Dockerfile.client + cp tests/startup.cjs flood-repo/.ci-startup.cjs printf '\n.git\n.github\n.env\n' >> flood-repo/.dockerignore - name: Install Docker CLI diff --git a/Dockerfile.client b/Dockerfile.client index fe5374f..5d8d108 100644 --- a/Dockerfile.client +++ b/Dockerfile.client @@ -2,7 +2,7 @@ # Build from a clean upstream release checkout using the Gitea workflow. ARG BUILDPLATFORM=amd64 -ARG NODE_IMAGE=docker.io/node:24-alpine3.24 +ARG NODE_IMAGE=docker.io/node:22-alpine3.24 FROM --platform=$BUILDPLATFORM ${NODE_IMAGE} as nodebuild @@ -48,5 +48,8 @@ RUN apk add coreutils # System local configuration VOLUME /server/db/ +# Fail the build before publication if the actual server cannot start. +RUN node .ci-startup.cjs && rm .ci-startup.cjs + # Flood server in development mode ENTRYPOINT ["npm", "--prefix=/usr/src/app/", "run", "start:development:server", "--", "--host=0.0.0.0", "--rundir=/server/"] diff --git a/README.md b/README.md index 8e727cb..e9c5088 100644 --- a/README.md +++ b/README.md @@ -24,5 +24,9 @@ runner image store, and the temporary builder and cache are removed afterward. No image archives or build records are uploaded to Gitea, and no local container builds are required. -Base image: Node.js 24 on Alpine Linux 3.24 stable (`node:24-alpine3.24`), +Base image: Node.js 22 on Alpine Linux 3.24 stable (`node:22-alpine3.24`), including the latest patch updates in those release series. + +Flood 4.7 requires Node 22 because its legacy SPDY dependency uses an internal +HTTP parser removed in Node 24. The image build starts the actual Flood server +and requires HTTP 200 before publishing; it does not start a torrent daemon. diff --git a/tests/startup.cjs b/tests/startup.cjs new file mode 100644 index 0000000..1c5ba12 --- /dev/null +++ b/tests/startup.cjs @@ -0,0 +1,52 @@ +const {spawn} = require('node:child_process'); +const http = require('node:http'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const runDir = fs.mkdtempSync(path.join(os.tmpdir(), 'flood-smoke-')); +const child = spawn('npm', ['run', 'start:development:server', '--', + '--host=127.0.0.1', '--port=3000', `--rundir=${runDir}`], + {stdio: 'inherit', detached: true}); +let startupError; +child.on('error', error => { startupError = error; }); +const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); +const check = () => new Promise(resolve => { + const req = http.get('http://127.0.0.1:3000/', response => { + let bytes = 0; + response.on('data', chunk => { bytes += chunk.length; }); + response.on('end', () => resolve(response.statusCode === 200 && bytes > 0)); + }); + req.setTimeout(1000, () => req.destroy()); + req.on('error', () => resolve(false)); +}); +(async () => { + try { + const deadline = Date.now() + 60000; + while (Date.now() < deadline) { + if (startupError) throw startupError; + if (child.exitCode !== null || child.signalCode !== null) { + throw new Error(`Flood exited before serving HTTP: ${child.exitCode ?? child.signalCode}`); + } + if (await check()) { + console.log('Flood startup smoke test passed: HTTP 200 with response body'); + return; + } + await sleep(500); + } + throw new Error('Flood did not serve HTTP within 60 seconds'); + } catch (error) { + console.error(error); + process.exitCode = 1; + } finally { + if (child.pid) { + try { process.kill(-child.pid, 'SIGTERM'); } catch (error) { + if (error.code !== 'ESRCH') throw error; + } + await sleep(500); + try { process.kill(-child.pid, 'SIGKILL'); } catch (error) { + if (error.code !== 'ESRCH') throw error; + } + } + fs.rmSync(runDir, {recursive: true, force: true}); + } +})();