Skip to content

Releases: Qbix/webserver

v2.1.0 – Strange New Worlds

Choose a tag to compare

@github-actions github-actions released this 29 Sep 20:33

Qbix Server v2.1.0 — Strange New Worlds

v2.0 was a mesh-networked runtime. v2.1 makes it production-ready: every major PHP framework runs unmodified, the control panel manages apps end-to-end, mobile apps can be built and distributed for iOS and Android from the panel, and nine security audit passes harden the entire stack.

The mesh layer is still there — every v2.0 feature works unchanged. v2.1 adds the framework compat layer, a rebuilt control panel, mobile build scaffolding, and a significant number of security fixes.

Framework Compatibility

The compat source transform now handles output buffer protection, SAPI detection, and 27 shimmed PHP functions. Frameworks like Laravel, Symfony, WordPress, Drupal, CakePHP, Yii, and Mezzio run out of the box — no code changes, no plugins, no extensions.

# Drop a Laravel app in and go
php qbixserver.php --root=my-laravel-app/public --port=8080

Source Transform

The rewriter intercepts calls that would break under Qbix's execution model:

  • ob_end_flush(), ob_end_clean(), ob_get_clean(), ob_get_level() are shimmed so frameworks that drain output buffers in a loop don't infinite-loop against Qbix's protected buffer.
  • php_sapi_name() returns 'cli-server' instead of 'cli'. The PHP_SAPI constant is replaced via a context-aware constant engine that skips qualified references like SomeClass::PHP_SAPI.
  • All shimmed calls are emitted fully qualified (\Q_WebServer_Compat::_header(...)) so they resolve correctly inside namespaced framework code. The v2.0 rewriter omitted the leading backslash, which broke every Symfony, Laravel, and Drupal response.

13 Framework Presets

Built-in presets for Laravel, Symfony, WordPress, Drupal, CakePHP, CodeIgniter, Yii, Mezzio, Slim, Joomla, Magento, Nextcloud, and ownCloud. Each preset sets the front controller, upload limits, memory limits, and session settings appropriate for the framework.

12 Boot Adapters

Boot adapters automatically detect and bootstrap each framework. A Custom adapter allows user-specified boot callables.

Performance

Benchmarked against PHP's built-in development server:

Framework php -S Qbix Server Speedup
Laravel 190 req/s 653 req/s 3.4×
Drupal 404 req/s 1,636 req/s 4.0×
CakePHP 905 req/s 1,790 req/s 2.0×
Mezzio 1,580 req/s 2,337 req/s 1.5×

See BENCHMARKS.md for full methodology.

Control Panel

App Management

Create, delete, start, stop, and configure apps from the panel. Domain assignment, SSL cert provisioning, and framework detection are integrated into the app creation flow. The panel also manages WordPress and Drupal plugin/module installation through their respective CLIs.

Cache Clearing

Append ?Q.clearCache to any URL to clear all caches — opcode, static file, precompression, and image — for that app. During development this replaces the cycle of clearing APC, restarting workers, and purging the image cache separately.

Script Modules

The panel's JavaScript is organized into modules with a loader, replacing the inline scripts that grew to several thousand lines in v2.0. The module system loads only the code needed for the active tab.

Mobile Build and Distribution

The control panel can scaffold, build, and package native iOS and Android apps.

Scaffolding

Click Prepare with a platform selected and the panel generates a complete native project:

  • iOS: Swift source files + project.yml for xcodegen. PhpBridge.swift starts PHP via posix_spawn, TransportManager.swift handles BLE/MC/LAN, BackgroundKeepAlive.swift maintains the silent audio session.
  • Android: Kotlin source files + Gradle (Kotlin DSL). PhpBridge.kt extracts the binary from APK assets and runs it via ProcessBuilder, TransportManager.kt handles BLE/LAN, QbixServerService.kt runs the Foreground Service.

Both include the GATT service UUIDs, chunking protocol, and mesh handshake integration.

Building

Click Build and the panel runs xcodebuild or ./gradlew assembleRelease. CI produces qbixserver-ios-arm64 and qbixserver-android-arm64 micro binaries via static-php-cli, with Android NDK cross-compilation.

Distribution

iOS apps are archived and uploaded to App Store Connect via the Xcode Organizer or xcodebuild -exportArchive. TestFlight handles beta distribution (25 internal testers without review, 10,000 external testers with review). Ad Hoc provisioning supports direct distribution to up to 100 registered devices.

Android apps are signed with a release keystore and uploaded to the Google Play Console as an AAB. The Play Console offers internal (100 testers, no review), closed (invite-only), and open testing tracks before production. Signed APKs can also be distributed directly for sideloading or alternative stores.

See mobile/iOS.md and mobile/Android.md for full walkthroughs.

Security Hardening

