Showcase: sovereign HTTPS demo server + local CA
Some checks failed
ci / check (push) Has been cancelled

serve-demo.mjs serves dist/ (app shell, SPA fallback) and instance/origin/
(signed packages + latest.json) on one HTTPS port; generates /sync-config.json
with the pinned signing-key fingerprint from the published package and offers
/rootCA.pem for phone trust. gen-certs.sh creates a local ECDSA CA + server
cert with SANs for localhost, hotspot 10.42.0.1, and current interface IPs.
No tunnels, no third parties: everything runs on this laptop. npm run
demo:certs / demo:serve.
This commit is contained in:
Lumen Stage1 2026-10-02 12:40:17 -05:00
commit 0e7d85b862
4 changed files with 191 additions and 0 deletions

145
scripts/serve-demo.mjs Normal file
View file

@ -0,0 +1,145 @@
#!/usr/bin/env node
/* eslint-disable no-console -- this is the demo server CLI; stdout is its interface */
/**
* Sovereign demo server — HTTPS file server for the phone showcase.
* Serves the built app shell (dist/) and the signed origin tree
* (instance/origin/) on one HTTPS port, no third parties involved.
*
* Routes:
* /editions/** → origin tree (immutable packages)
* /latest.json → origin pointer (mutable)
* /sync-config.json → pinned trust config for the app
* /rootCA.pem → the CA cert phones must install to trust us
* everything else → dist/ (app shell, SPA fallback to index.html)
*
* Usage: node scripts/serve-demo.mjs [--port 8443] [--host 0.0.0.0]
* [--app dist] [--origin instance/origin] [--certs instance/certs]
* [--edition lumen-2026]
*/
import { createServer } from "node:https";
import { createHash } from "node:crypto";
import { readFileSync, existsSync, statSync } from "node:fs";
import { join, normalize, extname } from "node:path";
const argv = process.argv.slice(2);
function arg(name, dflt) {
const i = argv.indexOf(`--${name}`);
return i >= 0 && argv[i + 1] ? argv[i + 1] : dflt;
}
const port = Number(arg("port", "8443"));
const host = arg("host", "0.0.0.0");
const appDir = arg("app", "dist");
const originDir = arg("origin", "instance/origin");
const certsDir = arg("certs", "instance/certs");
const edition = arg("edition", "lumen-2026");
const keyPath = join(certsDir, "server-key.pem");
const certPath = join(certsDir, "server.pem");
const caPath = join(certsDir, "rootCA.pem");
for (const p of [keyPath, certPath, caPath]) {
if (!existsSync(p)) {
console.error(`missing ${p} — run scripts/gen-certs.sh first`);
process.exit(1);
}
}
if (!existsSync(join(appDir, "index.html"))) {
console.error(`missing ${appDir}/index.html — run npm run build first`);
process.exit(1);
}
const MIME = {
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
".json": "application/json; charset=utf-8",
".pem": "application/x-pem-file",
".png": "image/png",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".webmanifest": "application/manifest+json",
};
function send(res, code, body, type) {
res.writeHead(code, {
"content-type": type,
"cache-control": "no-store",
"access-control-allow-origin": "*",
});
res.end(body);
}
function sendFile(res, path) {
const data = readFileSync(path);
const type = MIME[extname(path)] ?? "application/octet-stream";
// Immutable by contract: content-addressed package files under /editions/.
const immutable = path.includes("/packages/");
res.writeHead(200, {
"content-type": type,
"cache-control": immutable ? "public, max-age=31536000, immutable" : "no-store",
});
res.end(data);
}
function syncConfig() {
// Pinned trust for the app: fingerprint -> SPKI DER base64.
// Test key published by the pipeline next to the package (demo mode only).
const latest = JSON.parse(readFileSync(join(originDir, "latest.json"), "utf8"));
const pkgDir = join(
originDir,
"editions",
latest.edition,
"packages",
String(latest.packageVersion),
);
const pem = readFileSync(join(pkgDir, "publicKey.pem"), "utf8");
const derB64 = pem
.replace(/-----BEGIN PUBLIC KEY-----/g, "")
.replace(/-----END PUBLIC KEY-----/g, "")
.replace(/\s/g, "");
const digest = createHash("sha256").update(Buffer.from(derB64, "base64")).digest("hex");
return JSON.stringify({
edition: latest.edition,
origin: "https://REPLACE_HOST",
trustedKeys: { [`sha256:${digest}`]: derB64 },
});
}
const server = createServer(
{ key: readFileSync(keyPath), cert: readFileSync(certPath) },
(req, res) => {
const url = new URL(req.url ?? "/", `https://${req.headers.host ?? "localhost"}`);
const path = normalize(decodeURIComponent(url.pathname));
console.log(`${req.method} ${path}`);
if (path === "/sync-config.json") {
try {
const hostHeader = req.headers.host ?? `localhost:${port}`;
const conf = syncConfig().replace("https://REPLACE_HOST", `https://${hostHeader}`);
return send(res, 200, conf, MIME[".json"]);
} catch (e) {
return send(res, 500, JSON.stringify({ error: String(e) }), MIME[".json"]);
}
}
if (path === "/rootCA.pem") return sendFile(res, caPath);
if (path.startsWith("/editions/") || path === "/latest.json") {
const filePath = join(originDir, path);
if (filePath.startsWith(originDir) && existsSync(filePath) && statSync(filePath).isFile())
return sendFile(res, filePath);
return send(res, 404, "not found", "text/plain");
}
const appPath = join(appDir, path === "/" ? "index.html" : path);
if (appPath.startsWith(appDir) && existsSync(appPath) && statSync(appPath).isFile())
return sendFile(res, appPath);
// SPA fallback
return sendFile(res, join(appDir, "index.html"));
},
);
server.listen(port, host, () => {
console.log(`Lumen demo server (edition ${edition})`);
console.log(` app : ${appDir}`);
console.log(` origin : ${originDir}`);
console.log(` listening on https://${host}:${port}`);
});