Files
xteink-web-emulator/scripts/serve-web.py
T
evan 08f5e342b0 feat(web): add self-signed HTTPS mode for headless/LAN access
--tls generates a self-signed cert (SANs: localhost, 127.0.0.1, LAN IP) so a
headless box is reachable directly over HTTPS, which browsers treat as a secure
context (required for cross-origin isolation). make web now defaults to TLS.
2026-07-20 08:27:51 -04:00

74 lines
2.6 KiB
Python

#!/usr/bin/env python3
import argparse
import ipaddress
import socket
import ssl
import subprocess
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
class IsolatedHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
self.send_header("Cross-Origin-Resource-Policy", "same-origin")
super().end_headers()
def local_ip():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
try:
s.connect(("8.8.8.8", 80))
return s.getsockname()[0]
except OSError:
return "127.0.0.1"
def ensure_cert(cert: Path, key: Path):
"""Generate a self-signed cert covering localhost and the LAN IP, so a
headless box can be reached directly over HTTPS (secure context)."""
if cert.exists() and key.exists():
return
ip = local_ip()
san = f"subjectAltName=DNS:localhost,IP:127.0.0.1,IP:{ip}"
subprocess.run(
["openssl", "req", "-x509", "-newkey", "rsa:2048", "-sha256",
"-days", "3650", "-nodes", "-keyout", str(key), "-out", str(cert),
"-subj", "/CN=localhost", "-addext", san],
check=True,
)
print(f"Generated self-signed cert for localhost and {ip} at {cert}")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int, default=8000)
parser.add_argument("--host", default="0.0.0.0")
parser.add_argument("--tls", action="store_true", help="serve over HTTPS with a self-signed cert")
parser.add_argument("--cert", default=".serve-cert.pem")
parser.add_argument("--key", default=".serve-key.pem")
args = parser.parse_args()
server = ThreadingHTTPServer((args.host, args.port), IsolatedHandler)
scheme = "http"
if args.tls:
ensure_cert(Path(args.cert), Path(args.key))
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(args.cert, args.key)
server.socket = context.wrap_socket(server.socket, server_side=True)
scheme = "https"
print(f"Serving on {scheme}://{args.host}:{args.port}/web/")
if args.tls:
print(f"Reach it directly at {scheme}://{local_ip()}:{args.port}/web/ "
"(accept the self-signed cert warning once).")
else:
print("Note: WASM pthreads need a secure context — use localhost, an SSH "
"tunnel, or --tls; a bare LAN IP over HTTP is not cross-origin isolated.")
server.serve_forever()
if __name__ == "__main__":
main()