Skip to content

Sites: serve directly from FastAPI β€” no per-wolt servers, no ports - #383

Open
woltspace-jerpint[bot] wants to merge 2 commits into
mainfrom
uxw/sites-direct-serve
Open

Sites: serve directly from FastAPI β€” no per-wolt servers, no ports#383
woltspace-jerpint[bot] wants to merge 2 commits into
mainfrom
uxw/sites-direct-serve

Conversation

@woltspace-jerpint

Copy link
Copy Markdown
Contributor

What

Deletes the per-wolt site server processes and all port machinery. /wolt/{name}/site/* now serves straight from disk. Net βˆ’425 lines.

Why

The per-wolt livereload subprocesses were vestigial: the server already proxied every request, stripped livereload's own reload script, and injected its own β€” wired to a watchfiles WebSocket the server serves itself. Each subprocess was a static file server behind a proxy, dragging along a port (6001–6999), a site_port in wolt.json, a .state/site.json liveness file, and a bind-gap that needed the "waking up" fallback page.

How

  • serve_wolt_site: direct disk serving. HTML gets the reload script injected at serve time; everything else via FileResponse (ETag + Range β€” video on sites works now, the buffering proxy broke it). Livereload WS unchanged.
  • Path traversal guard (new responsibility β€” the subprocess used to clamp paths): resolve() + relative_to before any read, same idiom as _serve_static. Tested against literal ../, encoded, deep, and symlink escapes.
  • sites.py gutted to site_dir() + ensure_site() (lazy starter-site scaffold, same semantics as before).
  • Directory URLs 308-redirect to trailing slash (parity with the old tornado server β€” relative links inside subdir pages).
  • Gone: port allocation, site_port at creation (existing keys inert, zero migration), POST /sites/{name}/start|stop, the waking-up page, the livereload dependency, orphaned create-wakeup.html, the perma-flaky PID-liveness test.

Review

Built by uxwolt (fable), then adversarially reviewed by an independent fable agent, which found: a blocker (create-creature-wolt still imported the deleted start_site β€” my greps missed it, no .py extension), the missing directory redirect, missing asset no-cache, HEAD 405, and stale ARCHITECTURE.md lines (one told contributors to import the deleted function). All fixed in the second commit. The reviewer explicitly cleared: traversal guard (8 attack shapes), iframe headers (nothing adds X-Frame-Options), no downstream readers of removed fields, no circular imports, non-rodent scaffold-on-GET is pre-existing behavior.

Tests

461 passed β€” test_wolt_sites.py rewritten to exercise the real ASGI route via TestClient (serving, injection, scaffold-on-request, traversal/symlink attacks, redirect, caching, HEAD). The one remaining failure (test_sessions_all_dead) is the pre-existing environment failure, unrelated; its perma-flaky sibling test_stale_state_cleaned is deleted along with the code it tested.

Behavior notes

  • First paint of a new wolt's site is now instant (no subprocess bind gap).
  • /sites lists wolts with site dirs (no more running/port fields β€” nothing consumed them).
  • New-wolt flow unchanged: scaffold at creation β†’ starter site in viewport β†’ wolt redesigns β†’ livereload.

πŸ€– Generated with Claude Code

UXwolt and others added 2 commits August 4, 2026 01:23
The per-wolt livereload subprocesses were vestigial: the server already
proxied every request, stripped livereload's own reload script, and
injected its own β€” wired to a watchfiles WebSocket the server serves
itself. The subprocess was a static file server behind a proxy, plus a
port, a state file, a liveness check, and a bind-gap needing a "waking
up" fallback page.

Now /wolt/{name}/site/* serves files straight from disk: HTML gets the
reload script injected at serve time, everything else goes through
FileResponse (ETag + Range β€” video on sites works now). The livereload
WebSocket is unchanged. A resolve + relative_to guard keeps requests
inside the site dir (same idiom as _serve_static), with tests that
attack it β€” traversal, encoding, symlink escape.

Gone: port allocation (6001-6999), site_port in wolt.json (existing
keys inert, no migration), .state/site.json, POST /sites/{name}/start
and /stop, the waking-up page, the livereload dependency, the orphaned
create-wakeup.html, and the perma-flaky PID-liveness test.

Sites scaffold at wolt creation as before; ensure_site() re-scaffolds
lazily on first request if missing. First paint is now instant β€” no
subprocess bind gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Independent review findings on the direct-serve refactor:

- create-creature-wolt still imported the deleted start_site β€” the
  whole CLI died on import. Now uses ensure_site.
- Directory URLs without a trailing slash 308-redirect to the
  canonical form so relative links inside index.html resolve (the old
  tornado static server did this; direct serving lost it).
- Assets get the same no-cache header as HTML so livereload's refetch
  can't be served a stale copy.
- HEAD requests supported (route was GET-only, old server answered HEAD).
- Unknown-wolt 404 self-refreshes β€” the viewport can load the site URL
  before the wolt dir exists on first boot.
- wolt name check tightened to re.fullmatch.
- ARCHITECTURE.md + migration doc updated (ports, site.json, start_site
  references were stale β€” one actively told contributors to import the
  deleted function).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
woltspace Building Building Preview Aug 4, 2026 1:34am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants