Skip to content

0015: How DevKit runs on Windows

Status: accepted; in progress

DevKit’s core was built on macOS mechanisms: a unix socket for its API, launchd for ports 80 and 443, /etc/resolver for .test, php-fpm pools on unix sockets, and process groups and signals for supervision. Windows has none of these, and the app should share one core rather than fork it.

Keep one devkit-core and put each platform difference behind a small function or cfg block:

Concern macOS Windows
API transport unix socket, 0600 local-only named pipe \\.\pipe\devkitd-<hash of home> (api::serve, client::pipe_name)
Ports 80 and 443 launchd socket handoff devkitd binds 127.0.0.1:80/443 itself; Windows doesn’t reserve low ports
.test names /etc/resolver/test → DNS on port 5354 one NRPT rule for .test → DNS on 127.0.0.1:53 (NRPT can’t name a port)
CA trust login keychain current user’s Root store (certutil -user -addstore Root)
PHP php-fpm pool per version on a unix socket a small pool of php-cgi.exe -b 127.0.0.1:<port> workers per version, recycled with PHP_FCGI_MAX_REQUESTS; PHP from windows.php.net
Python, dev servers unix socket or TCP TCP on loopback
Supervision process groups and signals CREATE_NEW_PROCESS_GROUP and taskkill /T
Terminal shims symlinks in bin/ .cmd files in bin/
Redis-compatible service Valkey Garnet
Starting at login launchd job Tauri autostart (per user, no service)

Done: the whole workspace builds and passes clippy for Windows, CI checks it on every pull request, the API works over the named pipe, supervision uses Windows process groups, and secrets come from the OS random source on every platform.

Done, tested on every platform and run end to end on Windows. On every pull request, CI’s Windows runner runs the whole test suite and crates/devkitd/tests/windows_e2e.rs. That test installs the NRPT rule, starts devkitd on 127.0.0.1:80, :443 and :53, resolves a site through Windows’ own resolver, serves it over HTTPS, serves PHP downloaded from windows.php.net through the php-cgi workers, runs the php.cmd shim and a site process through cmd, and removes the rule again:

  • setup and removal: one elevated PowerShell script (written to DevKit’s generated folder, run through Start-Process -Verb RunAs with the execution policy bypassed for that file only) adds the NRPT rule for .<tld> with the comment DevKit, and removal deletes rules with that comment. The TLD must be a plain DNS label before it reaches the script. The CA goes into the current user’s Root store with certutil -user -addstore Root, as the person, and comes out with certutil -user -delstore Root "DevKit Local CA". resolver_installed reads the NRPT rules from the registry (reg query), and ca_trusted looks for the CA’s SHA-1 thumbprint with certutil -user -store Root.
  • DNS on port 53: config::defaults::DNS_PORT is 53 on Windows (5354 on macOS), and check_ports names the program holding it.
  • the port-holder check: setup::listener reads netstat -ano -p TCP|UDP and names the process with tasklist /SVC (with a svchost.exe’s services, and pid 4 as the Windows HTTP service); services::port_holder uses it too.
  • PHP downloads from windows.php.net: the non-thread-safe x64 zip per version from releases.json, checked against the SHA-256 listed there, with extension_dir and the bundled extension DLLs in php.ini.
  • the php-cgi worker pool: PHP_CGI_WORKERS (4) workers per version and per Xdebug pool, each php-cgi.exe -b 127.0.0.1:<port> under the supervisor with PHP_FCGI_MAX_REQUESTS (500), handed out round-robin and restarted when they exit. A request that meets a worker as it exits goes to the next one.
  • Xdebug as xdebug.org’s DLL for the PHP version, checked against the SHA-256 on its download page.
  • downloads: Node’s Windows zips from nodejs.org, Garnet in place of Valkey, and Windows builds of Mailpit, PostgreSQL (TCP only), MySQL, Meilisearch, RustFS and cloudflared, each checked against its published SHA-256 where there is one.
  • the terminal: php.cmd, composer.cmd and devkit.cmd in bin/, and that folder at the front of the user Path (HKCU\Environment), read and written unexpanded so other entries’ %VARIABLES% survive.
  • the NSIS installer in the release workflow, signed as the Windows update bundle and listed in latest.json.
  • Windows wording and chrome: Settings › Terminal describes the user Path, the tray says Exit DevKit, and the power rail has no gap for traffic lights.
  • just check-windows (part of just lint) runs clippy for Windows on a Mac, so Windows-only code is type-checked before CI.

Still to do before a Windows release:

  • trusting the CA from the app on a Windows PC: certutil -user -addstore Root shows a confirmation that only a person can answer, so CI can’t run it (the end-to-end test trusts the CA in its own HTTPS client instead)
  • installing the NSIS build on a Windows PC. The website offers it (/download/windows) since 0.1.1, unsigned, with the SmartScreen step explained.