Nine audit passes reviewed Panel.php (~7,000 lines), TransportManager.swift (~860 lines), and TransportManager.kt (~680 lines). Each pass found progressively fewer issues — 7, then 3, then 1 — confirming convergence.

XSS Prevention

All innerHTML assignments in the Nearby tab now escape API-returned data through escHtml(). Previously, a peer could set its mesh name to <img onerror=...> and have it rendered unsanitized in the control panel. Fixed in: mesh identity display, routing table rows, peer connection status, script selector options, attestation labels, and attestation signer fields.

Path Traversal

basename() is applied to directory names from user input in apiQbixNpm, apiFrameworkPkgDownload, and framework package endpoints. Without this, a target value of ../../etc could traverse outside the expected directory.

Shell Injection

escapeshellarg() is applied to all user-supplied paths passed to shell commands. This covers the WordPress CLI path (wp) and Drupal CLI path (drush) in six locations across apiFrameworkPackages, apiFrameworkPkgAction, and apiFrameworkRun. A crafted CLI path like /usr/bin/wp; rm -rf / would previously execute the injected command.

Authentication

apiChangePassword and apiLogout now check the Authorization: Bearer header in addition to X-Panel-Token and cookies, matching the existing checkAuth logic. API clients authenticating via Bearer header could previously not change their password or log out — the token lookup returned empty and the operation silently failed or invalidated the wrong session.

BLE SSRF

Both the iOS and Android TransportManagers now reject BLE-received HTTP request paths that don't start with /. Without this check, a crafted BLE request could use the path as a userinfo@host trick — the HTTP client would interpret user:pass@evil.com/path as a request to evil.com — turning the local server into an open proxy reachable over Bluetooth.

// iOS fix
guard parsed.path.hasPrefix("/") else {
    sendBLEResponse("HTTP/1.1 400 Bad Request\r\n..."
        .data(using: .utf8)!, to: central)
    return
}
// Android fix
if (!path.startsWith("/")) return@submit

MultipeerConnectivity Peer Identity

The iOS TransportManager now uses the peer's actual mesh_id from the ECDH handshake when relaying MultipeerConnectivity messages to PHP, instead of the MC displayName. The MC display name is an arbitrary string set by the remote device; the mesh_id is the cryptographic identity established during the handshake. Using the display name meant the PHP server couldn't match MC messages to the correct peer, breaking message routing for any peer whose display name didn't happen to match its mesh ID.

Documentation

Doc What's new
FRAMEWORKS.md All 13 frameworks with presets, adapters, benchmarks
BENCHMARKS.md Framework benchmark section
mobile/README.md Transport layer, GATT protocol, platform requirements
mobile/iOS.md Signing, TestFlight, App Store, Ad Hoc distribution
mobile/Android.md Keystore, Play Store, testing tracks, direct APK
README.md Expanded Mobile section with build/distribute overview

Upgrading from v2.0

No breaking changes. All v2.0 configuration, APIs, and mesh behavior are preserved. The framework compat layer activates only when serving a framework that needs it. The security fixes apply automatically.

v2.0.0 – Strange New Worlds

Choose a tag to compare

@github-actions github-actions released this 25 Sep 01:40

Qbix Server v2.0.0 — Strange New Worlds

v1.x was a PHP web server. v2.0 is a mesh-networked runtime. Phones discover each other over Bluetooth, establish encrypted sessions without any central server, synchronize data peer-to-peer, and run PHP applications on every node.

The web server is still there — every v1.x feature works unchanged. v2.0 adds a new capability surface on top.

Mesh Protocol

Every Qbix Server instance has a cryptographic identity: an ECDSA P-256 keypair generated on first run. The peer ID is sha256(public_key) — 64 hex characters, same security model as Ethereum. No registration, no CA, no blockchain.

When two servers discover each other (over BLE, Wi-Fi, or TCP), they perform a 4-step handshake: certificate exchange, mutual authentication via ECDSA signatures, ECDH ephemeral key agreement, and AES-256-GCM session encryption. All subsequent traffic is encrypted end-to-end.

// Any Qbix app can now talk to nearby servers
Q::handleUsingRemote('qbix-peer://' . $peerId . '/api/endpoint', $data);

// React to peers coming and going
Q_WebServer_Transport::onPeerOnline(function ($peer) {
    // $peer has: peer_id, name, transport, address
    // Sync data, exchange messages, coordinate work
});

Routing

When peers are out of direct range, intermediate nodes relay traffic. The router uses distance-vector routing (the same algorithm that ran the early internet) with HELLO/BYE/HEARTBEAT messages, TTL limits, deduplication, and route dampening. A message from A to C routes through B transparently — encrypted end-to-end so B can't read it.

Data Sync

