[wasm-posix-kernel] WordPress Playground Web under wasm-posix-kernel - Proof of concept#3633
Closed
mho22 wants to merge 8 commits into
Conversation
Build the SAB-backed VFS image consumed by the kernel worker: binaries, /etc files, nginx + php-fpm configs, fpm-router, wp-config.php, wasm-optimizations mu-plugin, dinit service tree, streaming zip extraction. Co-locates host-bridge re-exports from the wasm-posix-kernel submodule and the playground-defines.php mu-plugin that backs KernelLimitedPHPApi.defineConstant.
Download WordPress + SQLite zips through the playground CORS proxy. Broadens the proxy allowlist to wordpress.org, downloads.w.org and the GitHub release host so zip fetches succeed under the iframe's cross-origin isolation regime.
Construct BrowserKernel + HttpBridgeHost from the prebuilt VFS image, inject the Host header on every bridge request (nginx returns 400 without one), poll waitForNginx until any HTTP status comes back (dinit returns before its children bind their sockets), and bump nextPid past the kernel-reserved range to avoid an EEXIST on the first host-side spawn.
KernelSpawnAdapter wraps kernel.spawn(coreutils|php, ...) behind a natural FS facade, serializing spawns through inFlight because BrowserKernel's onStdout/onStderr are constructor-time singletons with no per-pid routing. KernelLimitedPHPApi mirrors the CLI's shape (mkdir/writeFile/run/request/defineConstant + cookie jar), backed by the spawn adapter and the bridge sendRequest.
KernelPlaygroundWorkerEndpoint orchestrates boot (prepareWordPressZips → buildVfsImage → bootKernelWordPress → ensureWordPressInstalled), forwards every iframe HTTP request to the in-kernel nginx via requestStreamed (scope strip → origin- form → Host + x-playground-absolute-url injection → COEP/COOP/ CORP injection), and exposes LimitedPHPApi over Comlink via the late-binding stubs pattern so the iframe sees methods the moment KernelLimitedPHPApi is ready.
bootPlaygroundRemote registers the existing service worker, spawns the Comlink worker endpoint and exposes playgroundApi with progress/nav/goTo and URL helpers — mirroring the classic remote's iframe-side surface. remote-posix-kernel.html is the sibling entry the vite middleware aliases /remote.html to.
vite.posix-kernel.config.ts (remote) aliases the kernel binary URLs, rewrites /remote.html to /remote-posix-kernel.html, and serves the iframe with COEP require-corp / COOP same-origin / DIP / Service-Worker-Allowed so SharedArrayBuffer is available to the worker. The website wrapper config overlays COEP credentialless + COOP same-origin on the parent document so the iframe can be cross-origin isolated. Wired through nx targets and a `dev:experimental-posix-kernel` script — additive only, the classic `npm run dev` path is untouched.
- Switch submodule re-exports in `host-bridge.ts` / `vfs-builder.ts` to a `@wasm-posix-kernel/*` alias so TS doesn't descend into the submodule source (strict tsconfig vs. submodule's looser config). - Vite alias in `vite.posix-kernel.config.ts` resolves the same specifiers at runtime against `WASM_POSIX_KERNEL_DIR` or the bundled submodule. - New `wasm-posix-kernel.d.ts` declares opaque `any` shims for the alias plus Vite asset-suffix wildcards (`?url`, `?raw`, `?worker&url`) and the growable `SharedArrayBuffer` 2-arg form. - Drop now-unneeded `@ts-ignore` / `eslint-disable` comments at the kernel-mode import sites. - Replace the `Function` cast in `boot-playground-remote.ts` with an `as any` indexer; the dynamically-bound Comlink methods aren't in `keyof KernelPlaygroundWorkerEndpoint`. - Annotate `BrowserKernelOptions` callback params locally in `boot.ts` since the alias surface is `any`.
Collaborator
Author
|
Closing if favor of #3635 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an experimental
dev:experimental-posix-kernelmode to thePlayground website that boots WordPress in an iframe under
nginx + PHP-FPM running on
wasm-posix-kernel,
instead of the existing PHP.wasm Asyncify/JSPI runtime.
This PR is stacked on top of #3604 (the CLI-side proof of
concept). The first 15 commits in this PR belong to that PR; the
last 7 are the new web work — see "Commits" below. Like #3604,
this is for visibility / posterity, not ready to merge: it
depends on the same five upstream-pending kernel fixes tracked
in mho22/wasm-posix-kernel#50.
The constraint while implementing was additive only — every
change is a new file under
packages/playground/remote/orpackages/playground/website/, plus three minimal sibling edits(
package.jsonscript, twoproject.jsontargets, one new viteconfig per package). The classic
npm run devpath is untouchedand continues to boot Playground exactly as before.
What this adds
A new
npm run dev:experimental-posix-kernelscript that:http://127.0.0.1:5400/website-server/with COEP
credentialless+ COOPsame-originoverlaid onthe parent document, so an embedded iframe can be
cross-origin isolated and use SharedArrayBuffer.
:4400with COEPrequire-corp,COOP
same-origin, DIPisolate-and-require-corp,Service-Worker-Allowed: /, and aliases/remote.htmlto asibling
/remote-posix-kernel.htmlentry.existing CORS proxy (allowlist broadened to
wordpress.org / downloads.w.org / GitHub),
wasm-optimizations mu-plugin, dinit service tree, streaming
zip extraction),
BrowserKernel+HttpBridgeHost, pollswaitForNginxuntil any HTTP status comes back,in-kernel server,
LimitedPHPApivia aKernelLimitedPHPApishim soexisting v1 blueprint steps work against the
kernel-resident WordPress.
requestStreamed(scope strip → origin-form → Host +x-playground-absolute-urlinjection → COEP/COOP/CORP headerinjection on the response).
Demo:
Commits (web port)
Only the last 7 commits are new in this PR — the first 15 are
inherited from #3604:
993af91Experimental posix-kernel (web): nginx + PHP-FPM VFS imagebd5e063Experimental posix-kernel (web): WordPress preparation869845eExperimental posix-kernel (web): kernel boot orchestrator57edf38Experimental posix-kernel (web): KernelLimitedPHPApi shimad93171Experimental posix-kernel (web): Comlink worker endpointf2b71deExperimental posix-kernel (web): iframe boot + entry HTML5abc1c4Experimental posix-kernel (web): Vite configs + nx targetWhy this is experimental
unupstreamed kernel-side fixes (PHP-FPM stack-size, kernel
wakeBlockedPollsnapshot, host chown error swallowing,node-kernel-worker-entry bundle, PHP wasm size optimization)
tracked in mho22/wasm-posix-kernel#50.
DO NOT MERGE - CI artifact branchin
wasm-posix-kernelbecause release-tag artifacts aren'tfetched by the in-tree workflow yet. The submodule needs its
own upstreaming before this PR is mergeable.
loginblueprint step relies on a cookie-setting mu-plugin inpackages/playground/wordpress/src/index.ts. Kernel mode hasKernelLimitedPHPApi.defineConstant+ aplayground-defines.phpmu-plugin to injectPLAYGROUND_AUTO_LOGIN_AS_USER, but the cookie-setting halfisn't wired. A defensive
goTounwrap inboot-playground-remote.tsshort-circuits the V1redirection-handler URL in the meantime; once auto-login lands
the unwrap must be removed.
the HTTP cache (keyed to
downloads.w.org/ GitHub, outsideour origin, so site-data clears don't touch it). DevTools →
"Disable cache" boots cleanly. Incognito is the workaround for
now.
new vite config covers dev only; the website's existing build
pipeline is untouched.
Notable design choices
on the parent. SAB requires COI; for an embedded iframe to
be COI'd both the iframe response and the parent document
must opt in. The parent uses COEP
credentiallesssocross-origin sub-resources (analytics, Octokit) keep loading
without CORP. WordPress front-end responses don't ship COEP
themselves (only
wp_set_up_cross_origin_isolationonwp-admin pages), so
requestStreamedinjects COEP/COOP/CORPon every bridge response before constructing the
PHPResponse.x-playground-absolute-urlon every bridge request.wp-config reads the header and uses it as
WP_HOME/WP_SITEURL— avoids a boot-time substitution pass. nginxdoesn't auto-forward arbitrary headers to fastcgi, so both
locationblocks in the in-VFS nginx config explicitly passit through.
requestStreamedstripsthe scope and reduces to origin-form before posting to the
bridge. Absolute-URI form hangs nginx — RFC 7230 §5.3.2 allows
it on origin servers but the kernel-resident nginx doesn't.
/cors-proxy.php?instead of/cors-proxy/?.offline-mode-cache.ts:shouldCacheUrlreturns true forcors-proxy URLs (same-origin in dev), routing them through
cacheFirstFetchwhich truncates around 19 MiB on HTTPorigins. Pathnames ending in
.phpshort-circuit the cache,so the
.php?form bypasses it; the vite cors-proxy prefixstill forwards because the rewrite no-ops for
.php?paths.LimitedPHPApiover Comlink. The workerendpoint installs throwing stubs for every name in
LIMITED_PHP_API_METHODSat construction;doBootconstructsa
KernelLimitedPHPApiand rebinds those names viabindApiMethods. Comlink resolves method paths atmessage-receive time, so post-boot rebinding is visible to the
iframe immediately.
waitForNginxpolls because dinit returns before servicesbind.
kernel.boot()resolves as soon as the kernel itselfis ready, not when dinit's child services have bound their
sockets. The boot orchestrator polls
GET /until any HTTPstatus comes back (404 counts — nginx is up).
BrowserKernel'sonStdout/onStderrare constructor-timesingletons (no per-pid routing). The boot installs a
single-slot
activeCapturehandler that the spawn adapterswaps in before each
kernel.spawnand clears infinally;the adapter's
inFlightpromise serializes spawns so theglobal slot is unambiguous.
Open follow-ups (deliberately out of scope)
@wp-playground/wordpress, then remove thegoTounwrap inboot-playground-remote.ts)."incognito-only for dev" or apply
cache: 'no-store'onfetchZipBytes.config covers dev only. Production paths need a separate pass
once the kernel artifacts are npm-installable.
wasm-posix-kernelsubmodule with annpm-installable package — same blocker as [wasm-posix-kernel] WordPress Playground CLI under wasm-posix-kernel - Proof of concept #3604.
Test plan
The 25 existing
playground-remoteunit tests continue to pass:Manual boot in incognito Chrome:
The iframe should render the WordPress front page end-to-end.
🤖 Generated with Claude Code