How this site works

A server-rendered shell plus small independent modules, all behind a single gateway. Each module is its own container, in whatever language suits it.

backend network: internal, no outbound internet, only reachable by service name pixels files ro writes uploads Browser Gateway · Caddy TLS · routing · rate limits · size caps · headers Static files css · htmx · WebGL · game Media volume photography uploads Renderer Go + C rasterizer · raytracer run on bundled scenes Ingest Rust only code that parses uploaded files PRNU Python · FastAPI sensor-noise analysis never sees a file Shell Java · Spring Boot pages · nav · admin Thymeleaf + HTMX

Design ideas

Request flows

Viewing a page

  1. The browser requests a page. The gateway terminates TLS and forwards anything it doesn't recognise to the shell.
  2. The shell renders a Thymeleaf template inside the shared layout and returns HTML.
  3. CSS, htmx, images and the WebGL projects come straight from disk through the gateway's /static/* handlers, without touching the shell.

PRNU upload and analysis

  1. The PRNU form posts up to six photos to /api/prnu/analyze. The gateway rate-limits per client (IPv6 grouped by /64) and caps the request body. In production it also asks the shell to verify a Cloudflare Turnstile token before any upload byte is forwarded.
  2. The gateway sends that one route to ingest. It streams the multipart body, checks magic bytes and headers, then decodes each file in a sandboxed child process.
  3. Ingest forwards only decoded pixels and a few sanitised strings (make, model, serial) to PRNU's /internal/jobs. The gateway answers 404 for /internal/*, so this path is service-to-service only.
  4. PRNU starts a job in a fresh process and returns a status fragment. HTMX polls /api/prnu/jobs/<id> every second, and those requests go straight to PRNU.
  5. When the job finishes, the result fragment replaces the status and polling stops. Pixel data is deleted when the job ends, and jobs expire after 15 minutes.

Rasterizer and raytracer

  1. The rendering detail page loads a widget from /api/renderer/{program} with HTMX. Changing the scene or size picker re-requests the fragment.
  2. The Go server accepts only GET, and visitors choose a scene by name from scenes.json and a scale from a fixed list. Nothing they send reaches the C code.
  3. On a cache miss, the server renders in a temp directory under prlimit and a timeout, one render at a time by default.
  4. Renders are deterministic, so results are cached in memory. Every scene is also pre-rendered in the background at startup.

Rendering gallery

  1. The shell reads projects.json to build the gallery.
  2. A project page frames /static/rendering/<slug>/<entry>.html in an iframe. The files are plain WebGL2 pages that share one copy of the helper scripts.
  3. Only /static/rendering/* may be framed, and only by this site. Every other response is sent with X-Frame-Options: DENY.

Photography and admin

  1. The gallery is rendered by the shell from metadata in the media volume.
  2. /admin/* sits behind a single bcrypt-hashed login (Spring Security). The gateway rate-limits login attempts and allows uploads up to 1 GB.
  3. The shell writes each upload as .part, validates the header, then renames it to a random hex id with a JSON metadata file. It is the only writer of the volume.
  4. The gateway mounts the volume read-only and serves /media/* for hex-named media files only. Metadata and in-progress files return 404, and files are cached as immutable because a new upload always gets a new name.

Modules

ModuleStackPublic routesProtections
gatewayCaddyeverythingTLS, per-route rate limits, body size caps, security headers
shellJava, Spring Boot, Thymeleaf/, /admin/*admin login, 512 MB memory limit
ingestRustPOST /api/prnu/analyzesandboxed decode, pixel and size limits, fuzz-tested, read-only fs
prnuPython, FastAPI/api/prnu/jobs/*no image decoders, process per job, tmpfs scratch, read-only fs
rendererGo, CGET /api/renderer/*fixed scene list, rlimits, timeout, read-only fs
rendering, gameWebGL2, C++/WASM/static/rendering/*, /static/game/*static files only, read-only mounts