When a peer connects, the sync protocol runs automatically:

  1. Exchange Bloom filters (1KB each) to identify what's different
  2. Transfer only the missing records
  3. Resolve conflicts (last-writer-wins by default, pluggable per table)

For large datasets (10,000+ records), the protocol switches to prolly tree comparison: a deterministic content-addressed tree where identical subtrees are skipped entirely. With 10,000 records and 100 differences, the diff takes 1.2ms and transfers ~11KB of hashes instead of the full dataset.

Mobile Platforms

Qbix Server runs on iOS and Android. PHP runtimes now exist for both platforms (NativePHP Mobile, php-ios, Phphone). The native TransportManager handles peer discovery and transport negotiation.

Transport Priority

When a peer is reachable over multiple channels, the best one is used automatically:

Priority Transport Bandwidth Range
1 TCP (LAN/Wi-Fi) 100+ Mbps Same network
2 MultipeerConnectivity 2–25 Mbps ~200ft (iOS only)
3 BLE GATT ~2 Mbps ~100ft per hop

If Wi-Fi drops, traffic falls back to BLE seamlessly. The PHP server never knows — it sees HTTP on localhost regardless of transport.

BLE Chunking

HTTP payloads are chunked for BLE's MTU constraints (23–517 bytes per write). The chunking protocol is implemented identically in PHP, Swift, and Kotlin — flag byte + 4-byte length header, tested against MTU 23 (BLE 4.0), 247 (typical), and 517 (BLE 5.0 max). Maximum message size: 64KB over GATT.

Background Persistence

  • iOS: silent AVAudioEngine with MixWithOthers keeps the process alive. App Store precedent: PocketServer, Mob framework.
  • Android: Foreground Service with persistent notification. START_STICKY for auto-restart.

New in the Control Panel

Tab 14 ("Nearby") shows:

  • This server's mesh identity (peer_id)
  • Active transports (TCP, BLE, MultipeerConnectivity)
  • Connected peers with transport type, hop count, encryption status
  • Routing table (destination, next hop, hops)
  • Encrypted sessions list
  • Connect button for manually adding a TCP peer by address

Test Summary

Suite Tests
Mesh identity + handshake 37
Transport registry + events 52
Encrypted P2P HTTP 39
Routing + multi-hop 65
Bloom filter sync 59
BLE transport simulation 63
Prolly tree sync 41
Integration (all wired) 37
Server (HTTP, panel, workers) 71
Total 464

Source Files (Mesh Layer)

File Lines Purpose
Mesh.php 718 Identity, handshake, ECDH, AES-256-GCM
Transport.php 692 Peer registry, events, API, router wiring
MeshRouter.php 537 Distance-vector routing, FORWARD relay
MeshSync.php 450 Bloom filters, conflict resolution, sync flow
MeshBLE.php 332 Chunking protocol, rate limiter, simulator
ProllyTree.php 448 Content-addressed tree, O(d·log n) diff
TransportManager.swift 844 iOS: LAN + MC + BLE, auto-fallback
TransportManager.kt 658 Android: LAN + BLE, same protocol

Upgrading from v1.x

No breaking changes. All v1.x configuration, APIs, and behavior are preserved. The mesh layer is additive — it activates when peers are discovered and does nothing when running as a standalone server.

The mesh identity keypair is generated automatically on first run and stored in the data directory. Deleting it generates a new identity (same as losing an Ethereum wallet).

What Was in v1.5

Everything from v1.5 is included: ECDSA M-of-N signing, Sigstore Rekor transparency log, SQLite auto-provisioning, PHP 8.6 Io\Poll epoll driver, Metrics/Analytics with clickstream, autohost for 15 frameworks, 4-platform CI, --pack single-binary mode, Windows COW fork.

