Use nginx to serve the website
All checks were successful
Build and test project / build-and-test (push) Successful in 1m29s
All checks were successful
Build and test project / build-and-test (push) Successful in 1m29s
This commit is contained in:
13
README.md
13
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).
|
||||
|
||||
26
nginx.conf.template
Normal file
26
nginx.conf.template
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
11
start.sh
Normal file
11
start.sh
Normal file
@@ -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;'
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
40
web/serve.mjs
Normal file
40
web/serve.mjs
Normal file
@@ -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}`);
|
||||
});
|
||||
Reference in New Issue
Block a user