0015: How DevKit runs on Windows
Status: accepted; in progress
Context
Section titled “Context”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.
Decision
Section titled “Decision”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) |
Consequences
Section titled “Consequences”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
generatedfolder, run throughStart-Process -Verb RunAswith the execution policy bypassed for that file only) adds the NRPT rule for.<tld>with the commentDevKit, 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 withcertutil -user -addstore Root, as the person, and comes out withcertutil -user -delstore Root "DevKit Local CA".resolver_installedreads the NRPT rules from the registry (reg query), andca_trustedlooks for the CA’s SHA-1 thumbprint withcertutil -user -store Root. - DNS on port 53:
config::defaults::DNS_PORTis 53 on Windows (5354 on macOS), andcheck_portsnames the program holding it. - the port-holder check:
setup::listenerreadsnetstat -ano -p TCP|UDPand names the process withtasklist /SVC(with asvchost.exe’s services, and pid 4 as the Windows HTTP service);services::port_holderuses 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, withextension_dirand the bundled extension DLLs inphp.ini. - the php-cgi worker pool:
PHP_CGI_WORKERS(4) workers per version and per Xdebug pool, eachphp-cgi.exe -b 127.0.0.1:<port>under the supervisor withPHP_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.cmdanddevkit.cmdinbin/, and that folder at the front of the userPath(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 ofjust 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 Rootshows 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.