Fixes in this build

  • Framework code in namespaces. The source rewriter emitted Q_WebServer_Compat::_header(...) without a leading backslash, so inside any namespace block PHP looked for Vendor\Namespace\Q_WebServer_Compat and the request failed with "class not found". Every Symfony, Laravel and Drupal response goes through a namespaced Response::sendHeaders(), so all three returned 500. Shims are now emitted fully qualified. Regression test: tests/test_compat_namespace.php.
  • /Q/sync/* and /Q/api/transport/* returned 500 for every request. The raw query string was passed to array_merge(), which throws on a string. These are the endpoints the native TransportManager and remote peers call, so no peer could connect over HTTP.
  • Peer requests hung. A server receiving an encrypted request from a peer made an HTTP call back to its own port from inside its event loop, which could never be answered, and used an undeclared $listenPort that always resolved to 8080. Requests are now dispatched in-process through the router.
  • A proxied peer response could crash the server. API results whose status field was a string ("ok" from a peer's /Q/health) were used as the HTTP status code, and the metrics recorder then failed on arithmetic with a string. API status codes are now validated and metrics casts to int.
  • Bloom filters saturated. Sync filters were a fixed 1KB, which gave a 93% false-positive rate at 5,000 keys, meaning most missing records were never sent. Filters are now sized to the table (about 1.8 bytes per key, ~0.1% false positives).
  • tests/test_mesh_e2e.sh (two real servers: identity, handshake, encrypted request) now passes 14/14, and runs in CI with the other mesh suites.

New docs: compatibility.md (rewritten), images.md, sync.md, mobile.md, app-mode.md. The phar now bundles docs/ so /Q/docs works from it.

v1.5.0 – Boldly Go

Choose a tag to compare

@github-actions github-actions released this 22 Sep 07:24

Qbix Server v1.5.0 – Boldly Go

What's New Since v1.3

ECDSA P-256 Signing with M-of-N Verification

The Trust system now uses ECDSA P-256 by default (same curve as Sigstore and SSH). Keys are 256 bits instead of RSA's 2048, signatures are 64 bytes instead of 256. Old RSA keys still work — the signer auto-detects the key type.

Multiple signers can each sign the binary independently. At verification time, configure a threshold: "require 2 of 3 signatures to be valid."

# Generate ECDSA keys
./qbixserver --generate-key=alice
./qbixserver --generate-key=bob

# Each signer signs independently
./qbixserver --sign-binary --key=local/keys/alice.pem --signer=Alice
./qbixserver --sign-binary --key=local/keys/bob.pem --signer=Bob

# Verify: need 2 of 2
./qbixserver --verify-binary --m=2

Sigstore Rekor Transparency Log

After signing, optionally publish the attestation to Sigstore's public transparency log. Rekor provides an independent, tamper-evident record that the binary was signed at a specific time. A compromised server can't fake this — the Rekor entry is append-only and publicly auditable.

./qbixserver --publish-rekor
# → Published! Rekor UUID: ...
# → Verify: https://search.sigstore.dev/?uuid=...

The /Q/attestation endpoint returns both the server's own signatures and the Rekor log entry. Browsers get two independent attestations: the server says "my hash is X, signed by Alice and Bob," and Rekor says "hash X was registered at time T." If they agree, the deployment is what the signers approved.

/Q/attestation Endpoint

Serves the binary hash, signer metadata, verification result, and Rekor reference as JSON. Monitoring tools, browser extensions, or client-side JS can verify the deployment without trusting the server alone.

Security Tab in Control Panel

The panel now has 13 tabs. The new Security tab shows:

  • Binary hash (SHA-256) and all signatures with signer names, key IDs, algorithms, dates
  • Sign from the browser: paste a PEM private key, name the signer, click Sign
  • Verify with adjustable M-of-N threshold
  • Publish to Sigstore Rekor with one click (with confirmation — it's permanent and public)
  • Rekor log entry link after publishing
  • Code Trust status showing per-directory manifest verification results

Metrics and Analytics

Server-side operational metrics with buffered I/O:

Buffered logging — access log lines accumulate in memory (up to 500 lines). Flushed to disk every 10 seconds. Error log has a separate buffer. No disk write per request.

Time-series in SQLite — one row per minute: request count, p50/p95/p99 latency, status code distribution, worker count, memory. Queryable from the dashboard. Retained 30 days by default.

Clickstream analytics — tracks page transitions per session. Each (from_page, to_page) edge accumulates a count. The flow graph powers userflow diagrams on the dashboard. Sessions detected via framework-aware cookie matching (15 frameworks supported) with IP+UA fallback.

Per-page stats — hits, unique sessions, average response time, last hit. Queryable via metrics/pages.

Prometheus endpoint — GET /Q/metrics returns standard gauges and counters in Prometheus text format. Grafana, Datadog, or any scraper can consume it directly.

Log rotation — daily rotation, old logs compressed to .zip, purged after N days (default 7). Zip compression saves 85-90% on repetitive log data.

Anomaly webhook — set Q.webserver.metrics.anomalyWebhook to a URL and the server POSTs a JSON alert on traffic spikes (3× average), error spikes (>10% 5xx), or latency spikes (avg >5s).

Framework-aware session cookies — detects 15 frameworks and knows their session cookie names:

Framework Cookie
Qbix Q_session_* (prefix)
Laravel laravel_session
WordPress wordpress_logged_in_* (prefix)
Drupal SESS* (prefix)
Symfony PHPSESSID
Magento frontend, adminhtml
Craft CMS CraftSessionId
Moodle MoodleSession
+ 7 more auto-detected

Autohost — Auto-Provision Domains

Enable Q.webserver.autohost.enabled: true. When a request arrives with an unknown Host header, the server validates the hostname, checks DNS (multi-resolver: system + 1.1.1.1 + 8.8.8.8), provisions a Let's Encrypt cert, writes the domain config, and serves the app. Authorization modes: open, allowlist with glob patterns, or a custom PHP hook.

Watchdog — Auto-Restart on Crash

Fork an independent process that monitors the server and restarts it on crash with exponential backoff. Gives up after 10 crashes in one hour. On clean exit, the watchdog exits too — the shutdown handler explicitly kills it.

Graceful Worker Recycling

Workers track their request count. After maxRequests (default 1000), a worker finishes its current request and is replaced with a fresh fork. Panel controls: recycle a single worker, or "Recycle All" for a rolling restart. Per-worker table shows PID, status, and request count.

Config File Watcher

The server polls config files every 3 seconds. When a file changes, the config is re-read and merged. New requests see new values immediately. No restart needed.

Data Directory for Packed Binaries

When running as a packed binary, data goes to <binary>.data/ instead of ./local/. Subdirectories for logs, certs, and keys created automatically. All components use qbix_data_path() so paths resolve correctly in both packed and normal mode.

SQLite Auto-Provisioning

If your app bundles a .sqlite file at a conventional location, the server copies it to the data directory on first run and writes the framework config to point at it. The seed stays in the zip for factory reset — delete myapp.data/ and re-run to start fresh.

Seed locations checked: database/database.sqlite (Laravel), var/data.db (Symfony), local/db.sqlite (Qbix), data/db.sqlite, or any single .sqlite file in the app root.

Config writing for six frameworks:

  • Qbix — modifies local/app.json, replaces Db.connections.* with SQLite DSN, preserves existing plugin connections, auto-detects installed plugins from plugins/*/config/plugin.json and adds connections with correct table prefixes. Preserves tab indentation and empty objects.
  • Laravel — sets DB_CONNECTION=sqlite and DB_DATABASE=/path in .env.
  • Symfony — sets DATABASE_URL=sqlite:///path in .env.
  • WordPress — writes DB_DIR and DB_FILE constants in wp-config.php (requires wp-sqlite-db drop-in).
  • Craft CMS — sets CRAFT_DB_DRIVER=sqlite in .env.
  • Drupal — appends SQLite driver config to sites/default/settings.php.

