From 347b4f654aaea3e2b1a11edc4b82030cf8b7311c Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 19 Mar 2026 19:56:24 +0100 Subject: [PATCH] Use nginx to serve the website --- README.md | 13 ++++++----- nginx.conf.template | 26 ++++++++++++++++++++++ nixpacks.toml | 6 ++--- serve-locally.sh | 9 +++++++- start.sh | 11 ++++++++++ tests/e2e/playwright.config.js | 2 +- web/serve.mjs | 40 ++++++++++++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 nginx.conf.template create mode 100644 start.sh create mode 100644 web/serve.mjs diff --git a/README.md b/README.md index 8f5565c..bc5fb18 100644 --- a/README.md +++ b/README.md @@ -42,16 +42,15 @@ web/ js/ app.js # ES module entry point: step navigation, flow orchestration kobo-device.js # KoboModels, KoboDevice class - kobo-software-urls.js # KoboSoftwareUrls, getSoftwareUrl, getDevicesForVersion + kobo-software-urls.js # Fetches download URLs from JSON, getSoftwareUrl, getDevicesForVersion nickelmenu.js # NickelMenuInstaller: downloads/bundles NickelMenu + config patch-ui.js # PatchUI: loads patches, parses YAML, renders toggle UI patch-runner.js # KoboPatchRunner: spawns Web Worker per build patch-worker.js # Web Worker: loads WASM, runs patchFirmware() - wasm_exec.js # Go WASM runtime (generated by build.sh, gitignored) - wasm/ - kobopatch.wasm # Compiled WASM binary (generated by build.sh, gitignored) + wasm_exec.js # Go WASM runtime (copied from Go SDK by build.sh, gitignored) patches/ index.json # Available patch manifest + downloads.json # Firmware download URLs by version/serial (may be auto-generated) patches_*.zip # Patch files per firmware version nickelmenu/ # NickelMenu assets (generated by setup.sh, gitignored) NickelMenu.zip @@ -85,7 +84,7 @@ tests/ ## Adding a new software version 1. Add the patch zip to `web/src/patches/` and update `index.json` -2. Add download URLs to `KoboSoftwareUrls` in `web/src/js/kobo-software-urls.js` (keyed by version then serial prefix) +2. Add download URLs to `web/src/patches/downloads.json` (keyed by version then serial prefix) 3. The Kobo CDN prefix per device family (e.g. `kobo12`, `kobo13`) is stable; the date path segment changes per release ## Building the WASM binary @@ -138,7 +137,9 @@ You can delete the entire `web/dist/` folder and re-run `serve-locally.sh` to re The E2E tests cover all major user flows: - **NickelMenu** — install with config (manual download), install NickelMenu only, remove option disabled without device -- **Custom patches** — full patching pipeline, restore original firmware +- **Custom patches** — full patching pipeline, restore original firmware, build failure with "Go Back" recovery +- **Device detection** — firmware version validation (4.x supported, 5.x incompatible), unknown model warning +- **Back navigation** — verifies every back button returns to the correct previous screen in both auto and manual mode - **With simulated Kobo Libra Color** — install NickelMenu with config, remove NickelMenu, install custom patches, restore firmware The simulated device tests mock the File System Access API with an in-memory filesystem that mimics a Kobo Libra Color (serial prefix N428, firmware 4.45.23646). diff --git a/nginx.conf.template b/nginx.conf.template new file mode 100644 index 0000000..1771495 --- /dev/null +++ b/nginx.conf.template @@ -0,0 +1,26 @@ +worker_processes auto; +error_log stderr; +pid /tmp/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log off; + sendfile on; + gzip on; + gzip_types text/html text/css application/javascript application/json application/wasm; + + server { + listen ${PORT} default_server; + root web/dist; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + } +} diff --git a/nixpacks.toml b/nixpacks.toml index cd49668..7ba7b38 100644 --- a/nixpacks.toml +++ b/nixpacks.toml @@ -1,7 +1,7 @@ -providers = ["python"] +providers = ["node"] [phases.setup] -nixPkgs = ["git", "curl", "python3Minimal", "zip", "gnutar", "nodejs_22"] +nixPkgs = ["git", "curl", "zip", "gnutar", "nginx"] paths = ["/usr/local/go/bin"] cmds = [ "curl -sSfL https://go.dev/dl/go1.23.12.linux-amd64.tar.gz | tar -xz -C /usr/local", @@ -16,4 +16,4 @@ cmds = [ ] [start] -cmd = "python3 -m http.server ${PORT:-8080} -d web/dist" +cmd = "./start.sh" diff --git a/serve-locally.sh b/serve-locally.sh index 4344740..d8839e2 100755 --- a/serve-locally.sh +++ b/serve-locally.sh @@ -26,4 +26,11 @@ if [ ! -f "$DIST_DIR/wasm/kobopatch.wasm" ]; then fi echo "Serving at http://localhost:8888" -python3 -m http.server -d "$DIST_DIR" 8888 +if command -v nginx &>/dev/null; then + export PORT=8888 + envsubst '${PORT}' < "$SCRIPT_DIR/nginx.conf.template" > /tmp/kobopatch-nginx.conf + nginx -c /tmp/kobopatch-nginx.conf -g 'daemon off;' +else + echo "(nginx not found, using Node.js server)" + node serve.mjs +fi diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..057d67c --- /dev/null +++ b/start.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PORT="${PORT:-8080}" + +# Generate nginx config from template +export PORT +envsubst '${PORT}' < "$SCRIPT_DIR/nginx.conf.template" > /tmp/nginx.conf + +exec nginx -c /tmp/nginx.conf -g 'daemon off;' diff --git a/tests/e2e/playwright.config.js b/tests/e2e/playwright.config.js index f7d46f8..f524752 100644 --- a/tests/e2e/playwright.config.js +++ b/tests/e2e/playwright.config.js @@ -17,7 +17,7 @@ module.exports = defineConfig({ }, }, webServer: { - command: 'cd ../../web && npm install && node build.mjs && cd ../kobopatch-wasm && bash build.sh && cd ../web && python3 -m http.server -d dist 8889', + command: 'cd ../../web && npm install && node build.mjs && cd ../kobopatch-wasm && bash build.sh && cd ../web && PORT=8889 node serve.mjs', port: 8889, reuseExistingServer: true, }, diff --git a/web/serve.mjs b/web/serve.mjs new file mode 100644 index 0000000..52969ef --- /dev/null +++ b/web/serve.mjs @@ -0,0 +1,40 @@ +import { createServer } from 'node:http'; +import { createReadStream, statSync, existsSync } from 'node:fs'; +import { join, extname } from 'node:path'; + +const DIST = join(import.meta.dirname, 'dist'); +const PORT = process.env.PORT || 8888; + +const MIME = { + '.html': 'text/html', + '.css': 'text/css', + '.js': 'application/javascript', + '.json': 'application/json', + '.wasm': 'application/wasm', + '.zip': 'application/zip', + '.tgz': 'application/gzip', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.ico': 'image/x-icon', + '.webmanifest': 'application/manifest+json', +}; + +createServer((req, res) => { + const url = new URL(req.url, `http://localhost`); + let filePath = join(DIST, decodeURIComponent(url.pathname)); + + if (filePath.endsWith('/')) filePath = join(filePath, 'index.html'); + if (!extname(filePath) && existsSync(filePath + '/index.html')) filePath += '/index.html'; + + try { + const stat = statSync(filePath); + if (!stat.isFile()) throw new Error(); + res.writeHead(200, { 'Content-Type': MIME[extname(filePath)] || 'application/octet-stream' }); + createReadStream(filePath).pipe(res); + } catch { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not found'); + } +}).listen(PORT, () => { + console.log(`Serving web/dist on http://localhost:${PORT}`); +});