Skip to content

[wasm-posix-kernel] WordPress Playground CLI under wasm-posix-kernel - Proof of concept#3634

Draft
mho22 wants to merge 20 commits into
trunkfrom
playground-cli-experimental-posix-kernel
Draft

[wasm-posix-kernel] WordPress Playground CLI under wasm-posix-kernel - Proof of concept#3634
mho22 wants to merge 20 commits into
trunkfrom
playground-cli-experimental-posix-kernel

Conversation

@mho22
Copy link
Copy Markdown
Collaborator

@mho22 mho22 commented May 14, 2026

Summary

Adds an experimental --experimental-posix-kernel flag to the Playground CLI that boots WordPress under nginx + PHP-FPM running on wasm-posix-kernel, instead of the existing PHP.wasm Asyncify/JSPI runtime.

This PR is for visibility / posterity. It is not ready to merge — it depends on five kernel-side fixes that have not yet been upstreamed.

What this adds

A new --experimental-posix-kernel CLI flag that:

  1. Loads the wasm-posix-kernel host bridge from a user-provided WASM_POSIX_KERNEL_DIR checkout.
  2. Boots nginx + PHP-FPM inside the kernel, with a FastCGI router that serves static files and routes WordPress requests through index.php (no PCRE in the nginx build).
  3. Auto-prepares WordPress (downloads WP + SQLite Database Integration when no --mount is given) and runs the install over HTTP POST against the in-kernel server.
  4. Implements a KernelLimitedPHPApi shim so existing v1 blueprint steps (runPHP, installPlugin, writeFile, setSiteOptions, login, etc.) work against the kernel-resident WordPress.
  5. Clears stale playground_auto_login_already_happened cookies on the first request via a filesystem marker (avoids a 302 loop after --login runs).

Demo:

WASM_POSIX_KERNEL_DIR=/path/to/wasm-posix-kernel \
  npx nx dev playground-cli server \
    --experimental-posix-kernel --wp=latest --login

Commits

  1. b1f102a Experimental posix-kernel: nginx + PHP-FPM boot pipeline
  2. 9b7c2e6 Experimental posix-kernel: KernelLimitedPHPApi shim
  3. 7e8665c Experimental posix-kernel: WordPress preparation
  4. 1834dd4 Experimental posix-kernel: handler + --experimental-posix-kernel CLI flag
  5. 628bdfa Experimental posix-kernel: kernel-mode test suite
  6. d8afabe posix-kernel: clear stale auto-login cookie via first-request marker
  7. aa56dbd posix-kernel: require WASM_POSIX_KERNEL_DIR env var
  8. 38fb9cf posix-kernel: quiet nginx error_log + disable access_log

