v0.29.6
This commit is contained in:
1 parent
3e33982cd3
commit
ad95bfb050
27 files changed
+309
-80
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Generated
+1
-1
@@ -1688,7 +1688,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-proxy"
|
||||
version = "0.29.5"
|
||||
version = "0.29.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
+26
-26
@@ -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
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
+13
-3
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -2554,6 +2554,7 @@
|
||||
<h3 class="subheading" data-i18n="errorLogTitle">Error file</h3>
|
||||
<label class="check"><input id="error-log-enabled" type="checkbox" /><span
|
||||
data-i18n="enabled">Enabled</span></label>
|
||||
<p class="fine" data-i18n="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.</p>
|
||||
<div class="field-grid">
|
||||
<label><span data-i18n="errorLogPath">Error log path</span><input
|
||||
id="error-log-path" maxlength="1024"
|
||||
|
||||
@@ -161,7 +161,7 @@ fn format_error_event(event: &ErrorEvent, format: ErrorLogFormat) -> Vec<u8> {
|
||||
ErrorLogFormat::Json => {
|
||||
let mut object = Map::new();
|
||||
object.insert("timestamp".into(), Value::String(event.timestamp.clone()));
|
||||
object.insert("level".into(), Value::String("ERROR".into()));
|
||||
object.insert("level".into(), Value::String(event.level.clone()));
|
||||
object.insert("target".into(), Value::String(event.target.clone()));
|
||||
object.insert("message".into(), Value::String(event.message.clone()));
|
||||
for (key, value) in &event.fields {
|
||||
@@ -176,8 +176,9 @@ fn format_error_event(event: &ErrorEvent, format: ErrorLogFormat) -> Vec<u8> {
|
||||
ErrorLogFormat::Raw => {
|
||||
let one_line = |value: &str| value.replace('\r', "\\r").replace('\n', "\\n");
|
||||
let mut line = format!(
|
||||
"{} ERROR {} {}",
|
||||
"{} {} {} {}",
|
||||
event.timestamp,
|
||||
event.level,
|
||||
event.target,
|
||||
one_line(&event.message)
|
||||
);
|
||||
@@ -193,6 +194,21 @@ fn format_error_event(event: &ErrorEvent, format: ErrorLogFormat) -> Vec<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_error_fallback(event: &ErrorEvent, format: ErrorLogFormat) {
|
||||
// File unavailable or the bounded queue is full: preserve the redacted
|
||||
// diagnostic on stderr rather than dropping it into a black hole.
|
||||
let line = format_error_event(event, format);
|
||||
let mut stderr = io::stderr().lock();
|
||||
let _ = stderr.write_all(&line);
|
||||
}
|
||||
|
||||
fn warn_error_sink_failure(path: &std::path::Path, error: &dyn std::fmt::Display, action: &str) {
|
||||
let path = redact_text(&path.display().to_string());
|
||||
let error = redact_text(&error.to_string());
|
||||
let mut stderr = io::stderr().lock();
|
||||
let _ = writeln!(stderr, "RustProxy error log {action} ({path}): {error}; using stderr fallback");
|
||||
}
|
||||
|
||||
async fn error_log_loop(
|
||||
mut receiver: mpsc::Receiver<ErrorEvent>,
|
||||
settings: Arc<ArcSwap<ErrorLogSettings>>,
|
||||
@@ -232,6 +248,9 @@ async fn error_log_loop(
|
||||
file = None;
|
||||
open_path = None;
|
||||
retry_after = None;
|
||||
// A queued event may outlive a live settings change. It was
|
||||
// suppressed on the console when queued; do not lose it now.
|
||||
write_error_fallback(&event, config.format);
|
||||
continue;
|
||||
}
|
||||
let path = PathBuf::from(&config.path);
|
||||
@@ -250,24 +269,33 @@ async fn error_log_loop(
|
||||
errors.fetch_add(1, Ordering::Relaxed);
|
||||
retry_after = Some(Instant::now() + ERROR_LOG_RETRY);
|
||||
if last_warning.is_none_or(|time| time.elapsed() >= ERROR_LOG_RETRY) {
|
||||
tracing::warn!(path=%path.display(), %error, "optional error log file is unavailable");
|
||||
warn_error_sink_failure(&path, &error, "open failed");
|
||||
last_warning = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(opened) = file.as_mut() else {
|
||||
write_error_fallback(&event, config.format);
|
||||
continue;
|
||||
};
|
||||
let line = format_error_event(&event, config.format);
|
||||
if let Err(error) = opened.write_all(&line).await {
|
||||
// Tokio's File buffers writes; flush so diagnostics are visible to
|
||||
// log readers promptly and a later shutdown cannot strand a record.
|
||||
let write = async {
|
||||
opened.write_all(&line).await?;
|
||||
opened.flush().await
|
||||
}
|
||||
.await;
|
||||
if let Err(error) = write {
|
||||
errors.fetch_add(1, Ordering::Relaxed);
|
||||
file = None;
|
||||
retry_after = Some(Instant::now() + ERROR_LOG_RETRY);
|
||||
if last_warning.is_none_or(|time| time.elapsed() >= ERROR_LOG_RETRY) {
|
||||
tracing::warn!(path=%path.display(), %error, "optional error log write failed");
|
||||
warn_error_sink_failure(&path, &error, "write failed");
|
||||
last_warning = Some(Instant::now());
|
||||
}
|
||||
write_error_fallback(&event, config.format);
|
||||
} else {
|
||||
written.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
+18
-2
@@ -328,6 +328,7 @@ pub struct ErrorLogStats {
|
||||
#[derive(Debug)]
|
||||
struct ErrorEvent {
|
||||
timestamp: String,
|
||||
level: String,
|
||||
target: String,
|
||||
message: String,
|
||||
fields: BTreeMap<String, String>,
|
||||
@@ -369,9 +370,23 @@ impl ErrorLogController {
|
||||
self.settings.store(Arc::new(settings));
|
||||
self.settings_changed.send_replace(());
|
||||
}
|
||||
/// All diagnostics go to stderr when file logging is disabled. With a file
|
||||
/// configured, warnings/errors go only to that file (unless the sink fails).
|
||||
/// Informational startup messages remain on the console.
|
||||
pub fn console_enabled_for(&self, level: &Level) -> bool {
|
||||
!matches!(*level, Level::WARN | Level::ERROR) || !self.settings.load().enabled
|
||||
}
|
||||
fn push(&self, event: ErrorEvent) {
|
||||
if self.settings.load().enabled && self.sender.try_send(event).is_err() {
|
||||
let config = self.settings.load();
|
||||
if !config.enabled {
|
||||
return;
|
||||
}
|
||||
if let Err(error) = self.sender.try_send(event) {
|
||||
self.dropped.fetch_add(1, Ordering::Relaxed);
|
||||
// An overloaded/closed sink must not silently swallow a diagnostic
|
||||
// suppressed by the console filter. This emergency path is bounded
|
||||
// to one event and only runs on a failed non-blocking enqueue.
|
||||
write_error_fallback(&error.into_inner(), config.format);
|
||||
}
|
||||
}
|
||||
pub fn stats(&self) -> ErrorLogStats {
|
||||
@@ -451,7 +466,7 @@ impl tracing::field::Visit for EventVisitor {
|
||||
}
|
||||
impl<S: Subscriber> Layer<S> for ErrorLogLayer {
|
||||
fn on_event(&self, event: &Event<'_>, _ctx: LayerContext<'_, S>) {
|
||||
if *event.metadata().level() != Level::ERROR {
|
||||
if !matches!(*event.metadata().level(), Level::ERROR | Level::WARN) {
|
||||
return;
|
||||
}
|
||||
let mut visitor = EventVisitor::default();
|
||||
@@ -466,6 +481,7 @@ impl<S: Subscriber> Layer<S> for ErrorLogLayer {
|
||||
}
|
||||
self.controller.push(ErrorEvent {
|
||||
timestamp: rfc3339_now(),
|
||||
level: event.metadata().level().as_str().to_owned(),
|
||||
target: event.metadata().target().to_owned(),
|
||||
message: redact_text(visitor.message.as_deref().unwrap_or("error")),
|
||||
fields,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
fn entry() -> LogEntry {
|
||||
LogEntry {
|
||||
@@ -212,6 +213,7 @@ mod tests {
|
||||
fn error_json_is_one_record_and_reserved_fields_cannot_be_overwritten() {
|
||||
let event = ErrorEvent {
|
||||
timestamp: "2026-10-03T12:34:56.123Z".into(),
|
||||
level: "ERROR".into(),
|
||||
target: "rust_proxy::http".into(),
|
||||
message: "upstream failed\nsecond line".into(),
|
||||
fields: BTreeMap::from([
|
||||
@@ -224,6 +226,54 @@ mod tests {
|
||||
let value: Value = serde_json::from_slice(&line).unwrap();
|
||||
assert_eq!(value["message"], "upstream failed\nsecond line");
|
||||
assert_eq!(value["request_id"], "abc");
|
||||
assert_eq!(value["level"], "ERROR");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn diagnostics_switch_between_console_and_file_without_restart() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let path = tmp.path().join("error.log");
|
||||
let controller = ErrorLogController::start();
|
||||
assert!(controller.console_enabled_for(&Level::WARN));
|
||||
assert!(controller.console_enabled_for(&Level::ERROR));
|
||||
|
||||
controller.configure(ErrorLogSettings {
|
||||
enabled: true,
|
||||
path: path.to_string_lossy().into_owned(),
|
||||
format: ErrorLogFormat::Json,
|
||||
});
|
||||
assert!(!controller.console_enabled_for(&Level::WARN));
|
||||
assert!(!controller.console_enabled_for(&Level::ERROR));
|
||||
assert!(controller.console_enabled_for(&Level::INFO));
|
||||
|
||||
let subscriber = tracing_subscriber::registry().with(ErrorLogLayer::new(controller.clone()));
|
||||
tracing::subscriber::with_default(subscriber, || {
|
||||
tracing::warn!(target: "rust_proxy::tests", "PHP-FPM FastCGI stderr");
|
||||
tracing::error!(target: "rust_proxy::tests", "RustProxy worker panicked");
|
||||
});
|
||||
|
||||
let content = tokio::time::timeout(Duration::from_secs(5), async {
|
||||
loop {
|
||||
if let Ok(content) = tokio::fs::read_to_string(&path).await {
|
||||
if content.lines().count() == 2 {
|
||||
break content;
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("diagnostics were not written to the configured error file");
|
||||
let entries: Vec<Value> = content
|
||||
.lines()
|
||||
.map(|line| serde_json::from_str(line).unwrap())
|
||||
.collect();
|
||||
assert_eq!(entries[0]["level"], "WARN");
|
||||
assert_eq!(entries[1]["level"], "ERROR");
|
||||
|
||||
controller.configure(ErrorLogSettings::default());
|
||||
assert!(controller.console_enabled_for(&Level::WARN));
|
||||
assert!(controller.console_enabled_for(&Level::ERROR));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+29
-1
@@ -273,6 +273,7 @@ fn resolve_installed_data_dir(configured: PathBuf, explicitly_configured: bool)
|
||||
async fn application() -> Result<()> {
|
||||
let error_log = rust_proxy::logging::ErrorLogController::start();
|
||||
rust_proxy::logging::install_error_log_controller(error_log.clone())?;
|
||||
let console_router = error_log.clone();
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "rust_proxy=info,warn".into());
|
||||
tracing_subscriber::registry()
|
||||
@@ -280,10 +281,37 @@ async fn application() -> Result<()> {
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_target(false)
|
||||
.with_writer(rust_proxy::logging::RedactingStderr),
|
||||
.with_writer(rust_proxy::logging::RedactingStderr)
|
||||
.with_filter(tracing_subscriber::filter::filter_fn(move |metadata| {
|
||||
console_router.console_enabled_for(metadata.level())
|
||||
})),
|
||||
)
|
||||
.with(rust_proxy::logging::ErrorLogLayer::new(error_log))
|
||||
.init();
|
||||
// Rust's default panic hook writes directly to stderr, bypassing tracing
|
||||
// and the configured error file. Route worker panics through the same
|
||||
// WARN/ERROR sink as other diagnostics without duplicate journald output.
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
let payload = if let Some(value) = info.payload().downcast_ref::<&str>() {
|
||||
(*value).to_owned()
|
||||
} else if let Some(value) = info.payload().downcast_ref::<String>() {
|
||||
value.clone()
|
||||
} else {
|
||||
"non-string panic payload".to_owned()
|
||||
};
|
||||
let location = info.location().map_or_else(
|
||||
|| "unknown".to_owned(),
|
||||
|place| format!("{}:{}:{}", place.file(), place.line(), place.column()),
|
||||
);
|
||||
let thread = std::thread::current();
|
||||
tracing::error!(
|
||||
target: "rust_proxy::panic",
|
||||
thread = thread.name().unwrap_or("unnamed"),
|
||||
location = %location,
|
||||
panic = %payload,
|
||||
"RustProxy worker panicked"
|
||||
);
|
||||
}));
|
||||
let matches = Cli::command().get_matches();
|
||||
let explicit = |matches: &clap::ArgMatches, id: &str| {
|
||||
matches.value_source(id).is_some_and(|source| {
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::{
|
||||
use anyhow::{ensure, Context, Result};
|
||||
use axum::{body::Body, response::Response};
|
||||
use bytes::Bytes;
|
||||
use futures_util::stream;
|
||||
use futures_util::{stream, StreamExt};
|
||||
use http::{
|
||||
header::{ALLOW, CONTENT_LENGTH, CONTENT_TYPE},
|
||||
HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri,
|
||||
|
||||
@@ -178,9 +178,7 @@ async fn php_response_inner(
|
||||
permit,
|
||||
));
|
||||
let mut response = Response::new(if send_body {
|
||||
Body::from_stream(stream::unfold(rx, |mut rx| async move {
|
||||
rx.recv().await.map(|item| (item, rx))
|
||||
}))
|
||||
Body::from_stream(fastcgi_body_stream(rx))
|
||||
} else {
|
||||
Body::empty()
|
||||
});
|
||||
@@ -189,6 +187,18 @@ async fn php_response_inner(
|
||||
response
|
||||
}
|
||||
|
||||
// tower-http's compression encoder may poll a body again after observing EOF.
|
||||
// An unfused `stream::unfold` panics on that second poll, aborting the response
|
||||
// before the encoder can emit its final gzip/br/zstd bytes.
|
||||
pub(super) fn fastcgi_body_stream(
|
||||
rx: mpsc::Receiver<io::Result<Bytes>>,
|
||||
) -> impl futures_util::Stream<Item = io::Result<Bytes>> {
|
||||
stream::unfold(rx, |mut rx| async move {
|
||||
rx.recv().await.map(|item| (item, rx))
|
||||
})
|
||||
.fuse()
|
||||
}
|
||||
|
||||
pub(super) fn method_not_allowed_response() -> Response {
|
||||
let mut response = status_response(
|
||||
StatusCode::METHOD_NOT_ALLOWED,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::*;
|
||||
use super::fastcgi::{
|
||||
fastcgi_params, parse_cgi_headers, read_fcgi_record, write_fcgi_record, FCGI_END_REQUEST,
|
||||
FCGI_PARAMS, FCGI_STDIN, FCGI_STDOUT,
|
||||
fastcgi_body_stream, fastcgi_params, parse_cgi_headers, read_fcgi_record, write_fcgi_record,
|
||||
FCGI_END_REQUEST, FCGI_PARAMS, FCGI_STDIN, FCGI_STDOUT,
|
||||
};
|
||||
use axum::{body::Body, response::Response};
|
||||
use bytes::Bytes;
|
||||
use futures_util::StreamExt;
|
||||
use http::{
|
||||
header::{
|
||||
ACCEPT_RANGES, ALLOW, CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LENGTH,
|
||||
@@ -15,7 +17,7 @@ use http::{
|
||||
use http_body_util::BodyExt;
|
||||
use std::{collections::BTreeMap, path::Path, time::Duration};
|
||||
use tempfile::TempDir;
|
||||
use tokio::{io::AsyncWriteExt, time::Instant};
|
||||
use tokio::{io::AsyncWriteExt, sync::mpsc, time::Instant};
|
||||
|
||||
fn service(root: &Path, listing: bool) -> CompiledService {
|
||||
Service {
|
||||
|
||||
@@ -20,6 +20,66 @@ async fn fastcgi_fixture(response_body: &'static [u8]) -> std::net::SocketAddr {
|
||||
address
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fastcgi_stream_can_be_polled_repeatedly_after_eof() {
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
tx.send(Ok(Bytes::from_static(b"data"))).await.unwrap();
|
||||
drop(tx);
|
||||
let mut body = Box::pin(fastcgi_body_stream(rx));
|
||||
assert_eq!(body.next().await.unwrap().unwrap(), Bytes::from_static(b"data"));
|
||||
assert!(body.next().await.is_none());
|
||||
// Without `fuse` this panics inside futures-util::stream::unfold.
|
||||
assert!(body.next().await.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn php_fpm_gzip_response_finishes_after_fastcgi_eof() {
|
||||
use std::io::Read;
|
||||
|
||||
let expected = b"FastCGI compressed response regression: 0123456789 0123456789 0123456789";
|
||||
let tmp = TempDir::new().unwrap();
|
||||
std::fs::write(tmp.path().join("index.php"), b"<?php echo 'placeholder';").unwrap();
|
||||
let address = fastcgi_fixture(expected).await;
|
||||
let configured = Service {
|
||||
id: "php-compression-regression".into(),
|
||||
name: "PHP compression regression".into(),
|
||||
document_root: tmp.path().to_path_buf(),
|
||||
index: vec!["index.php".into()],
|
||||
php: Php {
|
||||
enabled: true,
|
||||
endpoint: format!("tcp:{address}"),
|
||||
connect_timeout_ms: 500,
|
||||
request_timeout_ms: 5_000,
|
||||
max_concurrent_requests: 1,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
configured.validate().unwrap();
|
||||
let service = configured.compile().unwrap();
|
||||
let response = serve_request(&service, &request(Method::GET, "/"), "/").await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let mut accept = HeaderMap::new();
|
||||
accept.insert("accept-encoding", "gzip".parse().unwrap());
|
||||
let settings = crate::compression::Settings {
|
||||
gzip: true,
|
||||
br: false,
|
||||
zstd: false,
|
||||
deflate: false,
|
||||
min_bytes: 32,
|
||||
};
|
||||
let response = crate::compression::encode(response, &accept, &Method::GET, &settings).await;
|
||||
assert_eq!(response.headers()[CONTENT_ENCODING], "gzip");
|
||||
|
||||
let compressed = response.into_body().collect().await.unwrap().to_bytes();
|
||||
let mut decoded = Vec::new();
|
||||
flate2::read::GzDecoder::new(compressed.as_ref())
|
||||
.read_to_end(&mut decoded)
|
||||
.unwrap();
|
||||
assert_eq!(decoded.as_slice(), expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn php_fpm_executes_index_and_try_files_front_controller_without_source_disclosure() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
|
||||
@@ -24,7 +24,7 @@ def read_docker_agent_source() -> str:
|
||||
|
||||
preflight(root)
|
||||
manifest = tomllib.loads((root / "Cargo.toml").read_text())
|
||||
assert manifest["package"]["version"] == "0.29.5"
|
||||
assert manifest["package"]["version"] == "0.29.6"
|
||||
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
|
||||
h3dep = manifest["dependencies"]["h3"]
|
||||
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"]
|
||||
@@ -32,14 +32,14 @@ assert "h3-quinn" not in manifest["dependencies"]
|
||||
lock_path = root / "Cargo.lock"
|
||||
if lock_path.exists():
|
||||
lock = lock_path.read_text()
|
||||
assert 'name = "rust-proxy"\nversion = "0.29.5"' in lock
|
||||
assert 'name = "rust-proxy"\nversion = "0.29.6"' in lock
|
||||
assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries"
|
||||
for release_doc, marker in {
|
||||
"README.md": "# RustProxy 0.29.5",
|
||||
"CHANGELOG.md": "## 0.29.5",
|
||||
"ROADMAP.md": "## 0.29.5",
|
||||
"AGENTS.md": "Current source version: 0.29.5.",
|
||||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.29.5",
|
||||
"README.md": "# RustProxy 0.29.6",
|
||||
"CHANGELOG.md": "## 0.29.6",
|
||||
"ROADMAP.md": "## 0.29.6",
|
||||
"AGENTS.md": "Current source version: 0.29.6.",
|
||||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.29.6",
|
||||
}.items():
|
||||
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}"
|
||||
assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
|
||||
@@ -285,7 +285,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa
|
||||
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
|
||||
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
|
||||
assert '"StreamMetrics"' in openapi and '"streams"' in openapi
|
||||
for marker in ['"version": "0.29.5"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
||||
for marker in ['"version": "0.29.6"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
||||
assert marker in openapi, marker
|
||||
for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||
assert marker in openapi, marker
|
||||
@@ -1396,7 +1396,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log"
|
||||
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
|
||||
assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241
|
||||
assert 'location.pathname === "/logging"' in settings_js_0241
|
||||
assert '"RuntimeInformation"' in openapi and '"version": "0.29.5"' in openapi
|
||||
assert '"RuntimeInformation"' in openapi and '"version": "0.29.6"' in openapi
|
||||
|
||||
# 0.24.1 compiler and hostname-cardinality regressions.
|
||||
tls_0241 = rust_source(root, "src/tls.rs")
|
||||
@@ -1635,5 +1635,5 @@ for marker_0282 in [
|
||||
assert 'Zewnętrzne ścieżki plików' in (root / 'langs/pl_pl.json').read_text()
|
||||
assert 'setfacl -m u:rustproxy:--x /etc/ssl/' in (root / 'docs/V0.28.2_IMPLEMENTATION.md').read_text()
|
||||
|
||||
print("PASS: 0.29.5 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v9 migrations.")
|
||||
print("PASS: 0.29.6 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v9 migrations.")
|
||||
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rust-independent documentation/Reader consistency checks for the 0.29.5 release."""
|
||||
"""Rust-independent documentation/Reader consistency checks for the 0.29.6 release."""
|
||||
from pathlib import Path
|
||||
import re
|
||||
from rust_source import rust_source
|
||||
@@ -64,11 +64,11 @@ assert '"/docs/content.json"' in admin
|
||||
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
||||
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
|
||||
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
|
||||
assert readme.startswith("# RustProxy 0.29.5")
|
||||
assert readme.startswith("# RustProxy 0.29.6")
|
||||
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
|
||||
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
|
||||
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
|
||||
assert roadmap.startswith("# RustProxy roadmap") and "## 0.29.5" and "## 0.29.1" in roadmap and "## 0.29.0" in roadmap and "## 0.28.2" in roadmap and "## 0.28.1" in roadmap and "## 0.27.3" in roadmap and "## 0.27.2" in roadmap and "## 0.27.1" in roadmap and "## 0.27.0" in roadmap and "## 0.26.7" in roadmap and "## 0.26.6" in roadmap and "## 0.26.5" in roadmap and "## 0.26.4" in roadmap and "## 0.26.0" in roadmap and "## 0.25.5" in roadmap and "## 0.25.4" in roadmap and "## 0.25.3" in roadmap and "## 0.25.2" in roadmap and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap
|
||||
assert roadmap.startswith("# RustProxy roadmap") and "## 0.29.6" and "## 0.29.1" in roadmap and "## 0.29.0" in roadmap and "## 0.28.2" in roadmap and "## 0.28.1" in roadmap and "## 0.27.3" in roadmap and "## 0.27.2" in roadmap and "## 0.27.1" in roadmap and "## 0.27.0" in roadmap and "## 0.26.7" in roadmap and "## 0.26.6" in roadmap and "## 0.26.5" in roadmap and "## 0.26.4" in roadmap and "## 0.26.0" in roadmap and "## 0.25.5" in roadmap and "## 0.25.4" in roadmap and "## 0.25.3" in roadmap and "## 0.25.2" in roadmap and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap
|
||||
assert "## 0.24." not in roadmap and "## Web Server follow-up" in roadmap and "docs/ROADMAP_HISTORY.md" in roadmap
|
||||
assert changelog.startswith("# Changelog\n")
|
||||
for release in ["0.29.1", "0.29.0", "0.28.2", "0.28.1", "0.28.0", "0.27.3", "0.27.2", "0.27.1", "0.27.0", "0.26.7", "0.26.6", "0.26.5", "0.26.4", "0.26.1", "0.26.0", "0.25.6", "0.25.5", "0.25.4", "0.25.3"]:
|
||||
|
||||
@@ -75,10 +75,10 @@ def check(root: Path, repair_known: bool = False) -> list[str]:
|
||||
raise ValueError(f"Conflicting Rust modules: {flat.relative_to(root)} and {module.relative_to(root)}")
|
||||
for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/config/internal_routing.rs", "src/proxy/websocket.rs", "src/proxy/error_fallback.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/vhost/error_fallback.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/admin/web/drafts.js", "src/pages.rs", "tools/dev/mock_server.py", "tools/dev/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.5_AUDIT.md", "docs/V0.25.4_AUDIT.md", "docs/V0.25.3_AUDIT.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "tools/checks/docs_check.py", "tools/checks/web_server_contracts.py", "tools/checks/rust_source.py", "tools/checks/rust_module_contracts.py", "tools/checks/source_module_contracts.py", "scripts/runtime/acme_provider.py", "src/basic_auth.rs", "scripts/deploy/deploy.py", "scripts/deploy/deploy_core.py", "scripts/deploy/deploy_runtime.py", "scripts/migration/migrate_npm.py", "scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/build/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json"]:
|
||||
if not (root / path).is_file():
|
||||
raise ValueError(f"Incomplete RustProxy 0.29.5 source tree: missing {path}")
|
||||
raise ValueError(f"Incomplete RustProxy 0.29.6 source tree: missing {path}")
|
||||
text = (root / "src/lib.rs").read_text(encoding="utf-8")
|
||||
if '#[path = "auth/mod.rs"]' not in text:
|
||||
raise ValueError("src/lib.rs is not the 0.29.5 entry point; extract the complete package, not individual files.")
|
||||
raise ValueError("src/lib.rs is not the 0.29.6 entry point; extract the complete package, not individual files.")
|
||||
return messages
|
||||
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ def upgrade_smoke(tmp: Path) -> None:
|
||||
assert backup.is_dir()
|
||||
assert (backup / "src/obsolete.rs").is_file()
|
||||
assert not (target / "src/obsolete.rs").exists()
|
||||
assert 'version = "0.29.5"' in (target / "Cargo.toml").read_text(encoding="utf-8")
|
||||
assert 'version = "0.29.6"' in (target / "Cargo.toml").read_text(encoding="utf-8")
|
||||
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
|
||||
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
|
||||
assert (target / "langs/site.json").is_file()
|
||||
@@ -146,7 +146,7 @@ def upgrade_smoke(tmp: Path) -> None:
|
||||
assert (target / "docker-agent/certificate.go").is_file()
|
||||
assert (target / "docker-agent/server.go").is_file()
|
||||
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
|
||||
assert manifest["to_version"] == "0.29.5"
|
||||
assert manifest["to_version"] == "0.29.6"
|
||||
assert manifest["removed_obsolete_runtime_state"] is True
|
||||
assert not legacy.exists()
|
||||
assert not (backup / "legacy-data").exists()
|
||||
@@ -619,7 +619,7 @@ def source_contract_smoke() -> None:
|
||||
assert marker in admin, marker
|
||||
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
|
||||
assert '"/api/status"' not in anonymous_block
|
||||
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.29.5"' in openapi_0241
|
||||
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.29.6"' in openapi_0241
|
||||
assert 'id="panel-logging"' not in html
|
||||
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
|
||||
assert marker in html, marker
|
||||
@@ -1109,7 +1109,7 @@ def source_contract_smoke() -> None:
|
||||
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
|
||||
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
|
||||
assert '"/api/config/history/restore"' in openapi
|
||||
for marker in ['"version": "0.29.5"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||
for marker in ['"version": "0.29.6"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||
assert marker in openapi, marker
|
||||
assert '"software_update"' in openapi and '"update_available"' in openapi
|
||||
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
|
||||
@@ -1314,7 +1314,7 @@ def main() -> None:
|
||||
source_contract_smoke()
|
||||
container_contract_smoke()
|
||||
grafana_contract_smoke()
|
||||
print("PASS: 0.29.5 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
|
||||
print("PASS: 0.29.6 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user