Sets QBIX_DB_PATH environment variable so apps can find the provisioned database.

Hosts File Management

The Domains tab reads /etc/hosts (Windows: drivers\etc\hosts), cross-references configured domains, and offers to add missing entries with platform-specific elevation commands (macOS auth dialog, Windows UAC, Linux pkexec).

Migration Guides

Built-in Cert Renewal

A _certRenewal task runs every 12 hours via the internal scheduler. Scans all certs and renews any expiring within 30 days.

Download

Platform Binary Fork Model
Linux x86_64 qbixserver-linux-x86_64 pcntl_fork (COW)
Linux ARM64 qbixserver-linux-aarch64 pcntl_fork (COW)
macOS ARM64 qbixserver-macos-arm64 pcntl_fork (COW)
Windows x64 qbixserver-windows-x64.exe RtlCloneUserProcess (COW)
Windows x64 qbixserver-windows-x64-gui.exe Same, no console window

Stats

  • 70 unit tests + 30 end-to-end API tests + 25 SQLite provisioning tests = 125 checks, 0 failures
  • 13-tab control panel (4,411 lines)
  • Trust system with ECDSA + M-of-N + Rekor (780 lines)
  • Metrics with clickstream + Prometheus (724 lines)
  • SQLite auto-provisioning for 6 frameworks (387 lines)
  • 22 documentation files
  • ~16,000 lines of PHP + 165 lines of C

v1.4.0 – Boldly Go

Choose a tag to compare

@github-actions github-actions released this 22 Sep 03:17

Qbix Server v1.4.0 – Boldly Go

Cross-Platform, Single-Binary PHP Apps

This release adds Windows COW fork support, auto-provisioning domains, a watchdog process, single-binary app distribution, and a 12-tab control panel that manages frameworks, packages, workers, domains, certs, and hosts — all from a browser.

Download

Platform Binary Fork Model
Linux x86_64 qbixserver-linux-x86_64 pcntl_fork (COW)
Linux ARM64 qbixserver-linux-aarch64 pcntl_fork (COW)
macOS ARM64 qbixserver-macos-arm64 pcntl_fork (COW)
Windows x64 qbixserver-windows-x64.exe RtlCloneUserProcess (COW)
Windows x64 qbixserver-windows-x64-gui.exe Same, no console window

Place qbix_fork.dll next to the Windows binary for COW fork support. Without it, the server falls back to php-cgi subprocess mode.

What's New

Windows COW Fork

A 165-line C shim (fork_shim.c) calls the Windows kernel's RtlCloneUserProcess from ntdll.dll via PHP FFI. Each forked worker shares the parent's memory as copy-on-write — the same model that makes it fast on Linux. The Q_WebServer_Fork class (147 lines) abstracts this: pcntl_fork() on Unix, FFI+DLL on Windows, automatic fallback if neither is available.