Why this is experimental

  • Depends on five fixes in wasm-posix-kernel that are not yet upstreamed (see mho22/wasm-posix-kernel#50):
    • PHP-FPM stack-size fix (fcgi_read_request buf[65543] overflows BSS into musl's vmlock, causing the install POST to deadlock on __vm_wait).
    • Kernel wakeBlockedPoll snapshot fix (live Map iteration livelocked the worker under nginx + PHP-FPM I/O patterns).
    • Host chown EPERM/EINVAL/ENOTSUP swallowing (kernel runs every process as uid 0; unprivileged host FS can't honor chowns).
    • Host node-kernel-worker-entry bundle fix (worker entry wasn't emitted by tsup, so consumers couldn't require.resolve it from the published dist/).
    • PHP wasm size optimization (-Oz + DWARF strip; ~28% smaller).
  • Tests require a built wasm-posix-kernel checkout with prebuilt PHP / nginx wasm artifacts. Those binaries take 30-60 minutes to build and need Docker + Emscripten. Until kernel-side publishes release artifacts, these tests can only run on developer machines that have the sibling checkout — they will not run in this repo's CI as-is.
  • Production CLI bundle does not yet copy-files the runtime resources (configs/nginx.conf, configs/php-fpm.conf, router.php, wp-templates/). The dev path resolves them directly from src/. See "Open follow-ups".

Open follow-ups (deliberately out of scope)

  1. Bundle copy-files for runtime resources. The production vite build needs to ship posix-kernel/configs/, router.php, and wp-templates/ into dist/.
  2. --site-url support under kernel mode. The kernel handler currently ignores args['site-url']; wp-templates/wp-config.php derives WP_HOME / WP_SITEURL from the request Host header.
  3. Intl extension. Surface intl.so + icudt74l.dat into the kernel filesystem and wire extension=intl.
  4. Replace the env var with an npm-installable kernel package. Once wasm-posix-kernel publishes prebuilt artifacts + host bundle to npm, host-bridge.ts can drop the WASM_POSIX_KERNEL_DIR indirection and switch to a regular package import.

Test plan

The 16 kernel-mode tests in packages/playground/cli/tests/posix-kernel/*.spec.ts pass locally against a built wasm-posix-kernel checkout (HEAD of mho22:playground-cli-experimental-posix-kernel):

WASM_POSIX_KERNEL_DIR=/path/to/wasm-posix-kernel \
  npx nx test-playground-cli playground-cli \
    --testFile=tests/posix-kernel/auto-prepare.spec.ts

They will fail in stock CI without a kernel checkout — expected at this stage. See "Why this is experimental".

🤖 Generated with Claude Code

mho22 and others added 15 commits May 12, 2026 16:41
Adds the standalone boot pipeline for an experimental
--experimental-posix-kernel mode. nginx + PHP-FPM run inside a
sibling wasm-posix-kernel checkout (located via the
WASM_POSIX_KERNEL_DIR env var); the host bridge dynamic-imports
the kernel's host/dist bundle so we don't depend on it as an npm
package. boot.ts reserves a numeric port (kernel-resident nginx
can't take :0), spawns php-fpm and nginx, multiplexes their
stdout/stderr, and exposes a KernelRuntime that lets later layers
spawn additional php.wasm CLIs against the same host. The
single-FastCGI-entry router.php is loaded in-place by nginx and
covers the static / PHP-on-disk / WordPress-fallback split, since
the kernel's nginx is built without PCRE.

No CLI wiring yet — slice 1 is just the boot primitives.
Adds the LimitedPHPApi-shaped surface that blueprint-v1 step
implementations expect, against the kernel-resident WordPress
install. Filesystem methods read/write the host filesystem
directly (kernel uses host fs through wasm-posix-kernel's
NodePlatformIO, so the bytes are the same). request() goes to
nginx via fetch with an in-memory cookie jar; run({ code }) /
cli({ argv }) spawn fresh php.wasm CLIs against the same kernel
host through KernelRuntime.spawnCapturing().

defineConstant() persists a JSON store and regenerates a
playground-defines.php mu-plugin so the constants survive
across requests (nginx + FPM are stateless w.r.t. the
playground process). The mu-plugin template lives in
wp-templates/ alongside the other PHP fixtures.

Path translation: the API surface keeps documentRoot as the
host's wordPressRoot (so blueprint step PHP code that does
require_once "{docRoot}/wp-load.php" resolves correctly under
both classic-VFS and kernel modes), and a tightened
VFS_DOCROOT_IN_CODE regex with a (?<![\\w/-]) lookbehind avoids
re-rewriting the trailing /wordpress directory when it appears
inside a host path.
Materializes a self-contained WordPress document root for the
kernel mode. Reuses Playground's existing WP release resolver,
cached download helper, SQLite Database Integration fetch, and
the @php-wasm/stream-compression zip decoder; idempotent each
step skips work that's already on disk.

ensureWordPressInstalled() drives wp-admin/install.php?step=2
over fetch, since a standalone php.wasm bootstrapping wp-load
hangs on the SQLite drop-in's per-request connection setup —
posting through the working FPM pipeline reuses the
nginx + php-fpm stack we already booted.

Three PHP fixtures land in wp-templates/:

- wp-config.php — minimal Playground-flavored config; WP_DEBUG /
  WP_DEBUG_LOG / WP_DEBUG_DISPLAY are guarded with !defined()
  so the playground-defines mu-plugin (set via --define-bool
  flags from the CLI) wins. Without the guard, redefinition
  warnings prepend HTML to JSON test responses.
- disable-wp-mail.php — no-op wp_mail() so wp_install()'s
  wp_new_blog_notification doesn't take the
  PHPMailer→popen→sendmail path the wasm-posix-kernel can't
  resolve. Mu-plugins load before pluggable.php's
  function_exists('wp_mail') guard, so the no-op wins.
- auto-login.php — trimmed copy of the 1-auto-login.php
  mu-plugin generated in @wp-playground/wordpress's boot
  helpers; signs in as PLAYGROUND_AUTO_LOGIN_AS_USER on first
  request.
…flag

Wires the kernel-mode boot pipeline into the CLI. PosixKernelHandler
mirrors BlueprintsV1Handler / BlueprintsV2Handler — bootWordPress()
resolves a port (reserveFreePort if --port is in use), prepares
WordPress when no /wordpress mount is provided, brings up the
kernel-resident nginx + php-fpm via bootPosixKernelWordPress, then
drives the WordPress installer over HTTP. runBlueprint() applies
--define / --define-bool / --define-number constants and runs the
compiled blueprint v1 steps through KernelLimitedPHPApi.

run-cli.ts gets a hidden --experimental-posix-kernel boolean and a
new runCLI overload that returns PosixKernelRunCliServer (just
serverUrl + LimitedPHPApi + dispose; no Express server, no
PHPWorker pool). The --experimental-posix-kernel branch refuses
xdebug / redis / memcached and currently only supports the server
command. start-server.ts gains a reserveFreePort() helper since
kernel-resident nginx needs an explicit numeric port.
Five spec files exercising --experimental-posix-kernel:

- boot.spec.ts — minimal smoke test against a static index.php
  document root, mounted via --mount=<dir>:/wordpress.
- auto-prepare.spec.ts — no --mount: WordPress + SQLite drop-in
  download, install drives end-to-end, GET / returns 200 with
  WordPress markup.
- blueprint-v1.spec.ts — runPHP, writeFile, mkdir, login steps
  against the kernel-resident WordPress; the writeFile output is
  fetched back through nginx as a regression test for path
  translation.
- php-api.spec.ts — drives KernelLimitedPHPApi.run() directly
  (sequential and parallel) to regression-cover the time-multiplexed
  stdout capture in boot.ts (the kernel currently emits every
  stdout chunk with pid: 0, so a misordered capture would corrupt
  blueprint runPHP output).
- run-cli.spec.ts — curated kernel-mode subset of
  tests/run-cli.spec.ts: --define matrix (string/bool/number),
  --wp version selection, default site URL, blueprint with git
  resources, internal cookie store persistence, port-in-use
  fallback, and the auto-login cookie-clear scenario.

Tests bind in serial — the kernel host boots one nginx + php-fpm
per test file, so running the whole tests/posix-kernel/ directory
in parallel is flaky; per-file invocations are reliable.
Mirrors the classic CLI's Express middleware that clears a stale
playground_auto_login_already_happened cookie on the first real
request after boot. The classic path runs in Node; under
--experimental-posix-kernel the front door is the kernel-resident
nginx, so the equivalent has to live in router.php.

boot.ts threads a per-tempDir marker path
(<tempDir>/first-request-pending) into nginx as
fastcgi_param PLAYGROUND_FIRST_REQUEST_MARKER and exposes
resetFirstRequestMarker() on the boot result. The handler arms
the marker post-install (creating it earlier would short-circuit
the install probe in ensureWordPressInstalled). router.php
@Unlink's the marker on the first request that observes it (FPM
workers race; the unlink is atomic, only one wins) and emits a
302 + Set-Cookie clearing the cookie ONLY when the request
actually carries the cookie — an unconditional 302 to
$_SERVER['REQUEST_URI'] would trip undici's redirect-cycle
detection and break tests that issue plain fetches.

Unblocks the auto-login describe in run-cli.spec.ts.
Drop the hardcoded developer-machine default for the kernel checkout
location. The CLI now errors out with a clear message if the env
var is unset, so the path is no longer baked into the source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The default config flooded stdout with nginx [notice]/[info]
startup chatter and a per-request access line. Drop error_log
to 'error' and turn access_log off so the dev console only shows
the kernel-mode banner, the FPM ready notice, and the
'WordPress is ready at ...' line. PHP-FPM's NOTICE banner is
still shown (useful, low volume). Tests don't depend on log
output (boot uses TCP loopback probing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 16 kernel-mode specs under
packages/playground/cli/tests/posix-kernel/ require a built
wasm-posix-kernel checkout (host/dist/index.js + 4 wasm binaries)
that the CLI's host-bridge.ts loads at runtime via
WASM_POSIX_KERNEL_DIR. Building it from source needs Docker +
Emscripten and takes 30-60 minutes — too heavy to run on every CI
job.

Wire the kernel as a git submodule pinned to an artifacts branch on
mho22/wasm-posix-kernel that ships pre-built host/dist/ + the four
wasms (kernel.wasm, nginx.wasm, php.wasm, php-fpm.wasm). The
existing CI workflow already runs actions/checkout@v4 with
'submodules: true', so no workflow change is needed.

Adds tests/posix-kernel/setup.ts as a vitest setupFiles entry. It
sets process.env.WASM_POSIX_KERNEL_DIR to the submodule path on
disk if (and only if) the env var is not already set by the
developer. The fork-pool workers inherit the env var, and
host-bridge.ts then resolves host/dist/index.js + the wasm
binaries from the submodule.

The submodule URL points at mho22/wasm-posix-kernel (a personal
fork) because wasm-posix-kernel does not yet publish release
artifacts to npm or GitHub Releases. Once it does, swap the
submodule for a tarball download in postinstall.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous configs capped concurrency at one PHP request at a time
(nginx worker_processes=1, FPM pm=static with pm.max_children=1).
That kneecapped anything that fans out — Site Editor REST calls,
WP-Cron, async block fetches — by serializing every request through
a single FPM worker.

Switch nginx to worker_processes=auto and FPM to pm=dynamic with
pm.max_children=4 (start_servers=2, min_spare=1, max_spare=3). The
kernel-side prerequisite — wasm-posix-kernel commit f7b3f9eb's
shared AF_INET accept queue across fork — is already in the HEAD
this PR depends on, so multi-worker nginx is safe.

Drop the disable_functions=fsockopen + allow_url_fopen=0 hardening
that mirrored boot.ts:578-583. They were a workaround for the WP
installer's wp_install_maybe_enable_pretty_permalinks() self-loopback
deadlocking against pm.max_children=1 (the only worker was already
busy serving the install POST that triggered the loopback). With
>= 2 children the loopback completes naturally and the hardening is
no longer load-bearing.

Memory note: 4 PHP-FPM children + 2 nginx workers ≈ 6 wasm
processes vs. 2 today. Each PHP wasm is ~18 MB, so ~70-80 MB
resident at peak — fine for a dev CLI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… apply CLI constants pre-install

Address three Copilot review findings on PR #3604:

1. nginx was listening on 0.0.0.0 by default (no interface in the
   `listen` directive), which exposes the experimental dev server on
   the LAN even though the CLI advertises 127.0.0.1. The configs now
   use a `listen __HOST__:__PORT__` placeholder substituted by
   bootPosixKernelWordPress; the new `host` boot option defaults to
   `'127.0.0.1'` (kept symmetric with the existing `__PORT__`,
   `__SERVER_NAME__`, etc. placeholders so a future `--host` CLI
   flag becomes a one-line plumb-through).

2. wp-templates/wp-config.php defaulted `WP_DEBUG_DISPLAY` to true,
   while the classic CLI defaults it to false (run-cli.ts:723-724).
   The mismatch let PHP notices/warnings prepend HTML to JSON
   responses on early requests. Flipped to false to match.

3. mergeDefinedConstants(this.args) ran inside runBlueprint(), i.e.
   *after* bootWordPress()'s ensureWordPressInstalled() drove the
   WP installer. So `--define WP_DEBUG=...` and friends never made
   it into the install POST. Classic mode applies these via
   bootWordPress() before install; mirror that ordering by
   hoisting the loop into bootWordPress() before the install call.

Drive-bys from the host-placeholder change:
  - Renamed the kernel-runtime variable `host` to `kernelHost`
    inside bootPosixKernelWordPress to free up `host` for the
    network-bind concept (KernelRuntime.host → .kernelHost
    accordingly; not consumed by external callers).
  - waitForLoopback now takes the bind host explicitly. The FPM
    loopback probe still hits 127.0.0.1 (kernel-internal port),
    but the nginx probe matches the user-facing bind address.
  - Dropped the redundant URL from the "Booting WordPress…"
    pre-boot status print; run-cli.ts already emits a final
    "WordPress is ready at <serverUrl>" once boot completes.

All 16 kernel-mode specs pass (153 tests across 13 files in the
playground-cli vitest suite).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…in CI

Two fixes for CI failures on PR #3604:

1. `node:url` was missing from getExternalModules() in
   vite-external-modules.ts. host-bridge.ts imports `pathToFileURL`
   from `node:url` for the dynamic-import URL of the kernel's host
   bundle, so production rollup builds (`nx build playground-cli`,
   `package-for-self-hosting`) failed with `"pathToFileURL" is not
   exported by "__vite-browser-external"`. Adding it alongside the
   existing `node:fs`, `node:crypto`, `node:http`, `node:net`,
   `node:process` entries lets rollup keep the import as external,
   matching how the file is actually consumed (Node-only).

2. The kernel's host bundle (wasm-posix-kernel/host/dist/index.js)
   leaves `fflate` and `fzstd` as external imports. They are
   declared in wasm-posix-kernel/host/package.json's dependencies,
   but the submodule's host/ directory is not part of the playground
   workspaces, so npm ci on the runners doesn't install them. The
   kernel-mode specs failed at `import { inflateSync } from "fflate"`.
   Add a CI step that runs `npm install --omit=dev --no-package-lock
   --ignore-scripts --prefix wasm-posix-kernel/host` before the
   test-playground-cli matrix's test step, populating
   wasm-posix-kernel/host/node_modules/{fflate,fzstd} where Node's
   resolver finds them when the host bundle is dynamic-imported.
The published @wp-playground/cli bundle ships only the JS chunks Vite
emits in dist/ — the wp-templates/*.php, configs/*.conf, and router.php
source files don't end up alongside the runtime. Module-init readFileSync
calls for those resources crashed test-built-npm-packages with ENOENT
because consumers install the published tarball, not the source tree.

Switch every static resource load to a Vite `?raw` import so the file
contents are inlined as string literals at build time:

  - php-api.ts: DEFINES_MU_PLUGIN_PHP
  - prepare-wordpress.ts: WP_CONFIG_PHP, DISABLE_WP_MAIL_MU_PLUGIN_PHP,
    AUTO_LOGIN_MU_PLUGIN_PHP
  - boot.ts: ROUTER_PHP, NGINX_CONF_TEMPLATE, PHP_FPM_CONF

For the boot-time config files, write the inlined strings to tempDir at
boot — nginx and php-fpm need real paths, but those paths no longer have
to live next to the JS bundle. Drop the now-unused __dirname constant
from boot.ts and switch nginx's prefix `cwd` to tempDir.

Add `vite/client` to tsconfig.lib.json + tsconfig.spec.json so tsc
recognizes the `?raw` import suffix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shipped kernel.wasm is wasm64 and the host bundle creates a
WebAssembly.Memory descriptor with `address: "i64"`. Node 22's V8
rejects that form ("Cannot convert a BigInt value to a number"),
the worker-thread init throws, and the kernel host hangs forever
on the "ready" message — every --experimental-posix-kernel test
times out. Node 24's V8 accepts memory64 natively.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…play_errors

Two polish fixes surfaced smoke-testing --experimental-posix-kernel
--login in a real browser:

1. router.php only handled file paths. A request like /wp-admin/
   maps to a directory on disk, is_file() returned false, and the
   request fell through to the WP front-end index.php — URL bar
   said /wp-admin/ but the page rendered the homepage. Hitting
   /wp-admin/index.php directly already worked. Added a
   DirectoryIndex branch that resolves <dir>/index.php and includes
   it so the trailing-slash form behaves the same.

2. php-fpm is launched with `-c /dev/null` (no php.ini), so PHP's
   compiled-in display_errors=On default applies under FastCGI.
   That writes E_USER_NOTICE/E_WARNING into the response body
   (e.g. wp_version_check()'s trigger_error() output prepended to
   wp-admin HTML), which then cascades into "Cannot modify header
   information" warnings. Set php_admin_flag[display_errors]=off in
   the [www] pool; errors still flow to stderr via the existing
   global error_log. (display_errors=stderr only works under CLI
   SAPI — under FastCGI it falls back to the body.)

Both affect every kernel-mode user, not just stale-cookie sessions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds an experimental --experimental-posix-kernel flag to the Playground CLI that boots WordPress under nginx + PHP-FPM running on wasm-posix-kernel instead of PHP.wasm. Marked as a proof-of-concept depending on five unmerged kernel-side fixes.

Changes:

  • New CLI flag and PosixKernelHandler that boots nginx + PHP-FPM in the kernel and serves WordPress through a single FastCGI router script
  • KernelLimitedPHPApi shim implementing the v1 blueprint API surface against the kernel-resident WordPress (host fs + HTTP + spawned php.wasm)
  • WordPress auto-prepare pipeline (download, SQLite, mu-plugins) and a kernel-mode test suite gated by WASM_POSIX_KERNEL_DIR

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
wasm-posix-kernel New git submodule pointing at the artifacts branch
.gitmodules Adds the wasm-posix-kernel submodule entry
.github/workflows/ci.yml Bumps Node to 24 and installs kernel host runtime deps
packages/vite-extensions/vite-external-modules.ts Adds node:url to externalized modules
packages/playground/cli/vite.config.ts Wires the kernel-mode test setup file
packages/playground/cli/tsconfig.{lib,spec}.json Adds vite/client types for ?raw imports
packages/playground/cli/src/run-cli.ts Adds --experimental-posix-kernel flag and dispatch path
packages/playground/cli/src/start-server.ts New reserveFreePort() helper
packages/playground/cli/src/posix-kernel/host-bridge.ts Resolves the kernel checkout from WASM_POSIX_KERNEL_DIR
packages/playground/cli/src/posix-kernel/boot.ts Boots nginx + php-fpm inside the kernel
packages/playground/cli/src/posix-kernel/php-api.ts LimitedPHPApi-shaped shim over host fs / HTTP / kernel spawn
packages/playground/cli/src/posix-kernel/posix-kernel-handler.ts Orchestrates boot, install, and blueprint v1 execution
packages/playground/cli/src/posix-kernel/prepare-wordpress.ts Downloads + lays out WordPress and SQLite integration
packages/playground/cli/src/posix-kernel/router.php Single FastCGI entry point handling static / PHP / WP routing
packages/playground/cli/src/posix-kernel/wp-templates/* wp-config, auto-login, defines, disable-wp-mail templates
packages/playground/cli/src/posix-kernel/configs/{nginx,php-fpm}.conf nginx + php-fpm configs templated at boot
packages/playground/cli/tests/posix-kernel/* Kernel-mode test suite (auto-prepare, boot, blueprint-v1, php-api, run-cli)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/playground/cli/src/posix-kernel/boot.ts
Comment thread packages/playground/cli/src/posix-kernel/router.php
Comment thread packages/playground/cli/src/posix-kernel/php-api.ts
Comment thread packages/playground/cli/src/posix-kernel/prepare-wordpress.ts
Comment thread packages/playground/cli/tests/posix-kernel/run-cli.spec.ts
Comment thread packages/playground/cli/tests/posix-kernel/run-cli.spec.ts
Comment thread packages/playground/cli/src/posix-kernel/posix-kernel-handler.ts
Comment thread packages/playground/cli/src/posix-kernel/php-api.ts
Comment thread packages/playground/cli/src/posix-kernel/configs/php-fpm.conf
@mho22 mho22 removed the request for review from bgrgicak May 14, 2026 09:18
@mho22 mho22 force-pushed the playground-cli-experimental-posix-kernel branch 2 times, most recently from c0418c4 to 905f8ba Compare May 15, 2026 13:49
… I/O

Round 3 (905f8ba) handed PHP-FPM the user's --mount via the host
path in SCRIPT_FILENAME / DOCUMENT_ROOT. On Windows that value is
`C:\…`; PHP-FPM's musl-libc checks `path[0] == '/'` to decide
"absolute", treats `C:` as relative, joins it onto cwd, and never
gets to Node `fs.openSync` — surfacing as 14 test failures on
windows-latest CI for PR #3634. Round 2's NTFS junction was strictly
closer to working but added its own breakage (1 failure: 200 with
empty body, ENOENT through the junction inside the kernel).

This change moves the Windows path translation to where it belongs
— the kernel's Node-side host bridge — and has the CLI hand the
kernel only POSIX-shaped paths. The submodule bump pairs with
e178b0f3 in wasm-posix-kernel: NodePlatformIO.rewritePath now
translates `/C/foo` -> `C:/foo` on win32 before each fs.* call.

CLI changes:

* `temp-dir.ts` (new): the booter's temp dir is a regular
  `tmp-promise` directory. The /dev/shm/ overload from 2bd8b02 is
  gone — with the host bridge translating any `/<letter>/...`, a
  `toPosixPath(nativeDir.path)` view of the same dir flows through
  the same mechanism.
* `boot.ts`: takes paired host/kernel paths
  (`wordPressRoot{Host,Kernel}Path`, `tempDir{Host,Kernel}Path`).
  Substitutes `__TEMP_DIR__`, `__WORDPRESS_ROOT__`, `__FPM_PORT__`
  in nginx.conf with kernel-shaped values. Drops the local
  `toPosixSeparators` helper.
* `php-api.ts`: `documentRoot = toPosixPath(hostRoot)` so blueprint
  v1's phpVar embedding of documentRoot produces a path PHP inside
  the kernel can resolve. cwd / DOCROOT / translateVfsPathsInCode /
  scriptPath argv all use the POSIX form. `hostRoot` stays native
  for this class's own Node fs.*.
* `posix-kernel-handler.ts`: computes
  `wordPressRootKernelPath = toPosixPath(wordPressRootHostPath)`
  and passes both to the booter. Uses createPosixKernelTempDir().
* `configs/nginx.conf`: re-applies `__TEMP_DIR__` for nginx's pid
  and *_temp_path directives (the bundled nginx wasm was built with
  `/tmp/nginx_*_temp` defaults, which on Windows has no /tmp/ on
  the host fs). `fastcgi_pass` uses the per-boot `__FPM_PORT__`.
* `configs/php-fpm.conf`: `listen = 127.0.0.1:__FPM_PORT__` so
  concurrent vitest workers don't EADDRINUSE on 9000.

History note: this branch was force-reset back to ff83a8f (drops
the broken-on-Windows round 3 plus the now-superseded /dev/shm
tempdir trick) before this commit. The earlier round-3 baseline is
not preserved in linear history.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mho22 mho22 force-pushed the playground-cli-experimental-posix-kernel branch from 905f8ba to 11a61a2 Compare May 17, 2026 19:11
…itedPHPApi.toHost

Round 5 of the Windows-CI fix sequence for `--experimental-posix-kernel`.
Addresses the two failures left over from round 4 on test-playground-cli
(windows-latest):

1. `should run blueprint including git:resources` — `installPlugin`
   builds `pluginDirectoryPath = joinPaths(await playground.documentRoot,
   'wp-content', 'plugins', 'blocky-formats')`. On Windows
   `documentRoot` is the POSIX form (`/C/Users/.../wordpress`), so the
   plugin path becomes `/C/Users/.../wordpress/wp-content/plugins/
   blocky-formats`. `KernelLimitedPHPApi.toHost()` previously only
   rewrote the `/wordpress` VFS prefix, leaving `/C/...` paths to flow
   into Node `fs.*` unchanged — which on Windows resolves relative to
   the current drive instead of the real host root. The plugin files
   ended up in the wrong directory, kernel-side `glob()` found nothing,
   and activation failed. Extend `toHost` to also recognize
   `documentRoot` / `documentRoot/...` prefixes and translate them back
   to native `hostRoot`. No-op on macOS/Linux where the two are equal.

2. `should use default site-url when not provided` — the test asked for
   `port: 9500` and asserted the response body contained
   `http://127.0.0.1:9500`. Under vitest's file parallelism on Windows
   it raced the same-named test in `tests/run-cli.spec.ts`, which also
   wants 9500. The posix-kernel handler silently falls back to a free
   port when 9500 is busy (intentional design, covered by
   `port in use > uses a free port when the requested port is already
   taken`), so the assertion saw a random port instead. Switch the test
   to `port: 0` and assert against `cliServer.serverUrl`'s actual
   origin — preserves the intent (siteurl matches the bound URL) and
   doesn't contest 9500.

macOS suite: 154/154 still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rebases the wasm-posix-kernel submodule branch
playground-cli-experimental-posix-kernel-with-artifacts onto current
origin/main (42 commits forward). The superseded "set 128 KiB
stack-size" commit was dropped — main already enforces a 4 MB stack via
-Wl,-z,stack-size=4194304.
@mho22 mho22 force-pushed the playground-cli-experimental-posix-kernel branch 2 times, most recently from 2252902 to 0c51a67 Compare May 18, 2026 09:49
…("nobody")

The submodule's PR #442 (vfs cutover) deleted the kernel's synthetic
/etc/passwd|group|hosts files. When NodeKernelHost is constructed
without rootfsImage, the new NodePlatformIO reads the host's real
/etc/passwd — on macOS that returns uid=-2 for "nobody", which
musl's getpwnam parses as invalid, so php-fpm and nginx both abort
their pool startup with "cannot get uid for user 'nobody'" /
"getpwnam('nobody') failed".

This commit cuts the playground CLI over to the rootfs-based VFS
model:

  - Bumps the wasm-posix-kernel submodule to a tip that adds an
    `extraMounts` option on NodeKernelHostOptions (and bundles the
    rebuilt rootfs.vfs image alongside the kernel binaries).
  - boot.ts now passes `rootfsImage: "default"` so the worker
    mounts host/wasm/rootfs.vfs at "/" with the canonical scratch
    layout (/tmp, /var/*, /home/user, /root, /srv). rootfs/etc/passwd
    in the image carries the standard `nobody:x:65534:65534:...`
    line that musl accepts.
  - boot.ts also passes extraMounts so the per-boot temp dir
    (nginx.conf, php-fpm.conf, router.php, fastcgi/client_body temp
    dirs, logs) and the wordPress document root remain reachable
    through the kernel VFS — without them, the longest-prefix mount
    resolver hits /var/folders/... (macOS) or /tmp/... (Linux) and
    falls through to either the scratch /tmp memfs or the rootfs
    image, both of which 404 the files nginx and FPM need.
  - host-bridge.ts widens the typed surface to mirror the upstream
    additions (rootfsImage + extraMounts).

Verified locally on Node 24 (Darwin) — all 11 posix-kernel
run-cli.spec.ts tests pass and getpwnam/getgrnam succeed inside
the kernel without the synthetic /etc fallback.
@mho22 mho22 force-pushed the playground-cli-experimental-posix-kernel branch from 0c51a67 to 16470c7 Compare May 18, 2026 12:25
The submodule's HostFileSystem.safePath rejected every mount-relative
path on Windows with EACCES because the traversal guard compared
against `this.rootPath + "/"` instead of the platform-native
separator. The kernel surfaced the throw as errno 13, which caused
both nginx and php-fpm to abort startup with "Permission denied"
when opening their config files from extra mounts (per the Windows
CI failure on 76524882511).

Bumps the submodule pointer to ac47b26e (source) /
d4e8610991fcfa4e8e96746d52a6fd71fb60a63f (artifacts) so CI picks up
the one-character fix (`"/"` -> `nodePath.sep`).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants