diff --git a/AGENTS.md b/AGENTS.md
index 8c8f8b7..0912720 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,7 +2,7 @@
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes.
The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
-compatibility. Current source version: 0.29.5.
+compatibility. Current source version: 0.29.6.
## Delivery
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c7b42cf..e75bffe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@
> test-only changes and packaging implementation notes were moved to
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
+## 0.29.6
+
+- Fix PHP-FPM response truncation with gzip, Brotli, Zstd and deflate in native Web Server mode: fuse the FastCGI response stream so compression can safely poll after EOF without a Tokio worker panic.
+- Route `WARN` and `ERROR` diagnostics (including bounded PHP-FPM stderr and Rust panic-hook events) to the configured error file when enabled; otherwise use console/systemd. Remove duplicate stderr entries while file logging works; fall back to stderr if the file cannot be written or the bounded queue is full.
+- Add FastCGI EOF/compression and dynamic diagnostic-routing regression tests. No schema, cache-format or saved-configuration changes. Rust compilation and live Roundcube verification remain pending until tested with Cargo on a real host.
+
## 0.29.5
- Fix Roundcube 1.7 static asset delivery: a valid `static.php/...` PHP `PATH_INFO` target executes directly, before the Web Service's `try_files` fallback can mistakenly route it to `index.php` and return HTML instead of CSS/JavaScript.
diff --git a/Cargo.lock b/Cargo.lock
index c5e2fae..c669d5f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1688,7 +1688,7 @@ dependencies = [
[[package]]
name = "rust-proxy"
-version = "0.29.5"
+version = "0.29.6"
dependencies = [
"anyhow",
"arc-swap",
diff --git a/Cargo.toml b/Cargo.toml
index 2b17704..7d4b63e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rust-proxy"
-version = "0.29.5"
+version = "0.29.6"
edition = "2021"
rust-version = "1.88"
description = "RustProxy - independent reverse proxy with local administration"
diff --git a/README.md b/README.md
index ca3960b..3c57eec 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-# RustProxy 0.29.5
+# RustProxy 0.29.6
Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration,
streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools.
-**0.29.5 release source.**
+**0.29.6 release source.**
## Layer-4 streams
@@ -11,7 +11,13 @@ Raw TCP/UDP forwarding has its own **Streams** page in the primary navigation, a
Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners.
-## What changed in 0.29.5
+## What changed in 0.29.6
+
+- Fix native Web Server/PHP-FPM response compression across HTTP/1.1, HTTP/2 and HTTP/3 by fusing the FastCGI response stream; prevent a `futures-util::stream::unfold` panic after EOF.
+- When **Error file** is enabled, write warnings, errors, PHP-FPM stderr and worker panic diagnostics to the error file instead of duplicating them in systemd. When disabled, use the console. If the file cannot be written, fall back to console diagnostics.
+- Add EOF, gzip and error-log routing regressions; actual Rust/Cargo build and Roundcube runtime verification are still required.
+
+## Previously in 0.29.5
- Fix PHP-FPM `PATH_INFO` dispatch for existing scripts such as Roundcube 1.7 `/static.php/skins/...`: when PATH_INFO is enabled, invoke the script before `try_files` can fall back to `/index.php`.
- Keep FastCGI `SCRIPT_FILENAME`, `SCRIPT_NAME`, `PATH_INFO`, `REQUEST_URI` and the query string accurate, and check access rules on the resolved PHP script path.
@@ -40,7 +46,7 @@ Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost
- Add streaming **multipart byte-range** responses for requests such as `Range: bytes=0-99,200-299`, with a maximum of 16 ranges and identity representation for multipart responses.
- Add opt-in PHP-FPM **PATH_INFO** splitting. With `path_info: true`, `/index.php/foo/bar` securely resolves the existing `index.php` script, keeps `SCRIPT_FILENAME` on that file and sends `/foo/bar` as `PATH_INFO`.
-0.29.5 keeps SQLite schema 9 and Cold Cache format 3. Existing 0.29.0 configuration remains compatible; new fields use safe/default-off serde defaults. Historical details are in [CHANGELOG.md](CHANGELOG.md).
+0.29.6 keeps SQLite schema 9 and Cold Cache format 3. Existing 0.29.0 configuration remains compatible; no migration or new configuration field is needed. Historical details are in [CHANGELOG.md](CHANGELOG.md).
### Historical 0.26.7 release notes
diff --git a/ROADMAP.md b/ROADMAP.md
index 47106e0..6a22b98 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,5 +1,13 @@
# RustProxy roadmap
+## 0.29.6 - FastCGI compression EOF and diagnostic routing
+
+- [x] Fuse FastCGI response streams so compressors may safely probe the end of a PHP-FPM response multiple times.
+- [x] Route WARN/ERROR diagnostics and panic-hook events to the configured error file, or stderr when the file is disabled/unavailable.
+- [x] Add source tests for repeated EOF polling, gzip output, and live error-log settings changes.
+- [ ] Run Cargo tests and reproduce the Roundcube gzip request against a built 0.29.6 binary before production deployment.
+- [x] Retain schema 9, Cold Cache format 3 and 0.29.x saved-configuration compatibility.
+
## 0.29.5 - Roundcube PHP PATH_INFO routing
- Resolve PHP `PATH_INFO` on an existing script before `try_files` falls back to the front controller, while preserving normal URL and script security checks.
diff --git a/SHA256SUMS b/SHA256SUMS
index 94e453c..2979744 100644
--- a/SHA256SUMS
+++ b/SHA256SUMS
@@ -1,13 +1,13 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
-03caed3d51469aba1a5ef646c9b986b79648a14f9fc58631c3e26a836eb675b9 AGENTS.md
-5fc20f00dcbce7659a1366a79c9adc65644db4790997904f66a40f611dc3e6b0 CHANGELOG.md
-733220d3b7f4b2c5b79c0d152f6e046cdeb34a29ce0a4e51565078e7f1562773 Cargo.lock
-88b2e5d4b64288177b9ecd4319616a89984b8cc4654cc92e615c685f3c86685f Cargo.toml
+3b2eca0de71bfccea5a1053db1169555e66e2fc14fcef662cb9b04014d6db925 AGENTS.md
+58eb5a78cefc162dd12d5a5adf64b778fd73fff815d9dc8fb75898e15fb9411c CHANGELOG.md
+b42a46c774a5202a41872a035b6a19fd784111dc7d5a9ac66799b0decdca4ca6 Cargo.lock
+2d3de7fedcf5aa9c1c4e45092c8c7b958d60c25853c5a351570ecc298aad60ac Cargo.toml
75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile
-2c7a5c76f1cab0ec0c3a4ac24d97c3e94505b88d6fb177e7a81c9f63be071de3 README.md
-b91d99f9b1415a12d124d1223b806591f528b3ea629cb5b97e2e9498f9963c4d ROADMAP.md
+a66dd9dfbd7f0e382491e1807fec564d2ac6125684d4c24540affce88b89bcbf README.md
+299fb1eb8e0471a6b0da994290414505766079efbb28a12bcf9c77b5c6cdecaf ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
@@ -27,7 +27,7 @@ e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/n
5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go
875faa3969df9409d79f050a84b4183496f557edda3d34aaec19fe556cf8c471 docs/ACME_PROVIDERS.md
ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md
-67a1e0cbe5d1139251852401d876273ec7ea8cd1c595ecd63af097921b0d21b3 docs/API.md
+248bd040dcbda23e223a439e17232cd681deed3a77f273615a6ffa22ddfe51e4 docs/API.md
f054206a09bed81018708608cdeecbfd09018ee7cec9d3abc038f8a37d47b815 docs/API_ACCESS.md
4c5288c9ed427cfb919bd023ed9a321dcf6d64bc4cad3e1768393356c5d835ba docs/ARCHITECTURE.md
598298303036912c19e0a4be1431acc7b7d3df7333f3e756e8268ac0e8c16921 docs/CACHE.md
@@ -36,7 +36,7 @@ c6003dcbc0b95e2cfa9523b7017186cc8b4826248b588af77111ac5eb15174f9 docs/CHANGELOG
c8e69e492179ce39958cc63615cf46c4ac08cfdb7f3483aaab66868a3abcd687 docs/CONTAINER.md
7a88824e1d84383ebf69fbef992d6d6c9975902a9170502eac800bf38fd97712 docs/CUSTOM_LOCATIONS.md
16d8f59908c66e595d6003495cbf5c068c68b10aa9495eb8612c38b2de0e4a7c docs/DOCKER_DISCOVERY.md
-9ea8db4d034ecb573976dd323da29f044538525c230df9f43a47be1ac867b24c docs/ERROR_LOGGING.md
+5324b215075214a5bb9b388bc82d6491eacfc507df95856d1aecedd184e25b80 docs/ERROR_LOGGING.md
7dacf2129f2575823d26104ce12fd48a951906c8b2cfd40a7981b8e14813b154 docs/FILE_LOGS.md
8dfc89a8b3512103b87864bd7c24a3dc3de4465edf8f9874e6bed1f4634dd930 docs/HTTP3.md
a00ec3c1d3b28ba10677435128e031e5f18fe8c5cef2ac90eb0f6779cefeb434 docs/INSTALL.md
@@ -48,7 +48,7 @@ fea1ff4055d0930edeffb0c8501a6ba1ac55ec7a7064dcd0e300a43915e8432e docs/MIGRATION
1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md
e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND_HOSTS.md
89a1af6c9fe10c572b211f9632ce256166e67990b5dacacf2ae07265d99a5172 docs/NPM_MIGRATION.md
-7d19e5617b18c95bedd307b311517460f7c6ce0a19a67fc614fa9a1f48dfcb1c docs/PHP_FPM.md
+98ed766b6c6778114959a1f17048188b63f218501bbef326067017a91e114a66 docs/PHP_FPM.md
572c70fbd2f61fecd80a8ff9fa5de4a94c4f158de5e90da8e920f99b051241fa docs/PROMETHEUS.md
7046079140db7c152dc9caa536259d3c175c8893bda3a23d6ca4e4acb8d0b4c0 docs/README.md
6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md
@@ -60,7 +60,7 @@ a2bb837cdd3559d58158880c1f832badf64035376a3623050bb4e40c2bca104c docs/ROADMAP_H
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
d5c5c59f6e63b8d83dad5d159657c4d4039dfeb4cc1d8af210169b576adbf7e2 docs/SETTINGS.md
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
-b29d1e50e75a6f9d440ffa6e4e6a3fb572a8971cad0751fa2f7b2026b641c8c3 docs/TEST_REPORT.md
+45c5af7894f1e3da1d4f49e0fbdb3657baa49aefe5ecfc004f14c5fc1051997e docs/TEST_REPORT.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
660687fc961f71aae2bdf71595aa47a336fa782f4e78ceee8c1d1e63e46393ee docs/TRANSPORT.md
@@ -132,8 +132,8 @@ cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/r
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
71cde48daa538431dfac51e438b1ed23b061abfdb4e144ba704237d75ac6270c langs/README.md
-3bd93bb75fc7ee7e5a67143af4fd388050dd7aa6037b6fa342f766b459100e2d langs/en_US.json
-0f43e65e9787106a7465e98d639ec63a164de47fcbe12059b9590e633f857b42 langs/pl_pl.json
+21650f4e9acf69d3c064068e3f4565bf99371b707c8969b6383ba57d32e7e9ab langs/en_US.json
+e256c7816a9b2d8e5cd5a9b3c1cb3fc049ef9a052f7b42407a61a59725074063 langs/pl_pl.json
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
@@ -182,7 +182,7 @@ e9f72e65c9ceb255248f460cd7a003fff33e4b258160e248353c3e1fb4e952fb src/admin/file
1596561bb57976268b02e36e8ace426091045ee9d3f8832ce775eccdbc30bc81 src/admin/file_logs_io.rs
b3e2c11dbc11bb8930117afdac64779111acc2ca7b2857f6e359a5108fc9ced8 src/admin/logging_api.rs
58802528128f53e493f7339063433a747b605007a8842b5efc670132d897d6f7 src/admin/mod.rs
-1396d24136707a81b9427290a855d9b5ec20fc1b222acd9e26b5319a52426328 src/admin/openapi.json
+e8590a28bf492162ac8d64a26b6fec44bba2efcb258ab608a04da135136b7af9 src/admin/openapi.json
6c2b9a0ecb2f4172f110a363c22a6be32895dbcff9605fe7aa94efe91e0a536a src/admin/operations.rs
5287fff6b3ad436ac695e9b66feb1b0b5705b744a9f00cec7ea6a3e951e5eebf src/admin/preferences.rs
31bd90ee5f5ec2984920225ff6b57e07db3e0758092fb55d7c230e3590c1b015 src/admin/public.rs
@@ -220,7 +220,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
af6e7a2e890df2fb7385a374bcb3fba52fb5a53dbe18adc1fb7865660fcba842 src/admin/web/file-logs.js
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
-66ad32f9259c8e500c44e4508a6fad5ad6e01c957dc044da78ef8f2fdca551cf src/admin/web/index.html
+d22291b13e371f2265273762bbba392e543f68a2799571f769abf306070a9058 src/admin/web/index.html
24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
af0fbd94d9b88ce1cce28e39dfd2c35de031adcba412dd4a9a17d9b416c256dd src/admin/web/login.html
@@ -343,14 +343,14 @@ d0cd8589321595309834690fa9ddb98488886bc98f34ea45aa0ccb3035412fb5 src/http3.rs
68bc11160bc5275a904e48af8698b37433096fb6d592364d8281d59bb9e2b3f4 src/http3/tests.rs
3dab8fac75263120e48ee869ad55a3e2ccc2a19fc9f6d6a6fe055657ec55b1e3 src/lib.rs
2551189a3e5701371026c9abd2c5d44d37495f28fb67e220180421bfd315e3dd src/logging.rs
-d8abc31f130e85b38c760b96e3369639464bca513991532e025485489a0af70e src/logging/error_runtime.rs
+3849a09a7d7402dc4d867b646cda153c574327ddd3c707578ddb4dc6f95b1563 src/logging/error_runtime.rs
a16613114273c54b6d2a16a94fa8748550e91468585469642622bd2c86087baa src/logging/format.rs
-83e7bd1517ede4a2d1696ba0b22bdd5d34a4c29fcc8eb5c30c454dc4eba66853 src/logging/model.rs
+f5815d778cc1785723faed4abb15f9880f22b6524b5289fe1e7158905aac0986 src/logging/model.rs
7c0d8e8f46f96cbaf3a5cb5fd47953f9185d7558dd71e599676bc63578b7ca99 src/logging/runtime.rs
-17425ebbca6bbbb0dcaeb65ebc44b1e51488a4092b7b6add556e764c6d486d6a src/logging/tests.rs
+03c50bd14fecd421b0901a5f0af891dc277e35420e32068781e3a6d78c88c24b src/logging/tests.rs
3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs
2f6b5a9517453541bf43580a9d963e64e36338575ee9ad295ca3718803a59ed6 src/main/admin_runtime.rs
-b4e9a3fae5df972df3747352383beb16524056a6aeb95d9d609414f790a586ce src/main/bootstrap.rs
+42a9197475823f67c1ca762951ede5d7a2f10124bd6ed4d1a3365f4297be894c src/main/bootstrap.rs
5a98d0e249338e73aa57ae2e352dc16db2f79c2d042c1544ce1c2022da76a31c src/main/proxy_runtime.rs
4dd7c1084a6214c0f2513ebf86fe8ef144470695557772bfe25979edd64491ab src/main/shutdown.rs
0a6578a0f4023b9f8d73043bf942dc5e096ef9afb8c0d2f131c14f65d08ac01d src/main/shutdown/tests.rs
@@ -469,8 +469,8 @@ b61a40bb0ead18b3e348f03a258bab6a68d5c8f46968430f692458aa0cfd5bce src/vhost/vali
7853f6ec127a91163a4418ca45b93a92f424d5c618f27b29f9d526598a26f7ef src/web_server/config/model.rs
42a613234af7015a0b14f125bb88be1dcc30a47e9064b12e3fbb5b71ce6f7caf src/web_server/config/validation.rs
86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.rs
-1c0759a3b2fda727b2af7180484bb72febdb079356dbba3169844d74281acb6f src/web_server/fastcgi/protocol.rs
-6a2fff148bf5a6485c2017a8b0cb3d4214cfc247197dd3c77619bf0d19331a19 src/web_server/fastcgi/response.rs
+7608f18d9a093a7cdd7019bd9a6cc2e4cf29bc941ab760ecaa86ab095958c497 src/web_server/fastcgi/protocol.rs
+1e10cabc08e1f71bcd281fd01c3fef28c0f2b8b294e4fc4adf5b19f3235a2368 src/web_server/fastcgi/response.rs
8186363315bd998919c6c783e6f7501528f257a5c0da77c5d60f423e8670db5e src/web_server/filesystem.rs
0aafd7f37c4fe784b047646e4ce5ddcb5fe821231a41ed0a9f519529385288a8 src/web_server/htaccess.rs
013830a79a78ce803ce776a9b333de406367751e0cea7e33c3f6a98e6600c243 src/web_server/htaccess/model.rs
@@ -486,8 +486,8 @@ c5cb561b0f276e8082f904b9446fa5885f6b3c0608f25ea2ef425061e9f7f6b4 src/web_server
400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs
d61ebdab046b6d2f8e34bb3058c5eb3f2d0766be573810387f48dd3116dc1a6d src/web_server/serve.rs
63d71a336187779e9cbc1cc6afa065c5fa9472835a8c2cc15dbf4527279d0c90 src/web_server/tests.rs
-77edb44b58d08d9818c42baaa34acdcbb19aea5ef8fb9014e6d6809d133deb07 src/web_server/tests/part_01.rs
-874c228e03d11b33b5db7e58279569b35820d813ad6a9f45ee73b630f7bdad03 src/web_server/tests/part_02.rs
+9426ad19fd6234e944d6e122a995d2cf8ebe4177d27e42fd157f64d05abd1e23 src/web_server/tests/part_01.rs
+6f0b68d732fa0d1385744ef7ac1b36d558f9532f9ef1dea44d3dcc47d149840a src/web_server/tests/part_02.rs
8f9a0e2b0a69a1f2b8cf7505a654606150da4a53bc8c09d1d6c4b8751185b134 src/web_server/tests/part_03.rs
b25b6e0082b5153272119c7faba9d6f2ccdd288ce5c066526431c56954c66c4c src/web_server/tests/part_04.rs
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
@@ -497,12 +497,12 @@ eecf680f001f2110dde4eceaccf2f922859a16c946a644f8854e8de4d3771f7c tests/policies
728999110a6d8cacb62bc0399bc333fa23a66526ee23f563a3c93bf00e5fad19 tests/transport_config.rs
b04362ad082a32c9ccc4cf38c46f44d71347adecf7aba3224b3af3ea4872e3d0 tools/README.md
358c1e94dd2b4a756430e5335747f0b437345b1713670a9972c5bcf78b837ccb tools/audit/admin_audit.py
-74b6ab569703d0e558b9708fddc3ed0cae4cd7bc4740be536205beba5d0a20f1 tools/checks/check_source.py
-0db0b6b6083df7e0e977bb4fd963826c761b83b0d68ea8f001347f02c7b0e728 tools/checks/docs_check.py
+b6cf2246b4075c8a030cdd5fc4c98557afe9b42562948a064dc6ae9ebeb31854 tools/checks/check_source.py
+e376c6324b7327943ad8b9715c497d05c8b0e3f83d6428f0456edfe07df05f15 tools/checks/docs_check.py
4f7821e7e25b09ce251d25a0b3fb4d8ce8396c36a5ed971c2637aeb325425909 tools/checks/langs_check.py
cf762780c4085791c72c3c5f93054d7f2a1107403bee0616a1f4113bbcbb99f9 tools/checks/memory_contracts.py
-655eedbe607fa8b65e3184f2d47cf5cabe0afbf19506fdf777b2a5f1a745529a tools/checks/preflight.py
-0ad29b742f77aa8347f71e0030ab8b6d4504e0fa2380b7ecb8cf88bf1da024ff tools/checks/release_smoke.py
+c249798b65079d4579ff8e7b150b51409237482668eebe156f01e2425bed799a tools/checks/preflight.py
+29579531358072b98e21979a5f65eeed096707c17668c03b77ccbf76183cec02 tools/checks/release_smoke.py
f69845bc26ec33160fc756953991cf32aa8ad6a7fb4a131caa2093db91e783ef tools/checks/rust_module_contracts.py
ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed tools/checks/rust_source.py
adf84a983e0dbba228e47d71ec2d5c9964f7ca9989d5139de58f96ee961b035a tools/checks/security_contracts.py
diff --git a/docs/API.md b/docs/API.md
index 0a6a337..5059566 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -171,7 +171,7 @@ Access entries are emitted at exchange completion or tunnel closure, not only wh
headers arrive. WebSocket exchanges retain status 101 and a `websocket_closed`
outcome when cleanly released. Errors/cancellation affect the outcome. No query
values, request bodies, cookies, password material or WebSocket payloads are logged
-by the access logger. Error diagnostics use stderr tracing. The in-memory access ring retains up to 4096
+by the access logger. Warnings and errors use the configured error file, or stderr when that file is disabled/unavailable. The in-memory access ring retains up to 4096
completed entries; live SSE/UI snapshots use the newest 256 by default. The dashboard can
temporarily expand an open Live Logs window to the retained 4096-entry buffer and keep
merging new live snapshots until that window closes. This is not a durable lossless log archive.
diff --git a/docs/ERROR_LOGGING.md b/docs/ERROR_LOGGING.md
index e29d9e6..a26e181 100644
--- a/docs/ERROR_LOGGING.md
+++ b/docs/ERROR_LOGGING.md
@@ -1,6 +1,6 @@
-# Optional error-file logging
+# Error and warning logging
-RustProxy can write an additional local file containing only `ERROR` events. This is an optional sink layered on the existing tracing/logging pipeline; journald/systemd and other configured sinks remain independent.
+RustProxy routes diagnostics according to the **Error file** switch under Settings > Logging. When the file is enabled, `ERROR` and `WARN` events, including PHP-FPM stderr and Rust worker panics, go to the configured error file rather than also being printed to the systemd journal. When the file is disabled (the default), those events go to the console/stderr (journald under systemd). `INFO` and lower-severity messages continue to use the console and any independently configured access sinks are unaffected.
## Configuration
@@ -16,15 +16,17 @@ The settings document contains:
}
```
-`format` is `JSON` or `RAW`. The feature is disabled by default. The administration Settings UI exposes the same enable/path/format controls and applies changes at runtime.
+`format` is `JSON` or `RAW`. The feature is disabled by default. The administration Settings UI exposes the same enable/path/format controls and applies changes at runtime, without restarting RustProxy.
-The sink accepts only `ERROR` events. JSON is one object per line; RAW is one human-readable line per event.
+The sink accepts `WARN` and `ERROR` events. JSON is one object per line (including its actual `level`); RAW is one human-readable line per event.
## Runtime behavior
-Events are handed to a bounded queue and written by one asynchronous worker. The request path does not perform the file write. When the queue is full, new error-file events are dropped and counted instead of allowing unbounded memory growth.
+Events are handed to a bounded queue and written by one asynchronous worker. The normal request path does not perform file I/O. When the queue is full or closed, the event is counted as dropped by the file sink but immediately written to the console as a redacted emergency fallback; memory usage stays bounded.
-Filesystem errors do not stop RustProxy. The worker reports failures through the existing logger, rate-limits repeated diagnostics and retries opening the file. Changing the path or disabling the sink closes the current file; a format change takes effect without an application restart.
+Filesystem errors do not stop RustProxy. The worker writes the affected diagnostics to stderr, rate-limits sink-failure messages, and retries opening the file. If the switch is disabled while events remain queued, they are sent to stderr rather than silently discarded. Changing the path or disabling the sink closes the current file; a format change takes effect without a restart.
+
+RustProxy installs a panic hook after initializing tracing so worker panics are recorded as `ERROR` events including thread, source location and panic payload. It intentionally does not install the default Rust hook in parallel, which would duplicate the message on stderr. It cannot capture forced termination (`SIGKILL`, `abort`) or messages written directly to stderr by external processes.
Created log files use mode `0600` on POSIX. Symlink/non-regular targets are refused.
diff --git a/docs/PHP_FPM.md b/docs/PHP_FPM.md
index 63740f4..547ee40 100644
--- a/docs/PHP_FPM.md
+++ b/docs/PHP_FPM.md
@@ -225,7 +225,7 @@ The FastCGI stdout parser understands standard CGI response headers, including `
For `HEAD`, PHP-FPM can still execute normally but the client response body is empty. GET/POST/PUT and other methods can reach a PHP script once routing resolves to a configured executable extension.
-PHP stderr is bounded by `stderr_log_bytes` and written to the RustProxy diagnostic log; it is not sent to the client as page content. CGI response headers are separately bounded by `max_response_header_bytes`.
+PHP stderr is bounded by `stderr_log_bytes` and recorded as a RustProxy `WARN` diagnostic. It goes to the configured error file when enabled, or the console/systemd journal otherwise; it is not sent to the client as page content. CGI response headers are separately bounded by `max_response_header_bytes`.
## Caching
diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md
index 8211d36..fbdd705 100644
--- a/docs/TEST_REPORT.md
+++ b/docs/TEST_REPORT.md
@@ -1,4 +1,14 @@
-# Verification report - RustProxy 0.29.5
+# Verification report - RustProxy 0.29.6
+
+## 0.29.6 FastCGI EOF and error-log routing (2026-10-09)
+
+- Operator reproduced: uncompressed `/static.php/...` returned HTTP 200 and 29,309 bytes with cache BYPASS; gzip returned HTTP 200 then TLS unexpected EOF and zero bytes. Repeated panics in `futures-util 0.3.34` `stream/unfold.rs:108` correlate with these failures. This is operator evidence, not a reproduced in-house integration test.
+- Cause identified in source: native Web Server FastCGI response bodies used an unfused `stream::unfold` over a Tokio MPSC receiver. The compression wrapper may poll the stream after it returns `None`; the unfused `Unfold` then panics. The Reverse Proxy source uses an HTTP client body rather than this FastCGI stream.
+- Change: wrap the FastCGI response stream in `.fuse()`. Add repeated-post-EOF polling test and mocked FastCGI-to-gzip completion/decode test. No changes to PHP-FPM parameters, vhost configuration or migrations.
+- Logging change: the existing error-file controller receives WARN and ERROR (including PHP-FPM stderr); the console tracing layer suppresses these only when the file is enabled, with fallback to stderr on file/queue failure. A Rust panic hook emits structured ERROR diagnostics instead of bypassing the sink via the default stderr hook.
+- Add asynchronous file routing and live enabled/disabled toggle tests. Actual Rust compilation, FastCGI compressor tests, panic runtime acceptance and live Roundcube regression require a Cargo-equipped environment and remain unverified here.
+- Release: Cargo package/lock root/OpenAPI and release checks moved to 0.29.6; retained SQLite schema 9 and Cold Cache format 3.
+- Static checks passed: `version_contracts.py`, `docs_check.py`, `langs_check.py`, `rust_module_contracts.py`, `web_server_contracts.py`, `check_source.py`, `release_smoke.py`, `web_service_ui_test.mjs`, and Node syntax/OpenAPI JSON parsing. The `./dev.sh test` run passed its source, browser UI, security, memory and upgrade checks before reaching the missing Rust toolchain; `./dev.sh check` exited 127 for the same reason.
## 0.29.5 Roundcube PHP PATH_INFO dispatch (2026-10-09)
@@ -102,9 +112,9 @@ This source package is not certified for production deployment.
Installer/update now installs /etc/logrotate.d/rustproxy using the recorded service user/group. Updates repair /etc/profile.d/rustproxy-path.sh rather than warning. logrotate uses copytruncate because RustProxy holds open log descriptors; some log records may be lost during truncate. Rust build and live Debian/systemd acceptance remain unverified in this environment.
-## Version identity (0.29.5 source release)
+## Version identity (0.29.6 source release)
-`Cargo.toml` package version is the canonical release identity (now 0.29.5). Rust uses `CARGO_PKG_VERSION` at build time; `build.rs` substitutes the admin HTML placeholder. Packaging, upgrade and dev scripts read the manifest with `scripts/version.py`. Run `python3 tools/checks/version_contracts.py` to check that executable paths do not hardcode a version. Historical documentation and external dashboards may retain manually managed versions. Rust compilation/runtime verification is still pending.
+`Cargo.toml` package version is the canonical release identity (now 0.29.6). Rust uses `CARGO_PKG_VERSION` at build time; `build.rs` substitutes the admin HTML placeholder. Packaging, upgrade and dev scripts read the manifest with `scripts/version.py`. Run `python3 tools/checks/version_contracts.py` to check that executable paths do not hardcode a version. Historical documentation and external dashboards may retain manually managed versions. Rust compilation/runtime verification is still pending.
## Shared PHP-FPM profiles and Web Server cache/transform/Host patch (0.29.4)
diff --git a/langs/en_US.json b/langs/en_US.json
index ecb5d6b..b9dcfeb 100644
--- a/langs/en_US.json
+++ b/langs/en_US.json
@@ -921,6 +921,7 @@
"loggingSent": "sent",
"loggingCapacity": "capacity",
"errorLogTitle": "Error file",
+ "errorLogRoutingHelp": "Warnings, errors and worker panics go to this file when enabled, or to the console/systemd journal when disabled. File failures fall back to the console.",
"errorLogPath": "Error log path",
"errorLogFormat": "Format",
"tls12": "TLS 1.2",
diff --git a/langs/pl_pl.json b/langs/pl_pl.json
index 85540aa..d2f18d6 100644
--- a/langs/pl_pl.json
+++ b/langs/pl_pl.json
@@ -921,6 +921,7 @@
"loggingSent": "wysłanych",
"loggingCapacity": "pojemność",
"errorLogTitle": "Plik błędów",
+ "errorLogRoutingHelp": "Ostrzeżenia, błędy i paniki wątków trafiają do tego pliku, gdy jest włączony. Gdy wyłączony, trafiają do konsoli/systemd. W razie błędu zapisu używana jest konsola.",
"errorLogPath": "Ścieżka logu błędów",
"errorLogFormat": "Format",
"tls12": "TLS 1.2",
diff --git a/src/admin/openapi.json b/src/admin/openapi.json
index 0aae8ee..3430bd0 100644
--- a/src/admin/openapi.json
+++ b/src/admin/openapi.json
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "RustProxy Administration API",
- "version": "0.29.5",
+ "version": "0.29.6",
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust."
},
"servers": [
diff --git a/src/admin/web/index.html b/src/admin/web/index.html
index 17367d6..797f3b2 100644
--- a/src/admin/web/index.html
+++ b/src/admin/web/index.html
@@ -2554,6 +2554,7 @@
Error file
+
Warnings, errors and worker panics go to this file when enabled, or to the console/systemd journal when disabled. File failures fall back to the console.