Single-Binary App Distribution

Pack your entire application into the server binary:

./qbixserver --pack=./my-app --output=myapp
./myapp --open

The binary detects the appended zip at startup by scanning for the ZIP End-of-Central-Directory signature. Standard zip tools can list and extract the contents (unzip -l myapp, 7-Zip on Windows). The --open flag opens the system browser when the server is ready. Set Q.webserver.open: "/" in config for auto-open without flags.

The GUI variant (-gui.exe) has its PE subsystem set to WINDOWS via editbin, so double-clicking it opens the app in a browser with no console window.

Autohost — Auto-Provision Domains on First Request

Enable Q.webserver.autohost.enabled: true and point any domain's DNS at your server. On first request, the server validates the hostname, checks DNS (multi-resolver: system + 1.1.1.1 + 8.8.8.8), provisions a Let's Encrypt cert via the built-in ACME client, writes the domain config, and serves your app. Subsequent requests go straight through.

Authorization modes: open (any hostname), allowlist (glob patterns like *.example.com), or a custom PHP hook file. Rate limiting: per-IP and global caps to stay under Let's Encrypt quotas. DNS mismatches cached 5 minutes, cert failures cached 1 hour.

Serves a branded splash page over plain HTTP while provisioning. No HSTS until a valid cert is in place, so the first-visit flow never triggers a browser warning.

Watchdog — Auto-Restart on Crash

The server forks an independent watchdog process at startup (Q.webserver.watchdog: true). It detaches via setsid, monitors the server, and on crash: logs the exit code and signal, waits with exponential backoff (2s → 4s → ... → 60s), and restarts. Gives up after 10 crashes in one hour. On clean exit (SIGTERM, Ctrl+C), the watchdog exits too — the shutdown handler explicitly kills it.

Graceful Worker Recycling

Workers now track their request count. After maxRequests (default 1000), a worker is replaced with a fresh fork. The recycling is graceful: busy workers finish their current request before being replaced. No in-flight requests are dropped.

New panel controls: recycle a single worker by index, or "Recycle All" for a rolling restart of the entire pool. The Workers tab shows a per-worker table with PID, status (idle/busy/recycling), and request count.

Config File Watcher

The server polls local/app.json, local/panel.json, and config/app.json every 3 seconds. When a file changes, the config is re-read and merged. New requests see new values immediately. No restart, no reload signal, no downtime.

Hosts File Management

The Domains tab reads /etc/hosts (or C:\Windows\System32\drivers\etc\hosts on Windows) and cross-references your configured domains. Domains not in the hosts file get an "Add to hosts" button that provides the platform-specific elevation command (macOS osascript dialog, Windows UAC, Linux pkexec). Domains ending in .localhost are marked as resolving natively — no hosts entry needed.

Control Panel — 12 Tabs

Tab What it does
Apps List, create, serve apps. Per-app fork mode toggle.
Domains Manage domains, view cert status, one-click ACME provisioning, hosts file management.
Autohost Toggle auto-provisioning, set authorization mode and allowlist, view provisioning log.
Workers Live pool stats, per-worker table with PID/status/request count, recycle controls.
Logs Real-time access and error log viewer.
Cron Scheduled tasks with "Run Now". Built-in cert renewal (every 12 hours).
Frameworks Auto-detects Laravel, Symfony, WordPress, Drupal, Joomla. Install, update, remove packages. Clone from GitHub.
Plugins Qbix plugin management with three-layer version tracking. npm/composer per plugin.
Scripts Run Qbix installer and other scripts from the panel.
Playground Execute PHP snippets in the server context.
System PHP version, extensions, disk space, phpinfo() iframe.
Docs Browse all 21 markdown docs.

Docs Viewer

Browse all server documentation at /Q/docs. Dark theme, sidebar nav, marked.js bundled locally (39KB, no CDN). Inline fallback parser if marked.js is missing.

Migration Guides

New documentation for switching from another server:

GitHub Actions — 4 Platform Build

Builds static binaries for Linux x86_64, Linux ARM64, macOS ARM64, and Windows x64. Windows uses spc from source (not the nightly binary) for reliability. Each platform has separate steps with proper shell handling. fail-fast: false ensures one platform's failure never blocks the others. The Windows build compiles qbix_fork.dll with MSVC and produces a GUI variant via editbin.

Built-in Cert Renewal

A _certRenewal task runs every 12 hours via the internal scheduler. Scans all certs and renews any expiring within 30 days. No external cron job or systemd timer needed.

Framework-Aware App Roots

When autohost provisions a new domain or --pack bundles an app, the server auto-detects the framework and sets the correct web root: web/ for Qbix, public/ for Laravel and Symfony, root for WordPress and Joomla.

Two Modes

Persistent workers (default) — workers stay alive across requests. 28 PHP functions shimmed via source transformation. Static properties restored in 0.03ms. Best performance.

Fork-per-request — for code with untrackable global state. Each worker costs ~120KB (COW), so you run 100× more workers than php-fpm on the same hardware.

Stats

  • 70 unit tests + 27 end-to-end API tests, 0 failures
  • 21 documentation files
  • Panel: 4,222 lines
  • Server: 5,618 lines
  • Total: ~15,000 lines of PHP + 165 lines of C + 208 lines of watchdog

v1.3.0 – Boldly Go

Choose a tag to compare

@github-actions github-actions released this 20 Sep 04:30

Full Changelog: v1.2.0...v1.3.0

Qbix Server v1.3.0 – Boldly Go

Cross-Platform Release

This release adds Windows support, a built-in ACME client, a control panel with framework management, and the ability to bundle your app into a single distributable binary.

Windows COW Fork

Qbix Server now runs on Windows with copy-on-write process forking — the same memory model that makes it fast on Linux and macOS. A small DLL (qbix_fork.dll, 15KB) calls the Windows kernel's RtlCloneUserProcess via FFI to fork the server process with COW page sharing. Each worker shares the parent's loaded PHP classes and only allocates memory for pages it writes to during the request.

If the DLL isn't present or the FFI extension isn't available, the server falls back to php-cgi subprocess mode — still functional, just without COW memory savings.

Download

Platform Binary COW Fork
Linux x86_64 qbixserver-linux-x86_64 Yes (pcntl)
Linux ARM64 qbixserver-linux-aarch64 Yes (pcntl)
macOS ARM64 qbixserver-macos-arm64 Yes (pcntl)
Windows x64 qbixserver-windows-x64.exe + qbix_fork.dll Yes (RtlCloneUserProcess)

What's New

ACME v2 Client — built-in Let's Encrypt integration. Set "tls": "auto" on a domain and the server provisions certificates on startup via HTTP-01 challenges. No certbot needed.

Control Panel — 11 tabs managing every aspect of the server:

  • Apps — list, create, serve apps. Per-app fork mode toggle (persistent / fork-per-request / auto).
  • Domains — add domains, view cert status, one-click ACME provisioning.
  • Workers — live pool stats (count, active, memory, PID).
  • Logs — real-time access and error log viewer.
  • Cron — scheduled tasks with "Run Now".
  • Frameworks — auto-detects Laravel, Symfony, WordPress, Drupal, Joomla. Shows packages from composer.lock, wp-cli, drush, or filesystem scanning. Install, update, remove, activate/deactivate from the panel. Clone plugins from GitHub URLs.
  • Plugins (Qbix) — three-layer version tracking: declared (config/app.json), installed (local/plugins.json), schema (Q_plugin table + extra JSON). npm/composer badges per plugin. Run the Qbix installer (install.php --all) from the panel.
  • System — PHP version, extensions, disk space, phpinfo() iframe. Key extensions highlighted.

Docs Viewer — browse all 18 markdown docs at /Q/docs. Dark theme, sidebar nav, marked.js bundled locally (no CDN). Links between docs work. Offline fallback parser if marked.js is missing.

--pack Flag — bundle your app into the binary: ./qbixserver --pack=./my-app -o myapp. The binary detects the appended zip at startup and serves from it. Standard zip tools can list and extract the contents. Distribute a single executable file containing the PHP runtime, the server, and your entire application.

System Limits Check — on startup, reads ulimit -n, ulimit -u, pid_max. Tries to raise limits automatically. Warns with platform-specific fix commands if it can't.

Dashboard Enhancements — system RAM usage (Linux/macOS/Windows), per-worker RSS from /proc/ps/tasklist, COW savings comparison, fork mode indicator.

Framework Presets

php qbixserver.php --root=public --preset=laravel
php qbixserver.php --root=public --preset=symfony
php qbixserver.php --root=.     --preset=wordpress
php qbixserver.php --root=web   --preset=drupal

Two Modes

Persistent workers (default) — workers stay alive across requests. 28 PHP functions shimmed via source transformation. Static properties restored in 0.03ms. Best performance: 2,294 req/s on CPU-bound work.

Fork-per-request — set forkPerRequest: true for code with internal static variables or untrackable global state. Each worker costs ~120KB (COW), so you run 100× more workers than php-fpm on the same hardware.

Bug Fixes

  • Pool superglobals (SERVER_SOFTWARE, PHP_SELF, etc.) not set in persistent workers
  • Multipart parsing passed lowercased content-type
  • Health endpoint 500 after docs route insertion
  • Safe worker calculation returned PHP_INT_MAX when fd limit was low
  • // comment fallback missing in API doc generation

Stats

  • 72 unit tests + 55 end-to-end tests, 0 failures
  • README split into 18 doc files with relative links
  • Panel: 3,882 lines
  • WebServer: 5,554 lines
  • Total server code: ~13,000 lines of PHP + 165 lines of C

v1.2.0 - Boldly Go

Choose a tag to compare

@github-actions github-actions released this 17 Sep 18:05

Persistent workers are now the default mode. No flags needed — the server auto-detects worker count from your RAM and CPU. Unmodified WordPress, Laravel, Symfony, and Drupal run 6–24× faster than php-fpm on the same hardware, without code changes.

What's new

Persistent workers as default

Workers auto-detect: min(available_RAM / 200KB, nproc × 200). On a 4GB machine that's 400 workers at 120KB each = 47MB total. No --workers flag needed.

28 shimmed functions (was 19)

Nine new shims: register_shutdown_function, set_error_handler, set_exception_handler, restore_error_handler, restore_exception_handler, spl_autoload_register, spl_autoload_unregister, putenv, plus enhanced ini_set tracking with per-request restore. All tracked per request and cleaned up between requests.

Cached Reflection snapshot restore

ReflectionProperty objects are cached at snapshot time. Restore does only setValue() calls — no per-request Reflection lookups. 31% faster on hello-world, 46% faster on WordPress-like workloads.

Benchmarks (single CPU, PHP 8.3)

Workload php-fpm Swoole Qbix vs fpm
CPU-bound (WP-like) ~350 req/s ~400 req/s 2,294 req/s 6.6×
I/O 50ms (c=200) 78 req/s ~300 req/s* 1,060 req/s 14×
I/O 200ms (c=400) 20 req/s ~200 req/s* 488 req/s 24×

*Swoole coroutines require Runtime::enableCoroutine() and coroutine-aware drivers. Unmodified PHP code uses blocking I/O and hits fpm's ceiling.

API discovery — three formats from one scan

Every app's handlers/ directory is scanned automatically:

  • /.well-known/openapi.json — OpenAPI 3.1 (Swagger UI, Postman, Redoc)
  • /.well-known/mcp.json — MCP tools for AI assistants (Claude, GPT, Cursor)
  • /.well-known/qbix.json — Federation manifest

Supports PHPDoc, YUIDoc, and bare // comment formats. No annotations required.

Pool worker fixes

  • $_SERVER: SERVER_SOFTWARE, GATEWAY_INTERFACE, SERVER_PROTOCOL, REQUEST_SCHEME, PHP_SELF
  • PHP_AUTH_USER / PHP_AUTH_PW parsed from Basic auth
  • HTTPS detection from X-Forwarded-Proto
  • Multipart form parsing ($_POST + $_FILES)
  • Status codes from Q_WebServer_State::getStatusCode()
  • php://input stream wrapper for persistent workers
  • // comment fallback for API doc generation

Config

{
  "forkPerRequest": false,
  "skipSourceCodeTransform": false
}

Both default to false — persistent workers with full shimming. Falsy defaults, descriptive names.

Test results

72/72 passing (was 63/72 in v1.1.0).

Full Changelog: v1.1.0...v1.2.0

v1.1.0 — Boldly Go

Choose a tag to compare

@github-actions github-actions released this 07 Sep 17:03

Full Changelog: v1.0.0...v1.1.0

v1.0.0 — Boldly Go

Choose a tag to compare

@EGreg EGreg released this 31 Aug 03:52

A pure PHP web server that replaces nginx + php-fpm. One process serves static files, PHP scripts, WebSocket connections, and a live dashboard.

Install

Download a binary — no PHP installation needed:

Platform Download
Linux x86_64 qbixserver-linux-x86_64
Linux ARM64 qbixserver-linux-aarch64
macOS ARM64 qbixserver-macos-arm64
chmod +x qbixserver-linux-x86_64
./qbixserver-linux-x86_64

Or if you already have PHP 8.1+: php qbixserver.php

What's in the binary

PHP 8.3 interpreter (statically linked), SQLite3, OpenSSL, curl, mbstring, pcntl, sockets, posix, phar, session.

Highlights

  • Two execution modes: fork-per-request (shared-nothing) and octane (--workers=N with snapshot restore)
  • Over 100× the concurrent capacity of nginx + php-fpm on the same memory
  • WebSocket with Socket.IO v5 protocol and rooms
  • SSE / streaming with auto-detection
  • Unix domain socket listen (--socket=/path) for nginx proxy
  • X-Accel-Redirect access control and X-Cache-Tree component-level caching
  • Live dashboard at /Q/dashboard
  • Full Qbix Platform compatibility — same APIs, easy migration to --app mode
  • 160+ tests across 6 suites