This commit is contained in:
Mateusz Gruszczyński committed 2026-10-03 18:53:51 +02:00
1 parent d3f0f060bf
commit 2e4aa4df4d
33 files changed
+287 -149

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes. 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 The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
compatibility. Current source version: 0.24.9. compatibility. Current source version: 0.24.10.
## Delivery ## Delivery
+11
View File
@@ -1,7 +1,18 @@
# Changelog # Changelog
## 0.24.10
- Keep Error File inside the existing revisioned `logging` configuration section while retaining its own Settings form; both access-log and error-file forms save atomically through `/api/config/logging`, so enabled/path/format persist without introducing a special endpoint.
- Preserve backward compatibility for historical `/api/config/logging` clients that send only the access-logging object.
- Keep the Error File panel free of the redundant ERROR/logrotate help sentence.
- Remove collapsing from Response time history and add an accessible information control explaining the cumulative mean metric.
- Update package/source/UI/OpenAPI/release identity to 0.24.10.
## 0.24.9 ## 0.24.9
- Added composite Settings > Logging support for `error_log.enabled`, path and format while retaining legacy `/api/config/logging` payload compatibility.
- Removed the Error Log help sentence from the panel while keeping the existing controls and behavior unchanged.
- Fix Rust type inference for zeroizing ACME/Base64 and AEAD buffers so the new Secret Store and encrypted backup code compile with the pinned dependency set. - Fix Rust type inference for zeroizing ACME/Base64 and AEAD buffers so the new Secret Store and encrypted backup code compile with the pinned dependency set.
- Fix the secret-redaction KV regex literal so 0.24.9 compiles correctly; the redaction behavior itself is unchanged. - Fix the secret-redaction KV regex literal so 0.24.9 compiles correctly; the redaction behavior itself is unchanged.
- Harden the built-in release checker for FreeBSD/OPNsense and other Unix platforms: supplement bundled Mozilla roots with common system CA bundles/`SSL_CERT_FILE`/`CURL_CA_BUNDLE`, follow bounded non-downgrading HTTP(S) redirects, and report failed checks as normal application `ERROR` events without changing dashboard behavior. - Harden the built-in release checker for FreeBSD/OPNsense and other Unix platforms: supplement bundled Mozilla roots with common system CA bundles/`SSL_CERT_FILE`/`CURL_CA_BUNDLE`, follow bounded non-downgrading HTTP(S) redirects, and report failed checks as normal application `ERROR` events without changing dashboard behavior.
Generated
+1 -1
View File
@@ -1688,7 +1688,7 @@ dependencies = [
[[package]] [[package]]
name = "rust-proxy" name = "rust-proxy"
version = "0.24.9" version = "0.24.10"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "rust-proxy" name = "rust-proxy"
version = "0.24.9" version = "0.24.10"
edition = "2021" edition = "2021"
rust-version = "1.88" rust-version = "1.88"
description = "RustProxy - independent reverse proxy with local administration" description = "RustProxy - independent reverse proxy with local administration"
+9 -2
View File
@@ -1,9 +1,9 @@
# RustProxy 0.24.9 # RustProxy 0.24.10
Independent Rust reverse proxy with a dashboard, SQLite configuration, Independent Rust reverse proxy with a dashboard, SQLite configuration,
streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools. streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools.
**0.24.9 release source.** **0.24.10 release source.**
## Layer-4 streams ## Layer-4 streams
@@ -11,6 +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. Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners.
## What changed in 0.24.10
- Make Error File a first-class revisioned Settings section so enable/path/format persist through the same section-save pattern as the rest of the panel, including across SQLite reopen/restart.
- Keep Error File independent from structured access logging in the UI and remove the redundant ERROR/logrotate help sentence.
- Keep response-time history permanently expanded and add the same compact information control used by Process, explaining that the chart is the sampled cumulative mean of completed HTTP exchanges.
- Update package/source/UI/OpenAPI/release identity to 0.24.10.
## What changed in 0.24.9 ## What changed in 0.24.9
- Add a central encrypted Secret Store backed by SQLite ciphertext and a private local master key, with migration of Cloudflare, OVH and RFC2136 ACME credentials away from persistent plaintext files. - Add a central encrypted Secret Store backed by SQLite ciphertext and a private local master key, with migration of Cloudflare, OVH and RFC2136 ACME credentials away from persistent plaintext files.
+10
View File
@@ -1,5 +1,15 @@
# RustProxy roadmap # RustProxy roadmap
## 0.24.10
Current direction: 0.24.9 -> 0.24.10
- [x] Make Error File a first-class revisioned configuration section and persist enabled/path/format across database reopen.
- [x] Remove the redundant Error File help sentence while retaining JSON/RAW controls and live apply.
- [x] Keep response-time history permanently expanded and add an accessible information popover matching Process.
- [x] Extend UI/API/source regressions and update release identity to 0.24.10.
- [ ] Run compiler-enabled `./dev.sh fmt`, `./dev.sh check`, `./dev.sh test` and browser acceptance on the release host.
## 0.24.9 ## 0.24.9
Current direction: 0.24.8 -> 0.24.9 Current direction: 0.24.8 -> 0.24.9
+32 -32
View File
@@ -1,19 +1,19 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
60d7b6e6599269c73bb901383fc979be2403d41dbea221ff3100def26161baa3 AGENTS.md 5e5cd5ee6792de542f5560c9ae055acfcf0946c0cb401356af2e1af4fad05815 AGENTS.md
837e5d2bbbd973ce32b86db4b444fa677cc1f770d9d0f1b197b2d6c1a9f64af8 CHANGELOG.md 626cc6d911837a56d7c4c1e713737f250c163bb2074fd30e31a16445908496c5 CHANGELOG.md
31147611b139a69c531fbdafcbe53e3331c7afbcfcab1167ebbca8b24b5bc4cb Cargo.lock a5fd3e8b7570c9b2453aacb5f1b8c3d1bfcc6a503dc520f6488691c43ccb71ae Cargo.lock
5928784bf7f69a9b21f2420d63a82986ae03a08998ceca6bae1900fda16804a2 Cargo.toml 9a1750d83b70f10817fb83ca504efaa56493ebd077a860bd12a299e33e2ac4c2 Cargo.toml
d6d22ba63f4bc3d16a13012ecaabff8a156be57c824d2f20f66976502fb27d1f Dockerfile d6d22ba63f4bc3d16a13012ecaabff8a156be57c824d2f20f66976502fb27d1f Dockerfile
48fc6b2362af7db0cb5a5b60a0883d4ec05ba26c1936685c1884651f5b8fca11 README.md 2deab152cd6c7eb471ca90ed3ff45b1cf155368a92993058a87ca5c8ee1f727c README.md
219fe5a950e382f785054f697fe68d9a9754ab8c5490567c52c555fc5a45732c ROADMAP.md 493c8926d7ebc6400840b808b341ca4aa62518a7e9ddf1303760cefbefdee483 ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
a55fafd4c9fc17f8af591407e7f86d222ce06658c64dcffda92acbf420f969f1 build.rs a55fafd4c9fc17f8af591407e7f86d222ce06658c64dcffda92acbf420f969f1 build.rs
cd246a44d67a769039183b59da238784d946cdfb5df843ee3464bf1c7dedf0db debian/README.md 0dda9a6e56a72a5a8e8c4a54fe1f3a3577e51365b702aa796612f98da04911c3 debian/README.md
299ed18788309061dcc69b1ba56a7ca7a43bf9e16d31f3ab4c50414d1e612c0a debian/build-package.sh d4ea44cc5357c5084ea85dcf2e8d945cd63cb09e5b4636cb22e035d48b609b28 debian/build-package.sh
0763a4bd8b429f7922b23a7276ff694f3a751b83fb127f6ad7ec654e5e507ecd debian/build-repository.sh 0763a4bd8b429f7922b23a7276ff694f3a751b83fb127f6ad7ec654e5e507ecd debian/build-repository.sh
8f0f9ab5368f2dc6577ca5bf8de42375cc97630994f89ac0552bec8a5cbb695f debian/changelog 326545e4a7f99d29da0e7c1d7bdfff3022c20b273244396d441bee034f687f16 debian/changelog
c90457b19b1b2f7998d3db59a656d215eb0fab23769197405960457b66d95cf7 debian/control c90457b19b1b2f7998d3db59a656d215eb0fab23769197405960457b66d95cf7 debian/control
dff2ecb9c14623416981f444bc6df1c4e2bf55d330260f36a312d8d42245400f debian/rules dff2ecb9c14623416981f444bc6df1c4e2bf55d330260f36a312d8d42245400f debian/rules
c537d54fd92a9b97c5f6e6ec63bc1276cda324513e2946b341b2193f63b62e6a debian/rustproxy.environment c537d54fd92a9b97c5f6e6ec63bc1276cda324513e2946b341b2193f63b62e6a debian/rustproxy.environment
@@ -24,18 +24,18 @@ d1c483406fb53674ed8db5f30de599b5a91fa1c6b28242eb70a64d6a736430bb debian/rustpro
e8e18df40bcd69d6aa404282679186d9b8256ac63a5b4e80d7f081e8a0095a2e debian/source/format e8e18df40bcd69d6aa404282679186d9b8256ac63a5b4e80d7f081e8a0095a2e debian/source/format
57d737d73b828bf763b210c8ddea39d7a63224f8d509a518d0b876ba2e6a805a deploy/README.md 57d737d73b828bf763b210c8ddea39d7a63224f8d509a518d0b876ba2e6a805a deploy/README.md
50329bd9d6980fb1c90a5cb29966b19a257905791d1d07d185d6bd4e3b0bcb1a deploy/rustproxy.service 50329bd9d6980fb1c90a5cb29966b19a257905791d1d07d185d6bd4e3b0bcb1a deploy/rustproxy.service
3096d5956723ccdeb09ba0c032057a1d40045f1ea98e92669a7f96ca3f0be48d dev.sh da0d07970dc59f11ddd9007508c208e3f1d787ee3349cd3bff02467d68ae9584 dev.sh
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md 5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example 5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
9e9f3f17f38f9b0730f598f65988ce8ef562d2182ef6778fc7292b990e9cf22f docker-agent/go.mod 9e9f3f17f38f9b0730f598f65988ce8ef562d2182ef6778fc7292b990e9cf22f docker-agent/go.mod
9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh 9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh
4902616a3763de27568529da910be7cc9d544c92cb36929bae098d5991035143 docker-agent/main.go b42833df3bfc0b842368e174653871b4bac4105a853c39af2cd8f9186fdfc1aa docker-agent/main.go
3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go 3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go
634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service 634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service
0dbacca69143bb1e38d74270e24562a6749e387b26ef869643e876df6e0b1edb docs/ACME_PROVIDERS.md 0dbacca69143bb1e38d74270e24562a6749e387b26ef869643e876df6e0b1edb docs/ACME_PROVIDERS.md
b794dc1ba8519c0fa77f30cb52a2cf85b1b28bfe7ae51dee27ba2ddeda07764a docs/ACME_RFC2136.md b794dc1ba8519c0fa77f30cb52a2cf85b1b28bfe7ae51dee27ba2ddeda07764a docs/ACME_RFC2136.md
3bfc98b55e7e905250c4ded38d18ca064c6006ae9232acfa5d48517d0da14f5b docs/API.md 3bfc98b55e7e905250c4ded38d18ca064c6006ae9232acfa5d48517d0da14f5b docs/API.md
2253520f5dc5a365f4a69c38308ea4c8c58f65b16cc06e83e192754791de49a7 docs/API_ACCESS.md de7753cc5dcd5f2c6c909284e3002a6657c7b25043e0c0a24aeeafab29b00ce2 docs/API_ACCESS.md
dde6a6a0b99ac34e65fe80e5eb8aebc2bc852bb97eb5c13a3c1ab538ef37f5a0 docs/ARCHITECTURE.md dde6a6a0b99ac34e65fe80e5eb8aebc2bc852bb97eb5c13a3c1ab538ef37f5a0 docs/ARCHITECTURE.md
2a1a6b8e5783ef4a25fc7933b4faedfde44557a827782a6a900d448163d81df7 docs/CACHE.md 2a1a6b8e5783ef4a25fc7933b4faedfde44557a827782a6a900d448163d81df7 docs/CACHE.md
0450813e3c875371d75cc41835f6362abb5300cd4bdf545f3011d9df8101170c docs/CACHE_DIAGNOSTICS.md 0450813e3c875371d75cc41835f6362abb5300cd4bdf545f3011d9df8101170c docs/CACHE_DIAGNOSTICS.md
@@ -44,7 +44,7 @@ c822cd1d6a055d5ddec06d4f6077f0b13837dfb25ede1ec1f4e9a52e182db946 docs/CUSTOM_LO
7db65a96f696aff1d7f74e2041cfc6ecdf437b1412ece90cee26da1ceff4cf7e docs/DOCKER_DISCOVERY.md 7db65a96f696aff1d7f74e2041cfc6ecdf437b1412ece90cee26da1ceff4cf7e docs/DOCKER_DISCOVERY.md
9a882f07945b207d032936feb843f29467e4654f8255fa0104d988a54987f982 docs/ERROR_LOGGING.md 9a882f07945b207d032936feb843f29467e4654f8255fa0104d988a54987f982 docs/ERROR_LOGGING.md
061e1ccee4f3e70126a27ba8bdbac10e895fdb4e6e4607c5c6a6ab1d164baf02 docs/HTTP3.md 061e1ccee4f3e70126a27ba8bdbac10e895fdb4e6e4607c5c6a6ab1d164baf02 docs/HTTP3.md
4deafcc78fdad53cb345e195d4a514fe7a6cf3d0c5c65d3be5e61f0b86f38cec docs/INSTALL.md 4d9cfb599663c5e7fe1741f2bcbea09f9c86609337e1901f9a0154f3a5b7013b docs/INSTALL.md
0a124bd3eafaa9c6b3bc2b811fdc3e6295a6a2978766c37cc0735c27f4cf6d59 docs/INTEGRATIONS_040.md 0a124bd3eafaa9c6b3bc2b811fdc3e6295a6a2978766c37cc0735c27f4cf6d59 docs/INTEGRATIONS_040.md
2baaa1d34979c33caead9b271a7d235c673acb4e188c94917d975c25c179c242 docs/LIVE_LOGS.md 2baaa1d34979c33caead9b271a7d235c673acb4e188c94917d975c25c179c242 docs/LIVE_LOGS.md
4e1504d1a343b94c2252545cbd5248af79f158c4566754dfe11f3c15a19ec0da docs/MANAGEMENT.md 4e1504d1a343b94c2252545cbd5248af79f158c4566754dfe11f3c15a19ec0da docs/MANAGEMENT.md
@@ -63,7 +63,7 @@ c6fa646500c0acac18bff6349cbb1d821680a849c8648b4eaa64f2a4305f9b9d docs/SECURITY.
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md 620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
fce52cc908fb86cab16072c0297b6cdaf4857e84185783bc96240d9a6bc321fd docs/SETTINGS.md fce52cc908fb86cab16072c0297b6cdaf4857e84185783bc96240d9a6bc321fd docs/SETTINGS.md
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md 3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
4396f0ee6b5c124aef2869eab802ecb221f3cdc4efc1cb1536484b392a8d0e32 docs/TEST_REPORT.md 88ac6d341b6ee657d9ad12afbd2e6660b9594aaf4d1b3d407c0efeee39097154 docs/TEST_REPORT.md
8ee6187a5460ca45643b7fd789e27bffd264449e3a92ff70e9359edee011bbbb docs/TRANSFORM.md 8ee6187a5460ca45643b7fd789e27bffd264449e3a92ff70e9359edee011bbbb docs/TRANSFORM.md
b02957b708174c9def830ced3c70c943a47af7a33467ef858255d04ab92c4af0 docs/TRANSPORT.md b02957b708174c9def830ced3c70c943a47af7a33467ef858255d04ab92c4af0 docs/TRANSPORT.md
96a6816915d526f00e261846131ee3f942ef332af996cf0f73c79ec1ba9f9c7a docs/V0.21.0_AUDIT.md 96a6816915d526f00e261846131ee3f942ef332af996cf0f73c79ec1ba9f9c7a docs/V0.21.0_AUDIT.md
@@ -97,13 +97,13 @@ b26833cb4251f9390a44192ecc827ff7f052fd19016a5ccf7498c94503ab4ce3 examples/mock-
17e7971fa9d9b141019fba3135d87cb1aa0cbc55d56b0718f879f880b615da26 examples/vhosts-balanced.json 17e7971fa9d9b141019fba3135d87cb1aa0cbc55d56b0718f879f880b615da26 examples/vhosts-balanced.json
e06905b6363103b1bcee1a09b9e02292033f114d88f3edbea22ddab53e9e5071 examples/vhosts-private.json e06905b6363103b1bcee1a09b9e02292033f114d88f3edbea22ddab53e9e5071 examples/vhosts-private.json
529b47c6d4ea44d2a25d1cf9561cb5ee1e05f50a568c6dba5889741bcf936434 grafana/README.md 529b47c6d4ea44d2a25d1cf9561cb5ee1e05f50a568c6dba5889741bcf936434 grafana/README.md
17f659f505bd6f06c98ac3778ddf5ad23f7326b99c633289205fd7620c14a0c8 grafana/rustproxy-overview.json bb3777d1f5b9a240a5dc409ac9f22d2c570b2b5632e446d11d313ac26393a9a2 grafana/rustproxy-overview.json
f45e5cc3d0c253534978d4b752b0667e2a7906ccf5a22106400e0f6b70b00d23 install-debian13.sh f45e5cc3d0c253534978d4b752b0667e2a7906ccf5a22106400e0f6b70b00d23 install-debian13.sh
858b39fa6886b71d20690bf369e76580922364b3cc7f0cf6ba9b040df9cd76e3 install.sh 858b39fa6886b71d20690bf369e76580922364b3cc7f0cf6ba9b040df9cd76e3 install.sh
29cada0936762f26495064d1524a668262ed729b21f20aebddc8b6e5cfea363b langs/README.md 29cada0936762f26495064d1524a668262ed729b21f20aebddc8b6e5cfea363b langs/README.md
edcbe1fb0385038f94ad58e55923dd32e8c71073f526f7af182abc59dddfe8c0 langs/en_US.json 11bcdc0788c43fb111aa8744ff0109b4b34c01f3ba688d7559828d63a9fa480b langs/en_US.json
e99a9f1744a45e946c825814e9ba4c75288309a83f5cc56100c3f2c1ba6f4134 langs/pl_pl.json e5179fa079d8030eb6b542a5851d3a4546922e94f77d969809e269ccbdf75b27 langs/pl_pl.json
bd1aedf3a88329cee660faaebac82a2d74e32fccaf92d4529799ab6ec7abe321 make_zip.py 59dce34d618dfcb4b5f56aee7d8f888bdfc6dcb25165c1a53a32a09edab496e2 make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
5c62da30fa9db594a4d656c77ae676d3c388072b6a85f91170ec7d911d7cde43 migrations/0004_admin_preferences.sql 5c62da30fa9db594a4d656c77ae676d3c388072b6a85f91170ec7d911d7cde43 migrations/0004_admin_preferences.sql
@@ -121,8 +121,8 @@ edb6ee1445b01cd6f3a74a3a607f39fb107d411b49df028d2e0f4679e093de31 scripts/acme_p
e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/acme_rfc2136.py e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/acme_rfc2136.py
ea2c670aa1fc2c145fb3666711642d526c2adb01537fc3b6398664e33136f0e4 scripts/admin_audit.py ea2c670aa1fc2c145fb3666711642d526c2adb01537fc3b6398664e33136f0e4 scripts/admin_audit.py
20e83f8b6347b819d1d1ea3de5c691c36f0816954a4210b0d3f39557fefe1931 scripts/backup.py 20e83f8b6347b819d1d1ea3de5c691c36f0816954a4210b0d3f39557fefe1931 scripts/backup.py
2ca3da5473554bf33555148a69ef81bcd690674e1de0deb0312ca096143b2398 scripts/check_source.py 8a820c8097a0ca7925e6fc7f3a44e229dc7ca38861bbe5470f60c465906ee20e scripts/check_source.py
e19eba9a8b8a0a8eb26b1e70a78fdfea2661be3a32b3f769fe8129dc81b7aaf5 scripts/deploy.py edb44b8eb4216262881a7045e11ec16fb8f7b8da1111d365118cde00d94e3f36 scripts/deploy.py
6facb2d65bfab234f09855097d12da8f72669a38ebceb4b1cbd03ab42b8d2469 scripts/langs_check.py 6facb2d65bfab234f09855097d12da8f72669a38ebceb4b1cbd03ab42b8d2469 scripts/langs_check.py
9f89ccdcd31da0f203113051a109ada8e51b44f91382ee2c5c75228d1e456540 scripts/memory_contracts.py 9f89ccdcd31da0f203113051a109ada8e51b44f91382ee2c5c75228d1e456540 scripts/memory_contracts.py
60504dd426195fca88d77e075d607c04540dbe8aae2cd806077b3818e0e0cf08 scripts/migrate_npm.py 60504dd426195fca88d77e075d607c04540dbe8aae2cd806077b3818e0e0cf08 scripts/migrate_npm.py
@@ -130,26 +130,26 @@ d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a scripts/mock_a
2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 scripts/mock_server.py 2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 scripts/mock_server.py
2d4c859aae5ba357a6d30024c8487ae8f59f01b192711f13f9c2f8fee0f48b50 scripts/os-acme-client/README.md 2d4c859aae5ba357a6d30024c8487ae8f59f01b192711f13f9c2f8fee0f48b50 scripts/os-acme-client/README.md
c73b814a42bb4ff703441dbd177ee625055eeb115f9b5bb0c2150aede573a159 scripts/os-acme-client/opnsense-rustproxy-cert-deploy.sh c73b814a42bb4ff703441dbd177ee625055eeb115f9b5bb0c2150aede573a159 scripts/os-acme-client/opnsense-rustproxy-cert-deploy.sh
a2b9cf7f003111f6b71adf2fe79853bb48d91a3eef53fb744e6c5ba8e6171eed scripts/preflight.py c23284f95deb4c8533d8c11d528c10efd19f8750b84396b124eff1037e9c008b scripts/preflight.py
ed4a944f5c25a9cf6e2be014e455fc8fd2167cd445696c869ef8acebf66983ef scripts/release_smoke.py 5869b1bc6fa652b041bfc1d87b8f8211aa29a85356f5dd1d4500fed15047d089 scripts/release_smoke.py
c37847ae9663197d306ea55eadbe4a1892d61d031ced923a8eeffc1ae876bb4b scripts/routeros-oci-build/.env c37847ae9663197d306ea55eadbe4a1892d61d031ced923a8eeffc1ae876bb4b scripts/routeros-oci-build/.env
45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 scripts/routeros-oci-build/.env.routeros.example 45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 scripts/routeros-oci-build/.env.routeros.example
3b48cff8506e5a6ae7d3876f47721f5de7d63e2e02264a78cf5b6ddd52d9fbc6 scripts/routeros-oci-build/Dockerfile.routeros 3b48cff8506e5a6ae7d3876f47721f5de7d63e2e02264a78cf5b6ddd52d9fbc6 scripts/routeros-oci-build/Dockerfile.routeros
7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e scripts/routeros-oci-build/Dockerfile.routeros.dockerignore 7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e scripts/routeros-oci-build/Dockerfile.routeros.dockerignore
4578227b1734ad31f40837232c3d3c4c5923a896e05c426e887c37f323686488 scripts/routeros-oci-build/README.md 5511e4aba66bd484e9fd5f811e81f007a6d38a4db261eb551f3df6be28ee8fbe scripts/routeros-oci-build/README.md
d53b2ae7315ac143bd17d6327611a39e7c23666408bb95e39da2ca8517322ccb scripts/routeros-oci-build/build-routeros-oci.sh d53b2ae7315ac143bd17d6327611a39e7c23666408bb95e39da2ca8517322ccb scripts/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a scripts/routeros-oci-build/docker-entrypoint.sh 3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a scripts/routeros-oci-build/docker-entrypoint.sh
c9be0bb829f45220653cf320b7466a567dd0745f511880eb6c27a3552c52e08b scripts/swagger_assets.py 7eed5d7fa012c21ff2b8785dfa9cb087aec0e304023fe6aa1254f419007f24d5 scripts/swagger_assets.py
102b23de19a88a8e60de392fb83aefd3dfb33e90cc97ed1690d9f3d4a33d8866 scripts/test_compression.py 102b23de19a88a8e60de392fb83aefd3dfb33e90cc97ed1690d9f3d4a33d8866 scripts/test_compression.py
94b96941648424f44c86d3e7c6225c9afc1d91a13e2755b154591a2b9e950013 scripts/upgrade.py 496c4495567386105f80f8f76356527efd5e97ceadd71e55a2bf4e1244bd5a78 scripts/upgrade.py
081aad397b2b9485e20b684dc11682d3a16464c5b4f110cd461f8518eb74ad99 src/acl.rs 081aad397b2b9485e20b684dc11682d3a16464c5b4f110cd461f8518eb74ad99 src/acl.rs
4213236dc8317667313fb028fa0536ce957ccc6210bb364913f82730517a15cd src/acme.rs 4213236dc8317667313fb028fa0536ce957ccc6210bb364913f82730517a15cd src/acme.rs
fdba60fa997aa18cb0a7ef50e286a8b6fd877d14fe829d4b0bcb5c7c269a5ae6 src/admin/documentation.rs fdba60fa997aa18cb0a7ef50e286a8b6fd877d14fe829d4b0bcb5c7c269a5ae6 src/admin/documentation.rs
9457c2e3557df929da1118daab792373fffeb10ac5b71acf294bae5a2ed0f6e2 src/admin/mod.rs 0c2fd4ae9ea7e009ee7164d1fd7189639be81fb0ad185b156bb7e76b69cee779 src/admin/mod.rs
358b21548909037c016356e9e2a7104fb175bdf7cd1ccf45cb0d8f5dc2c3b4c6 src/admin/openapi.json b8ed13ab5cfbda58fc01032c530fd8f592d2d62bd73ce42a0752128f1ca9d19a src/admin/openapi.json
b4fa393b780b2676b5d72e0f90cd7290292f424b8062d4b5983e722d6ca72c32 src/admin/public.rs b4fa393b780b2676b5d72e0f90cd7290292f424b8062d4b5983e722d6ca72c32 src/admin/public.rs
23c55f23deeec844a9abc2289440c9e0583aaeb72416fc5f3632a43a864c0efd src/admin/web/acme.js 23c55f23deeec844a9abc2289440c9e0583aaeb72416fc5f3632a43a864c0efd src/admin/web/acme.js
70ce2072b5216cfe8e556343273714516bf503e7dcd53fae9b32fbff37008a2c src/admin/web/app.js d783992e23073381578a45a029b437c9e4be9912f4289120c9b26c752a9c76cc src/admin/web/app.js
d0be71555de13f732fd6ee44438a0110eb08c91e78bbc1aa36a1a84182780210 src/admin/web/cache-panel.js d0be71555de13f732fd6ee44438a0110eb08c91e78bbc1aa36a1a84182780210 src/admin/web/cache-panel.js
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html 50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
3b84a7a08f9f68e2c78fed4523b594c31a251addc5480a69c5f6949908230539 src/admin/web/docker.js 3b84a7a08f9f68e2c78fed4523b594c31a251addc5480a69c5f6949908230539 src/admin/web/docker.js
@@ -164,7 +164,7 @@ f7d0955ab18c15e04369204e789f341d2e832f927c1ff20a3b2b9e35e7898f00 src/admin/web/
e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/error_504.html e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/error_504.html
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
dee3b113907f77138cdf919efdfb097e45b889cff3893bfc6f02ed998643bbae src/admin/web/host404.js dee3b113907f77138cdf919efdfb097e45b889cff3893bfc6f02ed998643bbae src/admin/web/host404.js
e8490ac3eaf5cd59f063f7a194e38bbc4cfcf89f7ee7c900fbbac4931f642558 src/admin/web/index.html 6bfed2d4a2228a0a7f0b37b3ccbb3833ec8359b607e392a8e63c40d26e5c63bb src/admin/web/index.html
24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js 24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js
cdab0c11a28deebdf401c0453fbc76b675e9e643722106fb20e812bb9648ccde src/admin/web/login-keys.json cdab0c11a28deebdf401c0453fbc76b675e9e643722106fb20e812bb9648ccde src/admin/web/login-keys.json
a253d202dc9a147f2add34c64e0f35c38fdc7f667f9908933ce5f959b1a92a2e src/admin/web/login.html a253d202dc9a147f2add34c64e0f35c38fdc7f667f9908933ce5f959b1a92a2e src/admin/web/login.html
@@ -173,9 +173,9 @@ e41619e5cb0ba01a3d287748f28eb38bbe7641f9bedade252d286c18e619c400 src/admin/web/
22318f39f7bb1ae98e65737d3c2ad9e63065b8e94ad59bed560c0e80d3a46949 src/admin/web/redirects.js 22318f39f7bb1ae98e65737d3c2ad9e63065b8e94ad59bed560c0e80d3a46949 src/admin/web/redirects.js
dafc8b023f334091c7b5a04920038bce46ea3ef4b068e1f88cffe49d44eaeeee src/admin/web/settings.js dafc8b023f334091c7b5a04920038bce46ea3ef4b068e1f88cffe49d44eaeeee src/admin/web/settings.js
1a30affd6a717095a1763998a5850a79ae0f86b7b694806f2aba2b880735dd1a src/admin/web/streams.js 1a30affd6a717095a1763998a5850a79ae0f86b7b694806f2aba2b880735dd1a src/admin/web/streams.js
aad4a2f55f750c26ab98f125a8f7712b365e6c993cf4ee9a3636d05a6a0b41b6 src/admin/web/style.css adb20e30084ec658f4cce136261a2d79724c9f3ca99144f5cc12d67f4035166a src/admin/web/style.css
049be02e0f68c3f6702f3bf29be760c4e5bc1199393ed198107c0a264711300d src/admin/web/transform-panel.js 049be02e0f68c3f6702f3bf29be760c4e5bc1199393ed198107c0a264711300d src/admin/web/transform-panel.js
64f14f2be913d9c01efec7a3c00dfd081daf5f5a1d76a48ac9c40d80d0409215 src/admin/web/vhosts.js 681f03273d821a710944ec89cb7319bf7debce2d3a68eb6e93db17db1f44feb1 src/admin/web/vhosts.js
1e5c6c6157f8b66d90751a0480cb90c6fdfa229330fc4c3f3504bc0c689c3d60 src/auth/files.rs 1e5c6c6157f8b66d90751a0480cb90c6fdfa229330fc4c3f3504bc0c689c3d60 src/auth/files.rs
606f5491f94da007881a679051677a4609881d3151ac753de02556e0e76e1ce8 src/auth/mod.rs 606f5491f94da007881a679051677a4609881d3151ac753de02556e0e76e1ce8 src/auth/mod.rs
1ceb57d97cbff655f185e126d98b8dc7884c7aac865e1f70e3c6d199685c13a7 src/auth/store.rs 1ceb57d97cbff655f185e126d98b8dc7884c7aac865e1f70e3c6d199685c13a7 src/auth/store.rs
@@ -207,7 +207,7 @@ eff4484fd9630009471f824c57c5c74f3d3c5378f9e46a3bdda03ede37daec82 src/geoip.rs
2f6dd8c74fe1d22a8fa4ec5a21d0d334a667ede1b11629541f4802252765b21b src/http3.rs 2f6dd8c74fe1d22a8fa4ec5a21d0d334a667ede1b11629541f4802252765b21b src/http3.rs
95bd6977db93be728b2597e942d9200e47474d6e0c7c78584df8c8bae6a1f9c7 src/lib.rs 95bd6977db93be728b2597e942d9200e47474d6e0c7c78584df8c8bae6a1f9c7 src/lib.rs
993cda76f3bfc029c02700f09dc25580e2a3d8e22b8be0cba4ba335f8c65b6e6 src/logging.rs 993cda76f3bfc029c02700f09dc25580e2a3d8e22b8be0cba4ba335f8c65b6e6 src/logging.rs
26b704f3de0020488c09c8ece38aeec62a7d4c561ae8c63f0eb2347570cf6d6b src/main.rs eca97890056d32e7c6e323cd526285a19675ef703f2833599b767964865de1a8 src/main.rs
7022ec2e991e9906e30b06f5bca58f96b9d7e09cc92a27202b5196d54e341b75 src/metrics.rs 7022ec2e991e9906e30b06f5bca58f96b9d7e09cc92a27202b5196d54e341b75 src/metrics.rs
64494e5746912cfcb10612ea4e0b336004b1f738bd7999be7e58fc96abadb6e1 src/not_found.rs 64494e5746912cfcb10612ea4e0b336004b1f738bd7999be7e58fc96abadb6e1 src/not_found.rs
e4b1f61c12437cc840c36eb7ab7b69b03f310bca3f89288ade9992d9dd1b7e2c src/pages.rs e4b1f61c12437cc840c36eb7ab7b69b03f310bca3f89288ade9992d9dd1b7e2c src/pages.rs
+1 -1
View File
@@ -1,6 +1,6 @@
# Debian packaging # Debian packaging
This directory builds the `rustproxy` binary package for RustProxy 0.24.9. This directory builds the `rustproxy` binary package for RustProxy 0.24.10.
The package installs: The package installs:
- `/usr/bin/rust-proxy` - `/usr/bin/rust-proxy`
+1 -1
View File
@@ -21,6 +21,6 @@ cargo fetch --locked
dpkg-buildpackage -us -uc -b dpkg-buildpackage -us -uc -b
mkdir -p dist mkdir -p dist
find .. -maxdepth 1 -type f \( -name 'rustproxy_0.24.9_*.deb' -o -name 'rustproxy_0.24.9_*.changes' -o -name 'rustproxy_0.24.9_*.buildinfo' \) \ find .. -maxdepth 1 -type f \( -name 'rustproxy_0.24.10_*.deb' -o -name 'rustproxy_0.24.10_*.changes' -o -name 'rustproxy_0.24.10_*.buildinfo' \) \
-exec cp -f {} dist/ \; -exec cp -f {} dist/ \;
echo "Packages copied to $ROOT/dist" echo "Packages copied to $ROOT/dist"
+4 -4
View File
@@ -1,8 +1,8 @@
rustproxy (0.24.9) stable; urgency=medium rustproxy (0.24.10) stable; urgency=medium
* Add encrypted Secret Store and migrate ACME provider credentials. * Add encrypted Secret Store and password-encrypted full backup/restore.
* Add password-encrypted full backup/restore with staged validation. * Keep ERROR file logging independent and persist its settings as a first-class section.
* Add optional bounded asynchronous ERROR file logging with secret redaction. * Keep response-time history expanded and add an accessible metric information control.
-- RustProxy Release <release@localhost> Sat, 03 Oct 2026 16:41:00 +0200 -- RustProxy Release <release@localhost> Sat, 03 Oct 2026 16:41:00 +0200
+2 -2
View File
@@ -18,7 +18,7 @@ if [[ $# -gt 0 ]]; then shift; fi
case "$mode" in case "$mode" in
help|-h|--help) help|-h|--help)
cat <<'HELP' cat <<'HELP'
RustProxy 0.24.9 RustProxy 0.24.10
./dev.sh Build and start proxy + local panel ./dev.sh Build and start proxy + local panel
./dev.sh run [serve flags] Same, with extra server options ./dev.sh run [serve flags] Same, with extra server options
./dev.sh admin setup-token Generate/print a one-time setup capability before first login ./dev.sh admin setup-token Generate/print a one-time setup capability before first login
@@ -123,7 +123,7 @@ case "$mode" in
check) exec cargo check --locked --all-targets "$@" ;; check) exec cargo check --locked --all-targets "$@" ;;
release) exec cargo build --locked --release "$@" ;; release) exec cargo build --locked --release "$@" ;;
esac esac
printf 'Building RustProxy 0.24.9...\n' >&2 printf 'Building RustProxy 0.24.10...\n' >&2
cargo build --locked cargo build --locked
BIN="$CARGO_TARGET_DIR/debug/rust-proxy" BIN="$CARGO_TARGET_DIR/debug/rust-proxy"
case "$mode" in case "$mode" in
+1 -1
View File
@@ -32,7 +32,7 @@ import (
"time" "time"
) )
const version = "0.24.9" const version = "0.24.10"
type config struct { type config struct {
Listen string Listen string
+1 -1
View File
@@ -1,4 +1,4 @@
# Administration API access - 0.24.9 # Administration API access - 0.24.10
## Sign-in boundary ## Sign-in boundary
+1 -1
View File
@@ -1,4 +1,4 @@
# Installing and updating RustProxy 0.24.9 # Installing and updating RustProxy 0.24.10
The scripts prepare a Linux/systemd installation. This source archive is **not a The scripts prepare a Linux/systemd installation. This source archive is **not a
verified production build**: Rust/Cargo, real CA operations, live systemd startup verified production build**: Rust/Cargo, real CA operations, live systemd startup
+4 -3
View File
@@ -1,8 +1,8 @@
# Verification report - RustProxy 0.24.9 # Verification report - RustProxy 0.24.10
## Scope ## Scope
RustProxy 0.24.9 implements the Secret Store + password-encrypted full backup/restore and optional ERROR-only file logging specifications. Secret Store adds schema version 7, encrypts ACME DNS-provider credentials with a local master key, migrates legacy provider `.ini` files, and recreates plaintext credential files only around the external Certbot operation. Full backup/restore uses Argon2id plus authenticated AES-256-GCM encryption, an explicit manifest, Secret Store key wrapping, staged validation and atomic data-directory activation. Error-file logging is a separate, disabled-by-default asynchronous bounded sink with JSON/RAW formats, central secret redaction and external `logrotate` rotation. RustProxy 0.24.10 implements the Secret Store + password-encrypted full backup/restore and optional ERROR-only file logging specifications. Secret Store adds schema version 7, encrypts ACME DNS-provider credentials with a local master key, migrates legacy provider `.ini` files, and recreates plaintext credential files only around the external Certbot operation. Full backup/restore uses Argon2id plus authenticated AES-256-GCM encryption, an explicit manifest, Secret Store key wrapping, staged validation and atomic data-directory activation. Error-file logging is a separate, disabled-by-default asynchronous bounded sink with JSON/RAW formats, central secret redaction and external `logrotate` rotation.
The release also retains the 0.24.8 overview cache diagnostics, 0.24.7 transform resource controls and 0.24.6 cache/background-worker memory hardening described by the earlier audit material below. The release also retains the 0.24.8 overview cache diagnostics, 0.24.7 transform resource controls and 0.24.6 cache/background-worker memory hardening described by the earlier audit material below.
@@ -13,12 +13,13 @@ The release also retains the 0.24.8 overview cache diagnostics, 0.24.7 transform
- `rust-proxy backup create` writes a new `.rpb` file outside the data directory, derives an independent backup key with Argon2id, encrypts authenticated chunks with AES-256-GCM, excludes cache/log/runtime data and wraps the Secret Store master key inside the encrypted backup. - `rust-proxy backup create` writes a new `.rpb` file outside the data directory, derives an independent backup key with Argon2id, encrypts authenticated chunks with AES-256-GCM, excludes cache/log/runtime data and wraps the Secret Store master key inside the encrypted backup.
- `rust-proxy backup restore` requires an interactive password, decrypts into private staging, validates manifest/checksums/schema/configuration/Secret Store decryptability before activation, then switches the data directory by rename with rollback on activation failure. - `rust-proxy backup restore` requires an interactive password, decrypts into private staging, validates manifest/checksums/schema/configuration/Secret Store decryptability before activation, then switches the data directory by rename with rollback on activation failure.
- `error_log` is independent from normal access logging, defaults disabled, accepts only `ERROR`, supports JSON/RAW, uses a bounded queue and a single asynchronous writer, handles filesystem failures without terminating RustProxy, and relies on system `logrotate` rather than application rollover. - `error_log` is independent from normal access logging, defaults disabled, accepts only `ERROR`, supports JSON/RAW, uses a bounded queue and a single asynchronous writer, handles filesystem failures without terminating RustProxy, and relies on system `logrotate` rather than application rollover.
- Error File stays in the existing revisioned `logging` section: its separate dashboard form and the access-log form both save a composite `{ logging, error_log }` value through `/api/config/logging`. A database-reopen regression verifies that `enabled`, path and format survive restart, the standalone `/api/config/error_log` route is intentionally absent, and historical access-only `/api/config/logging` payloads remain accepted for compatibility. The redundant Error Log help sentence is absent from the panel.
- The stderr/journald path, access-log sinks and the optional error-file sink share secret-redaction helpers for authorization/provider/password/private-key material. - The stderr/journald path, access-log sinks and the optional error-file sink share secret-redaction helpers for authorization/provider/password/private-key material.
- The KV secret-redaction expression uses a hash-delimited Rust raw string, avoiding the `E0762` unterminated-character-literal parser failure while preserving the same redaction character class. - The KV secret-redaction expression uses a hash-delimited Rust raw string, avoiding the `E0762` unterminated-character-literal parser failure while preserving the same redaction character class.
- The release checker preserves the existing dashboard contract while using bundled roots plus common system CA bundles (`SSL_CERT_FILE`, `CURL_CA_BUNDLE`, FreeBSD/OPNsense and Linux paths), follows at most five credential-free HTTP(S) redirects without HTTPS-to-HTTP downgrade, and reports network/TLS/HTTP/parser failures at `ERROR` level so the optional error-file sink receives them. - The release checker preserves the existing dashboard contract while using bundled roots plus common system CA bundles (`SSL_CERT_FILE`, `CURL_CA_BUNDLE`, FreeBSD/OPNsense and Linux paths), follows at most five credential-free HTTP(S) redirects without HTTPS-to-HTTP downgrade, and reports network/TLS/HTTP/parser failures at `ERROR` level so the optional error-file sink receives them.
- The overview Cache RAM block keeps separate histories for HOT allocated bytes, transient RAM, fill bytes, COLD queue bytes, COLD I/O bytes and COLD disk bytes; the disk series is never included in process RAM accounting. - The overview Cache RAM block keeps separate histories for HOT allocated bytes, transient RAM, fill bytes, COLD queue bytes, COLD I/O bytes and COLD disk bytes; the disk series is never included in process RAM accounting.
- HOT and transient histories are rendered against their runtime limits, while COLD storage is rendered against the configured COLD byte budget. - HOT and transient histories are rendered against their runtime limits, while COLD storage is rendered against the configured COLD byte budget.
- The long `memoryAccountingHint` remains translated and available on demand through the Process information control instead of consuming permanent dashboard height. - The long `memoryAccountingHint` remains translated and available on demand through the Process information control instead of consuming permanent dashboard height. Response time history is no longer collapsible and has the same compact information control explaining that it charts successive samples of the cumulative mean duration of completed HTTP exchanges.
- Transform `preset`, `memory_budget_bytes` and `max_inflight` are part of the normal revisioned Settings document and therefore persist in SQLite; the reopen test covers them. - Transform `preset`, `memory_budget_bytes` and `max_inflight` are part of the normal revisioned Settings document and therefore persist in SQLite; the reopen test covers them.
- Existing configurations default to 128 MiB transform temporary memory and 32 concurrent attempts. - Existing configurations default to 128 MiB transform temporary memory and 32 concurrent attempts.
- New transform attempts use atomic runtime byte/concurrency reservations and fail open with fixed `memory_pressure` / `concurrency_pressure` reasons rather than queueing full-body work. - New transform attempts use atomic runtime byte/concurrency reservations and fail open with fixed `memory_pressure` / `concurrency_pressure` reasons rather than queueing full-body work.
+1 -1
View File
@@ -3672,7 +3672,7 @@
}, },
"timepicker": {}, "timepicker": {},
"timezone": "browser", "timezone": "browser",
"title": "RustProxy 0.24.9 - Operations", "title": "RustProxy 0.24.10 - Operations",
"uid": "rustproxy-0170", "uid": "rustproxy-0170",
"version": 2, "version": 2,
"weekStart": "" "weekStart": ""
+3 -2
View File
@@ -909,7 +909,6 @@
"errorLogTitle": "Error file", "errorLogTitle": "Error file",
"errorLogPath": "Error log path", "errorLogPath": "Error log path",
"errorLogFormat": "Format", "errorLogFormat": "Format",
"errorLogHelp": "Only ERROR events are written. Rotation is handled by system logrotate.",
"tls12": "TLS 1.2", "tls12": "TLS 1.2",
"tls13": "TLS 1.3", "tls13": "TLS 1.3",
"tlsPolicy": "TLS policy", "tlsPolicy": "TLS policy",
@@ -1707,6 +1706,8 @@
"cacheMemoryRecommendedFor": "Recommended for detected memory:", "cacheMemoryRecommendedFor": "Recommended for detected memory:",
"cacheMemoryHeadroomWarning": "Warning: the additional cache budget is large compared with currently available host/cgroup memory:", "cacheMemoryHeadroomWarning": "Warning: the additional cache budget is large compared with currently available host/cgroup memory:",
"cacheObjectOverTransientWarning": "Maximum object exceeds the transient RAM budget; objects near that size will pass through instead of being cached.", "cacheObjectOverTransientWarning": "Maximum object exceeds the transient RAM budget; objects near that size will pass through instead of being cached.",
"vhCacheGlobalDisabled": "Global cache is disabled. This vhost cannot enable cache until the global cache is enabled." "vhCacheGlobalDisabled": "Global cache is disabled. This vhost cannot enable cache until the global cache is enabled.",
"responseLatencyInfo": "Response time information",
"responseLatencyHint": "The value is the cumulative mean duration of completed HTTP exchanges since process start. The chart shows successive samples of that mean; it is not a percentile or the latency of one request."
} }
} }
+3 -2
View File
@@ -909,7 +909,6 @@
"errorLogTitle": "Plik błędów", "errorLogTitle": "Plik błędów",
"errorLogPath": "Ścieżka logu błędów", "errorLogPath": "Ścieżka logu błędów",
"errorLogFormat": "Format", "errorLogFormat": "Format",
"errorLogHelp": "Zapisywane są tylko zdarzenia ERROR. Rotację obsługuje systemowy logrotate.",
"tls12": "TLS 1.2", "tls12": "TLS 1.2",
"tls13": "TLS 1.3", "tls13": "TLS 1.3",
"tlsPolicy": "Polityka TLS", "tlsPolicy": "Polityka TLS",
@@ -1707,6 +1706,8 @@
"cacheMemoryRecommendedFor": "Zalecane dla wykrytej pamięci:", "cacheMemoryRecommendedFor": "Zalecane dla wykrytej pamięci:",
"cacheMemoryHeadroomWarning": "Ostrzeżenie: dodatkowy budżet cache jest duży względem aktualnie dostępnej pamięci hosta/cgroup:", "cacheMemoryHeadroomWarning": "Ostrzeżenie: dodatkowy budżet cache jest duży względem aktualnie dostępnej pamięci hosta/cgroup:",
"cacheObjectOverTransientWarning": "Maksymalny rozmiar obiektu przekracza limit pamięci przejściowej; obiekty zbliżone do tego rozmiaru przejdą bez zapisania w cache.", "cacheObjectOverTransientWarning": "Maksymalny rozmiar obiektu przekracza limit pamięci przejściowej; obiekty zbliżone do tego rozmiaru przejdą bez zapisania w cache.",
"vhCacheGlobalDisabled": "Globalny cache jest wyłączony. Ten vhost nie może włączyć cache, dopóki cache globalny pozostaje wyłączony." "vhCacheGlobalDisabled": "Globalny cache jest wyłączony. Ten vhost nie może włączyć cache, dopóki cache globalny pozostaje wyłączony.",
"responseLatencyInfo": "Informacje o czasie odpowiedzi",
"responseLatencyHint": "Wartość to skumulowana średnia długość zakończonych wymian HTTP od startu procesu. Wykres pokazuje kolejne próbki tej średniej; nie jest to percentyl ani czas pojedynczego żądania."
} }
} }
+1 -1
View File
@@ -83,7 +83,7 @@ def add_file(archive: zipfile.ZipFile, rel: Path, data: bytes, timestamp: tuple[
def main() -> None: def main() -> None:
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.24.9.zip') parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.24.10.zip')
output = parser.parse_args().output.resolve() output = parser.parse_args().output.resolve()
paths = files() paths = files()
checksum = ROOT / 'SHA256SUMS' checksum = ROOT / 'SHA256SUMS'
+19 -12
View File
@@ -16,7 +16,7 @@ def read_cache_source() -> str:
preflight(root) preflight(root)
manifest = tomllib.loads((root / "Cargo.toml").read_text()) manifest = tomllib.loads((root / "Cargo.toml").read_text())
assert manifest["package"]["version"] == "0.24.9" assert manifest["package"]["version"] == "0.24.10"
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12" assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
h3dep = manifest["dependencies"]["h3"] h3dep = manifest["dependencies"]["h3"]
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"] assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"]
@@ -24,14 +24,14 @@ assert "h3-quinn" not in manifest["dependencies"]
lock_path = root / "Cargo.lock" lock_path = root / "Cargo.lock"
if lock_path.exists(): if lock_path.exists():
lock = lock_path.read_text() lock = lock_path.read_text()
assert 'name = "rust-proxy"\nversion = "0.24.9"' in lock assert 'name = "rust-proxy"\nversion = "0.24.10"' 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" 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 { for release_doc, marker in {
"README.md": "# RustProxy 0.24.9", "README.md": "# RustProxy 0.24.10",
"CHANGELOG.md": "## 0.24.9", "CHANGELOG.md": "## 0.24.10",
"ROADMAP.md": "## 0.24.9", "ROADMAP.md": "## 0.24.10",
"AGENTS.md": "Current source version: 0.24.9.", "AGENTS.md": "Current source version: 0.24.10.",
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.24.9", "docs/TEST_REPORT.md": "# Verification report - RustProxy 0.24.10",
}.items(): }.items():
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}" 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" assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
@@ -212,7 +212,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 '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
assert '"StreamMetrics"' in openapi and '"streams"' in openapi assert '"StreamMetrics"' in openapi and '"streams"' in openapi
for marker in ['"version": "0.24.9"', '"/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.24.10"', '"/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 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"']: 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 assert marker in openapi, marker
@@ -734,7 +734,8 @@ assert 'actions.append(enabledLabel, stateBadge, remove)' in docker_ui
assert 'row._readListeners = listenerControl.read' in docker_ui and '.docker-agent-listeners' not in docker_ui assert 'row._readListeners = listenerControl.read' in docker_ui and '.docker-agent-listeners' not in docker_ui
assert 'docker-agent-status-badge' in style_source and 'justify-content: center' in style_source assert 'docker-agent-status-badge' in style_source and 'justify-content: center' in style_source
assert '.docker-agent-grid>label' in style_source and '#docker-agent-add' in style_source and 'margin-top: 14px' in style_source assert '.docker-agent-grid>label' in style_source and '#docker-agent-add' in style_source and 'margin-top: 14px' in style_source
assert '<details class="mini-live-chart mini-live-chart-collapsible">' in html and 'class="mini-live-chart-head"' in html and 'data-i18n="responseLatencyHistory"' in html assert '<details class="mini-live-chart mini-live-chart-collapsible">' not in html
assert 'data-i18n="responseLatencyHistory"' in html and 'data-i18n="responseLatencyHint"' in html and 'data-i18n-aria="responseLatencyInfo"' in html
assert 'viewBox: "0 0 300 60"' in app_js and 'M0 15H300 M0 30H300 M0 45H300' in app_js assert 'viewBox: "0 0 300 60"' in app_js and 'M0 15H300 M0 30H300 M0 45H300' in app_js
assert '.network-interface-row {' in style_source and 'background: transparent' in style_source assert '.network-interface-row {' in style_source and 'background: transparent' in style_source
assert 'function checkboxField(key, control)' in docker_ui and 'checkboxField("dockerPreserveHost", preserve)' in docker_ui and 'checkboxField("dockerAclInline", inline)' in docker_ui assert 'function checkboxField(key, control)' in docker_ui and 'checkboxField("dockerPreserveHost", preserve)' in docker_ui and 'checkboxField("dockerAclInline", inline)' in docker_ui
@@ -1124,9 +1125,15 @@ for marker_0241 in ['"runtime"', '"platform"', '"statistics_source"', '"data_dir
assert marker_0241 in admin_0241 or marker_0241 in openapi, marker_0241 assert marker_0241 in admin_0241 or marker_0241 in openapi, marker_0241
assert 'id="process-platform"' in html and 'd.platform' in app_js assert 'id="process-platform"' in html and 'd.platform' in app_js
assert 'id="panel-logging"' not in html assert 'id="panel-logging"' not in html
assert 'id="settings-tab-logging"' in html and 'id="settings-page-logging"' in html and 'id="logging-form"' in html assert 'id="settings-tab-logging"' in html and 'id="settings-page-logging"' in html and 'id="logging-form"' in html and 'id="error-log-form"' in html
assert 'data-i18n="errorLogHelp"' not in html
assert 'bindSettingsForm("error-log-form"' in app_js and '}, "logging", selectLoggingSection);' in app_js
assert 'const selectLoggingSection = next => ({ logging: next.logging, error_log: next.error_log });' in app_js
assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log")' in admin_0241
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 'location.pathname === "/logging"' in settings_js_0241
assert '"RuntimeInformation"' in openapi and '"version": "0.24.9"' in openapi assert '"RuntimeInformation"' in openapi and '"version": "0.24.10"' in openapi
# 0.24.1 compiler and hostname-cardinality regressions. # 0.24.1 compiler and hostname-cardinality regressions.
tls_0241 = (root / "src/tls.rs").read_text() tls_0241 = (root / "src/tls.rs").read_text()
@@ -1171,5 +1178,5 @@ for schema_name in ["RedirectHost", "NotFoundHost"]:
node = openapi_0241["components"]["schemas"][schema_name]["properties"]["frontend"]["properties"]["hosts"] node = openapi_0241["components"]["schemas"][schema_name]["properties"]["frontend"]["properties"]["hosts"]
assert "maxItems" not in node, schema_name assert "maxItems" not in node, schema_name
print("PASS: 0.24.9 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, Debian/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-v7 migrations.") print("PASS: 0.24.10 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, Debian/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-v7 migrations.")
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.") print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
+1 -1
View File
@@ -97,7 +97,7 @@ def digest(path: Path) -> str:
def fetch_https(url: str, max_bytes: int) -> bytes: def fetch_https(url: str, max_bytes: int) -> bytes:
if urllib.parse.urlparse(url).scheme != 'https': if urllib.parse.urlparse(url).scheme != 'https':
raise ValueError('GeoIP downloads require HTTPS.') raise ValueError('GeoIP downloads require HTTPS.')
request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.24.9'}) request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.24.10'})
with urllib.request.urlopen(request, timeout=30) as response: with urllib.request.urlopen(request, timeout=30) as response:
if urllib.parse.urlparse(response.geturl()).scheme != 'https': if urllib.parse.urlparse(response.geturl()).scheme != 'https':
raise ValueError('GeoIP download redirected away from HTTPS.') raise ValueError('GeoIP download redirected away from HTTPS.')
+2 -2
View File
@@ -33,10 +33,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)}") 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/upstream.rs", "src/tls.rs", "src/proxy/websocket.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/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/management.js", "src/pages.rs", "scripts/mock_server.py", "scripts/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "scripts/acme_provider.py", "src/basic_auth.rs", "scripts/deploy.py", "scripts/migrate_npm.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/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", "debian/control", "debian/rules", "debian/rustproxy.service", "debian/rustproxy.logrotate", "grafana/rustproxy-overview.json"]: for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/upstream.rs", "src/tls.rs", "src/proxy/websocket.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/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/management.js", "src/pages.rs", "scripts/mock_server.py", "scripts/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "scripts/acme_provider.py", "src/basic_auth.rs", "scripts/deploy.py", "scripts/migrate_npm.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/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", "debian/control", "debian/rules", "debian/rustproxy.service", "debian/rustproxy.logrotate", "grafana/rustproxy-overview.json"]:
if not (root / path).is_file(): if not (root / path).is_file():
raise ValueError(f"Incomplete RustProxy 0.24.9 source tree: missing {path}") raise ValueError(f"Incomplete RustProxy 0.24.10 source tree: missing {path}")
text = (root / "src/lib.rs").read_text(encoding="utf-8") text = (root / "src/lib.rs").read_text(encoding="utf-8")
if '#[path = "auth/mod.rs"]' not in text: if '#[path = "auth/mod.rs"]' not in text:
raise ValueError("src/lib.rs is not the 0.24.9 entry point; extract the complete package, not individual files.") raise ValueError("src/lib.rs is not the 0.24.10 entry point; extract the complete package, not individual files.")
return messages return messages
+10 -8
View File
@@ -114,13 +114,13 @@ def upgrade_smoke(tmp: Path) -> None:
assert backup.is_dir() assert backup.is_dir()
assert (backup / "src/obsolete.rs").is_file() assert (backup / "src/obsolete.rs").is_file()
assert not (target / "src/obsolete.rs").exists() assert not (target / "src/obsolete.rs").exists()
assert 'version = "0.24.9"' in (target / "Cargo.toml").read_text(encoding="utf-8") assert 'version = "0.24.10"' in (target / "Cargo.toml").read_text(encoding="utf-8")
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n" 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 / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
assert (target / "langs/site.json").is_file() assert (target / "langs/site.json").is_file()
assert (target / "docker-agent/main.go").is_file() assert (target / "docker-agent/main.go").is_file()
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8")) manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
assert manifest["to_version"] == "0.24.9" assert manifest["to_version"] == "0.24.10"
assert manifest["removed_obsolete_runtime_state"] is True assert manifest["removed_obsolete_runtime_state"] is True
assert not legacy.exists() assert not legacy.exists()
assert not (backup / "legacy-data").exists() assert not (backup / "legacy-data").exists()
@@ -247,6 +247,7 @@ def ui_contract_smoke() -> None:
'id="settings-tab-logging"', 'id="settings-tab-logging"',
'id="settings-page-logging"', 'id="settings-page-logging"',
'id="logging-form"', 'id="logging-form"',
'id="error-log-form"',
'id="settings-tab-administration"', 'id="settings-tab-administration"',
'id="settings-tab-tls-policy"', 'id="settings-tab-tls-policy"',
'id="admin-tls"', 'id="admin-tls"',
@@ -361,8 +362,9 @@ def ui_contract_smoke() -> None:
assert marker in html, marker assert marker in html, marker
for marker in ["lastApiLatencyMs", "cacheMemorySamples", "networkRxSamples", "responseLatencySamples"]: for marker in ["lastApiLatencyMs", "cacheMemorySamples", "networkRxSamples", "responseLatencySamples"]:
assert marker in app, marker assert marker in app, marker
assert '<details class="mini-live-chart mini-live-chart-collapsible">' in html assert '<details class="mini-live-chart mini-live-chart-collapsible">' not in html
assert 'class="mini-live-chart-head"' in html and 'data-i18n="responseLatencyHistory"' in html assert 'data-i18n="responseLatencyHistory"' in html and 'data-i18n="responseLatencyHint"' in html and 'data-i18n-aria="responseLatencyInfo"' in html
assert 'bindSettingsForm("error-log-form"' in app and '}, "logging", selectLoggingSection);' in app
assert 'viewBox: "0 0 300 60"' in app and 'M0 15H300 M0 30H300 M0 45H300' in app assert 'viewBox: "0 0 300 60"' in app and 'M0 15H300 M0 30H300 M0 45H300' in app
assert '.network-interface-row {' in css and 'background: transparent' in css assert '.network-interface-row {' in css and 'background: transparent' in css
assert "hostTagEditor" in vhosts and "vhost-chart-line" in vhosts and "vhLifetimeChart" in vhosts assert "hostTagEditor" in vhosts and "vhost-chart-line" in vhosts and "vhLifetimeChart" in vhosts
@@ -506,9 +508,9 @@ def source_contract_smoke() -> None:
assert marker in admin, marker assert marker in admin, marker
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0] 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 '"/api/status"' not in anonymous_block
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.24.9"' in openapi_0241 assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.24.10"' in openapi_0241
assert 'id="panel-logging"' not in html assert 'id="panel-logging"' not in html
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"']: for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
assert marker in html, marker assert marker in html, marker
assert 'location.pathname === "/logging"' in settings_ui assert 'location.pathname === "/logging"' in settings_ui
@@ -950,7 +952,7 @@ def source_contract_smoke() -> None:
assert "removed_obsolete_runtime_state" in upgrade_source and "legacy-data" not in upgrade_source and "certs_dir.is_symlink()" in upgrade_source assert "removed_obsolete_runtime_state" in upgrade_source and "legacy-data" not in upgrade_source and "certs_dir.is_symlink()" in upgrade_source
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main 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") openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
for marker in ['"version": "0.24.9"', '"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.24.10"', '"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 marker in openapi, marker
assert '"software_update"' in openapi and '"update_available"' in openapi assert '"software_update"' in openapi and '"update_available"' in openapi
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']: for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
@@ -1098,7 +1100,7 @@ def main() -> None:
source_contract_smoke() source_contract_smoke()
container_contract_smoke() container_contract_smoke()
debian_grafana_contract_smoke() debian_grafana_contract_smoke()
print("PASS: 0.24.9 SQLite/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, Debian packaging, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.") print("PASS: 0.24.10 SQLite/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, Debian packaging, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
if __name__ == "__main__": if __name__ == "__main__":
+2 -2
View File
@@ -76,10 +76,10 @@ ENV_FILE=scripts/routeros-oci-build/.env \
bash scripts/routeros-oci-build/build-routeros-oci.sh push bash scripts/routeros-oci-build/build-routeros-oci.sh push
``` ```
With RustProxy version `0.24.9` and the defaults this publishes: With RustProxy version `0.24.10` and the defaults this publishes:
```text ```text
zot.example.net/network/rustproxy:0.24.9 zot.example.net/network/rustproxy:0.24.10
zot.example.net/network/rustproxy:latest zot.example.net/network/rustproxy:latest
zot.example.net/network/rustproxy:routeros zot.example.net/network/rustproxy:routeros
``` ```
+1 -1
View File
@@ -85,7 +85,7 @@ def download(destination: Path) -> None:
"""TLS verification uses Python's default trust store; never executes a shell.""" """TLS verification uses Python's default trust store; never executes a shell."""
started = time.monotonic() started = time.monotonic()
opener = build_opener(HttpsRedirects()) opener = build_opener(HttpsRedirects())
request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.24.9"}) request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.24.10"})
with opener.open(request, timeout=30) as response, destination.open("wb") as output: with opener.open(request, timeout=30) as response, destination.open("wb") as output:
length = response.headers.get("Content-Length") length = response.headers.get("Content-Length")
if length and int(length) > MAX_ARCHIVE: if length and int(length) > MAX_ARCHIVE:
+3 -3
View File
@@ -46,7 +46,7 @@ def upgrade(source: Path, target: Path, *, stopped: bool, data_dir: Path | None
for name in MANAGED: for name in MANAGED:
if (target / name).is_symlink(): if (target / name).is_symlink():
raise ValueError(f"Managed target {name} is a symlink; refusing to replace it.") raise ValueError(f"Managed target {name} is a symlink; refusing to replace it.")
backup = target / ".upgrade-backup" / ("before-0.24.9-" + uuid.uuid4().hex[:12]) backup = target / ".upgrade-backup" / ("before-0.24.10-" + uuid.uuid4().hex[:12])
backup.mkdir(parents=True, mode=0o700) backup.mkdir(parents=True, mode=0o700)
installed: list[str] = [] installed: list[str] = []
moved: list[str] = [] moved: list[str] = []
@@ -84,7 +84,7 @@ def upgrade(source: Path, target: Path, *, stopped: bool, data_dir: Path | None
remove_legacy = legacy.exists() remove_legacy = legacy.exists()
if remove_legacy and (legacy.is_symlink() or not legacy.is_file()): if remove_legacy and (legacy.is_symlink() or not legacy.is_file()):
raise ValueError("Obsolete renewal observation sidecar must be a regular file.") raise ValueError("Obsolete renewal observation sidecar must be a regular file.")
(backup / "upgrade.json").write_text(json.dumps({"from_directory":str(target), "to_version":"0.24.9", "managed_paths":MANAGED, "removed_obsolete_runtime_state": remove_legacy}, indent=2)+"\n", encoding="utf-8") (backup / "upgrade.json").write_text(json.dumps({"from_directory":str(target), "to_version":"0.24.10", "managed_paths":MANAGED, "removed_obsolete_runtime_state": remove_legacy}, indent=2)+"\n", encoding="utf-8")
if remove_legacy: if remove_legacy:
legacy.unlink() legacy.unlink()
except BaseException: except BaseException:
@@ -115,7 +115,7 @@ def main() -> None:
backup = upgrade(Path(__file__).resolve().parents[1], args.target, stopped=args.stopped, data_dir=data_dir) backup = upgrade(Path(__file__).resolve().parents[1], args.target, stopped=args.stopped, data_dir=data_dir)
except (OSError, ValueError) as error: except (OSError, ValueError) as error:
parser.exit(2, f"Upgrade refused or rolled back: {error}\n") parser.exit(2, f"Upgrade refused or rolled back: {error}\n")
print(f"Installed RustProxy 0.24.9. Previous managed source files: {backup}") print(f"Installed RustProxy 0.24.10. Previous managed source files: {backup}")
print("Preserved data, .env, .git, target and other unmanaged files. No database migration was run.") print("Preserved data, .env, .git, target and other unmanaged files. No database migration was run.")
print(f"Next: cd {args.target}; ./dev.sh test; ./dev.sh") print(f"Next: cd {args.target}; ./dev.sh test; ./dev.sh")
+102 -1
View File
@@ -1447,6 +1447,13 @@ struct ResponseSection {
compression: crate::compression::Settings, compression: crate::compression::Settings,
} }
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct LoggingSection {
logging: crate::logging::Settings,
error_log: crate::logging::ErrorLogSettings,
}
fn invalid_section(section: &str, error: serde_json::Error) -> ApiError { fn invalid_section(section: &str, error: serde_json::Error) -> ApiError {
ApiError( ApiError(
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
@@ -1502,8 +1509,17 @@ async fn update_section(
serde_json::from_value(value).map_err(|e| invalid_section(&section, e))? serde_json::from_value(value).map_err(|e| invalid_section(&section, e))?
} }
"logging" => { "logging" => {
if value.get("logging").is_some() || value.get("error_log").is_some() {
let part: LoggingSection =
serde_json::from_value(value).map_err(|e| invalid_section(&section, e))?;
config.logging = part.logging;
config.error_log = part.error_log;
} else {
// Backward compatibility for clients that still send only the
// historical access-logging object to /api/config/logging.
config.logging = config.logging =
serde_json::from_value(value).map_err(|e| invalid_section(&section, e))? serde_json::from_value(value).map_err(|e| invalid_section(&section, e))?;
}
} }
"forwarding" => { "forwarding" => {
config.forwarding = config.forwarding =
@@ -2387,6 +2403,91 @@ mod tests {
store.close().await; store.close().await;
} }
#[tokio::test]
async fn logging_section_persists_error_log_settings_atomically() {
let (router, store, dir, secret, csrf) = fixture().await;
let cookie = format!("{}={secret}", auth::COOKIE_NAME);
let logging = Config::default().logging;
let mut error_log = Config::default().error_log;
error_log.enabled = true;
error_log.path = "/tmp/rustproxy-error.log".into();
let response = router
.clone()
.oneshot(
request("/api/config/logging")
.method("PUT")
.header("content-type", "application/json")
.header("cookie", &cookie)
.header("origin", "http://127.0.0.1:9090")
.header("x-rustproxy-request", "1")
.header("x-csrf-token", &csrf)
.body(Body::from(
serde_json::to_vec(&json!({
"expected_revision": 0,
"value": {
"logging": logging,
"error_log": error_log,
},
}))
.unwrap(),
))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let updated = json_body(response).await;
assert_eq!(updated["revision"], 1);
assert_eq!(updated["config"]["error_log"]["enabled"], true);
assert_eq!(updated["config"]["error_log"]["path"], "/tmp/rustproxy-error.log");
let configuration = router
.oneshot(
request("/api/config")
.header("cookie", &cookie)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
let persisted = json_body(configuration).await;
assert_eq!(persisted["config"]["error_log"]["enabled"], true);
assert_eq!(persisted["config"]["error_log"]["path"], "/tmp/rustproxy-error.log");
drop(router);
store.close().await;
let reopened = Store::open(&dir.path().join("db"), &Config::default())
.await
.unwrap();
let persisted = reopened.load().await.unwrap();
assert_eq!(persisted.revision, 1);
assert!(persisted.config.error_log.enabled);
assert_eq!(persisted.config.error_log.path, "/tmp/rustproxy-error.log");
reopened.close().await;
}
#[tokio::test]
async fn error_log_is_not_a_standalone_configuration_section() {
let (router, store, _dir, secret, csrf) = fixture().await;
let cookie = format!("{}={secret}", auth::COOKIE_NAME);
let response = router
.oneshot(
request("/api/config/error_log")
.method("PUT")
.header("content-type", "application/json")
.header("cookie", &cookie)
.header("origin", "http://127.0.0.1:9090")
.header("x-rustproxy-request", "1")
.header("x-csrf-token", &csrf)
.body(Body::from(r#"{"expected_revision":0,"value":{}}"#))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
store.close().await;
}
#[tokio::test] #[tokio::test]
async fn default_pages_section_accepts_the_standard_value_payload() { async fn default_pages_section_accepts_the_standard_value_payload() {
let (router, store, _dir, secret, csrf) = fixture().await; let (router, store, _dir, secret, csrf) = fixture().await;
+1 -1
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3", "openapi": "3.0.3",
"info": { "info": {
"title": "RustProxy Administration API", "title": "RustProxy Administration API",
"version": "0.24.9", "version": "0.24.10",
"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." "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": [ "servers": [
+11 -8
View File
@@ -778,7 +778,7 @@ function renderSettings() {
if (!settingsDirty.has("response-form")) { if (!settingsDirty.has("response-form")) {
const server = config.server_header || {}, compression = config.compression || {}, diagnostics = config.diagnostic_headers || {}; const server = config.server_header || {}, compression = config.compression || {}, diagnostics = config.diagnostic_headers || {};
$("expose-request-id").checked = !!diagnostics.request_id; $("expose-request-id").checked = !!diagnostics.request_id;
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.24.9"; $("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.24.10";
for (const name of ["gzip", "br", "zstd", "deflate"]) $(`compress-${name}`).checked = !!compression[name]; for (const name of ["gzip", "br", "zstd", "deflate"]) $(`compress-${name}`).checked = !!compression[name];
$("compress-min").value = compression.min_bytes ?? 1024; syncServerMode(); $("compress-min").value = compression.min_bytes ?? 1024; syncServerMode();
} }
@@ -821,23 +821,23 @@ function renderSettings() {
updateTransformResourceSizing(); updateTransformResourceSizing();
} }
if (!settingsDirty.has("logging-form")) { if (!settingsDirty.has("logging-form")) {
const l = config.logging || {}, file = l.file || {}, logstash = l.logstash || {}, loki = l.loki || {}, format = l.format || {}, errorLog = config.error_log || {}; const l = config.logging || {}, file = l.file || {}, logstash = l.logstash || {}, loki = l.loki || {}, format = l.format || {};
$("log-file-enabled").checked = !!file.enabled; $("log-file-per-vhost").checked = !!file.per_vhost; $("log-file-path").value = file.path || "/var/log/rustproxy/access.jsonl"; $("log-file-directory").value = file.directory || "/var/log/rustproxy/vhosts"; syncLoggingFileMode(); $("log-file-enabled").checked = !!file.enabled; $("log-file-per-vhost").checked = !!file.per_vhost; $("log-file-path").value = file.path || "/var/log/rustproxy/access.jsonl"; $("log-file-directory").value = file.directory || "/var/log/rustproxy/vhosts"; syncLoggingFileMode();
$("log-logstash-enabled").checked = !!logstash.enabled; $("log-logstash-address").value = logstash.address || "127.0.0.1:5044"; $("log-logstash-enabled").checked = !!logstash.enabled; $("log-logstash-address").value = logstash.address || "127.0.0.1:5044";
$("log-loki-enabled").checked = !!loki.enabled; $("log-loki-url").value = loki.url || "http://127.0.0.1:3100/loki/api/v1/push"; $("log-loki-token").value = loki.bearer_token_env || ""; $("log-loki-enabled").checked = !!loki.enabled; $("log-loki-url").value = loki.url || "http://127.0.0.1:3100/loki/api/v1/push"; $("log-loki-token").value = loki.bearer_token_env || "";
$("log-loki-labels").value = JSON.stringify(loki.labels || { job: "rustproxy" }, null, 2); $("log-format-fields").value = JSON.stringify(format.fields || {}, null, 2); $("log-format-static").value = JSON.stringify(format.static_fields || {}, null, 2); $("log-loki-labels").value = JSON.stringify(loki.labels || { job: "rustproxy" }, null, 2); $("log-format-fields").value = JSON.stringify(format.fields || {}, null, 2); $("log-format-static").value = JSON.stringify(format.static_fields || {}, null, 2);
$("log-format-preset").value = detectLoggingPreset(format.fields || {}); $("log-format-preset").value = detectLoggingPreset(format.fields || {});
}
if (!settingsDirty.has("error-log-form")) {
const errorLog = config.error_log || {};
$("error-log-enabled").checked = !!errorLog.enabled; $("error-log-enabled").checked = !!errorLog.enabled;
$("error-log-path").value = errorLog.path || "/var/log/rustproxy/error.log"; $("error-log-path").value = errorLog.path || "/var/log/rustproxy/error.log";
$("error-log-format").value = ["JSON", "RAW"].includes(errorLog.format) ? errorLog.format : "JSON"; $("error-log-format").value = ["JSON", "RAW"].includes(errorLog.format) ? errorLog.format : "JSON";
syncErrorLogMode(); syncErrorLogMode();
} }
const sink = latestStatus?.logging?.sinks, errorSink = latestStatus?.logging?.error_file; const sink = latestStatus?.logging?.sinks, errorSink = latestStatus?.logging?.error_file;
if (sink) { $("logging-status").textContent = sink ? `${t("loggingQueue")}: ${fmt(sink.queue_dropped)} ${t("loggingDropped")} / ${fmt(sink.sent)} ${t("loggingSent")} / ${fmt(sink.errors)} ${t("errors")} · ${t("loggingCapacity")}: ${fmt(sink.queue_capacity)}` : "";
let status = `${t("loggingQueue")}: ${fmt(sink.queue_dropped)} ${t("loggingDropped")} / ${fmt(sink.sent)} ${t("loggingSent")} / ${fmt(sink.errors)} ${t("errors")} · ${t("loggingCapacity")}: ${fmt(sink.queue_capacity)}`; $("error-log-status").textContent = errorSink ? `${t("loggingQueue")}: ${fmt(errorSink.queue_dropped)} ${t("loggingDropped")} / ${fmt(errorSink.written)} ${t("loggingSent")} / ${fmt(errorSink.errors)} ${t("errors")} · ${t("loggingCapacity")}: ${fmt(errorSink.queue_capacity)}` : "";
if (errorSink) status += ` · ${t("errorLogTitle")}: ${fmt(errorSink.queue_dropped)} ${t("loggingDropped")} / ${fmt(errorSink.written)} ${t("loggingSent")} / ${fmt(errorSink.errors)} ${t("errors")}`;
$("logging-status").textContent = status;
}
if (!settingsDirty.has("forwarding-form")) { $("trusted-proxies").value = (config.forwarding?.trusted_proxies || []).join("\n"); $("trusted-proto").checked = !!config.forwarding?.trust_forwarded_proto; } if (!settingsDirty.has("forwarding-form")) { $("trusted-proxies").value = (config.forwarding?.trusted_proxies || []).join("\n"); $("trusted-proto").checked = !!config.forwarding?.trust_forwarded_proto; }
if (!settingsDirty.has("headers-form")) { for (const direction of ["request", "response"]) renderHeaderRules(direction, config.headers?.[direction] || []); } if (!settingsDirty.has("headers-form")) { for (const direction of ["request", "response"]) renderHeaderRules(direction, config.headers?.[direction] || []); }
} }
@@ -1120,6 +1120,7 @@ function applyLoggingPreset(name) {
$("log-format-fields").value = JSON.stringify(LOGGING_PRESETS[name], null, 2); $("log-format-fields").value = JSON.stringify(LOGGING_PRESETS[name], null, 2);
setSettingsDirty("logging-form"); setSettingsDirty("logging-form");
} }
const selectLoggingSection = next => ({ logging: next.logging, error_log: next.error_log });
bindSettingsForm("logging-form", next => { bindSettingsForm("logging-form", next => {
const parseObject = id => parseJsonObjectValue($(id).value, $(id)); const parseObject = id => parseJsonObjectValue($(id).value, $(id));
next.logging = { next.logging = {
@@ -1128,12 +1129,14 @@ bindSettingsForm("logging-form", next => {
loki: { enabled: $("log-loki-enabled").checked, url: $("log-loki-url").value.trim(), bearer_token_env: $("log-loki-token").value.trim() || null, labels: parseObject("log-loki-labels") }, loki: { enabled: $("log-loki-enabled").checked, url: $("log-loki-url").value.trim(), bearer_token_env: $("log-loki-token").value.trim() || null, labels: parseObject("log-loki-labels") },
format: { fields: parseObject("log-format-fields"), static_fields: parseObject("log-format-static") } format: { fields: parseObject("log-format-fields"), static_fields: parseObject("log-format-static") }
}; };
}, "logging", selectLoggingSection);
bindSettingsForm("error-log-form", next => {
next.error_log = { next.error_log = {
enabled: $("error-log-enabled").checked, enabled: $("error-log-enabled").checked,
path: $("error-log-path").value.trim(), path: $("error-log-path").value.trim(),
format: $("error-log-format").value format: $("error-log-format").value
}; };
}, "logging"); }, "logging", selectLoggingSection);
bindSettingsForm("forwarding-form", next => { next.forwarding = { trusted_proxies: $("trusted-proxies").value.split(/[\n,]/).map(s => s.trim()).filter(Boolean), trust_forwarded_proto: $("trusted-proto").checked }; }, "forwarding"); bindSettingsForm("forwarding-form", next => { next.forwarding = { trusted_proxies: $("trusted-proxies").value.split(/[\n,]/).map(s => s.trim()).filter(Boolean), trust_forwarded_proto: $("trusted-proto").checked }; }, "forwarding");
bindSettingsForm("headers-form", next => { next.headers = { request: collectHeaderRules("request"), response: collectHeaderRules("response") }; }, "headers"); bindSettingsForm("headers-form", next => { next.headers = { request: collectHeaderRules("request"), response: collectHeaderRules("response") }; }, "headers");
$("log-format-preset").addEventListener("change", event => applyLoggingPreset(event.target.value)); $("log-format-preset").addEventListener("change", event => applyLoggingPreset(event.target.value));
+32 -21
View File
@@ -479,17 +479,22 @@
by interface</span><b id="network-interface-chart-value">-</b></div> by interface</span><b id="network-interface-chart-value">-</b></div>
<div class="network-interface-charts" id="network-interface-charts"></div> <div class="network-interface-charts" id="network-interface-charts"></div>
</div> </div>
<details class="mini-live-chart mini-live-chart-collapsible"> <div class="mini-live-chart">
<summary class="mini-live-chart-head"><span <div class="mini-live-chart-head"><span
data-i18n="responseLatencyHistory">Response time history</span><b data-i18n="responseLatencyHistory">Response time history</span><span
id="latency-chart-value">-</b></summary> class="mini-live-chart-actions"><details class="metric-info">
<summary data-i18n-aria="responseLatencyInfo" aria-label="Response time information">i</summary>
<div class="metric-info-popover">
<p class="fine" data-i18n="responseLatencyHint">The value is the cumulative mean duration of completed HTTP exchanges since process start. The chart shows successive samples of that mean; it is not a percentile or the latency of one request.</p>
</div>
</details><b id="latency-chart-value">-</b></span></div>
<svg aria-label="Mean response latency history" <svg aria-label="Mean response latency history"
data-i18n-aria="responseLatencyHistory" role="img" viewbox="0 0 300 60"> data-i18n-aria="responseLatencyHistory" role="img" viewbox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path> <path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line" id="latency-sparkline" points="0,55 300,55"> <polyline class="diagnostic-line" id="latency-sparkline" points="0,55 300,55">
</polyline> </polyline>
</svg> </svg>
</details> </div>
</div> </div>
</article> </article>
</div> </div>
@@ -1391,7 +1396,7 @@
<option data-i18n="serverCustom" value="custom">Replace / add custom value</option> <option data-i18n="serverCustom" value="custom">Replace / add custom value</option>
</select> </select>
<label data-i18n="serverValue" for="server-value">Custom Server value</label><input <label data-i18n="serverValue" for="server-value">Custom Server value</label><input
id="server-value" maxlength="256" placeholder="RustProxy/v0.24.9" id="server-value" maxlength="256" placeholder="RustProxy/v0.24.10"
spellcheck="false" /> spellcheck="false" />
<h3 class="subheading" data-i18n="diagnosticHeaders">Diagnostic headers</h3> <h3 class="subheading" data-i18n="diagnosticHeaders">Diagnostic headers</h3>
<div class="switch-grid"> <div class="switch-grid">
@@ -1798,20 +1803,6 @@
<label><span data-i18n="loggingLabels">Labels (JSON object)</span><textarea <label><span data-i18n="loggingLabels">Labels (JSON object)</span><textarea
id="log-loki-labels" rows="4" spellcheck="false"></textarea></label> id="log-loki-labels" rows="4" spellcheck="false"></textarea></label>
</article> </article>
<article class="card">
<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>
<label><span data-i18n="errorLogPath">Error log path</span><input
id="error-log-path" maxlength="1024"
placeholder="/var/log/rustproxy/error.log" /></label>
<label><span data-i18n="errorLogFormat">Format</span><select
id="error-log-format">
<option value="JSON">JSON</option>
<option value="RAW">RAW</option>
</select></label>
<p class="fine" data-i18n="errorLogHelp">Only ERROR events are written. Rotation is handled by system logrotate.</p>
</article>
</div> </div>
<h3 class="subheading" data-i18n="loggingFormat">Configurable JSON format</h3> <h3 class="subheading" data-i18n="loggingFormat">Configurable JSON format</h3>
<p class="fine" data-i18n="loggingFormatHelp">Map output names to source fields. Leave <p class="fine" data-i18n="loggingFormatHelp">Map output names to source fields. Leave
@@ -1849,6 +1840,26 @@
type="submit">Validate, save type="submit">Validate, save
and apply</button></div> and apply</button></div>
</form> </form>
<form class="card edit-form" id="error-log-form">
<div class="card-heading">
<h2 data-i18n="errorLogTitle">Error file</h2><span class="pill live" data-i18n="hotApply">LIVE APPLY</span>
</div>
<label class="check"><input id="error-log-enabled" type="checkbox" /><span
data-i18n="enabled">Enabled</span></label>
<div class="field-grid">
<label><span data-i18n="errorLogPath">Error log path</span><input
id="error-log-path" maxlength="1024"
placeholder="/var/log/rustproxy/error.log" /></label>
<label><span data-i18n="errorLogFormat">Format</span><select
id="error-log-format">
<option value="JSON">JSON</option>
<option value="RAW">RAW</option>
</select></label>
</div>
<p class="fine" id="error-log-status" role="status"></p>
<div class="form-actions"><button class="primary" data-i18n="apply"
type="submit">Validate, save and apply</button></div>
</form>
</div> </div>
<div aria-labelledby="settings-tab-prometheus" class="settings-page" <div aria-labelledby="settings-tab-prometheus" class="settings-page"
data-settings-page="prometheus" hidden="" id="settings-page-prometheus" role="tabpanel"> data-settings-page="prometheus" hidden="" id="settings-page-prometheus" role="tabpanel">
@@ -2112,7 +2123,7 @@
</section> </section>
</div> </div>
</section> </section>
<footer class="page-footer"><span>RustProxy <b>0.24.9</b></span><span id="footer-status">API - / <footer class="page-footer"><span>RustProxy <b>0.24.10</b></span><span id="footer-status">API - /
dropped: 0</span></footer> dropped: 0</span></footer>
</div> </div>
</main> </main>
+12 -29
View File
@@ -1758,6 +1758,14 @@ progress {
text-align: right text-align: right
} }
.mini-live-chart-actions {
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 7px;
min-width: 0
}
.mini-live-chart svg { .mini-live-chart svg {
display: block; display: block;
width: 100%; width: 100%;
@@ -3773,6 +3781,10 @@ body[data-density="dense"] .stat strong {
max-width: none max-width: none
} }
.settings-page>.edit-form + .edit-form {
margin-top: var(--ui-gap)
}
.settings-page>.card { .settings-page>.card {
padding: var(--ui-card) padding: var(--ui-card)
} }
@@ -6359,31 +6371,6 @@ body[data-density="compact"] .page-footer {
} }
} }
.mini-live-chart-collapsible>summary {
cursor: pointer;
list-style: none;
user-select: none
}
.mini-live-chart-collapsible>summary::-webkit-details-marker {
display: none
}
.mini-live-chart-collapsible>summary::before {
content: "▸";
flex: 0 0 auto;
color: var(--muted);
margin-right: 1px
}
.mini-live-chart-collapsible[open]>summary::before {
content: "▾"
}
.mini-live-chart-collapsible>summary>span {
flex: 1 1 auto
}
.network-interface-charts { .network-interface-charts {
gap: 8px; gap: 8px;
padding-right: 0 padding-right: 0
@@ -6552,10 +6539,6 @@ body[data-density="compact"] .page-footer {
} }
} }
.mini-live-chart-collapsible:not([open])>summary {
margin-bottom: 0
}
.docker-route-grid { .docker-route-grid {
grid-template-columns: minmax(190px, 1fr) minmax(250px, 1.35fr) minmax(210px, 1fr) minmax(120px, .65fr) grid-template-columns: minmax(190px, 1fr) minmax(250px, 1.35fr) minmax(210px, 1fr) minmax(120px, .65fr)
} }
+1 -1
View File
@@ -600,7 +600,7 @@
authMode.addEventListener("change", syncAuth); syncAuth(); authMode.addEventListener("change", syncAuth); syncAuth();
g = group("vhResponsePolicy"); const server = value.server_header, diagnostics = value.diagnostic_headers; g = group("vhResponsePolicy"); const server = value.server_header, diagnostics = value.diagnostic_headers;
const serverMode = field(g, "server-mode", "serverHeader", server?.mode || "inherit", "select", { values: [["inherit", "vhInherit"], ["preserve", "serverPreserve"], ["hide", "serverHide"], ["custom", "serverCustom"]] }); const serverMode = field(g, "server-mode", "serverHeader", server?.mode || "inherit", "select", { values: [["inherit", "vhInherit"], ["preserve", "serverPreserve"], ["hide", "serverHide"], ["custom", "serverCustom"]] });
const serverValue = field(g, "server-value", "serverValue", server?.value || "RustProxy/v0.24.9", "text", { maxLength: 256 }); const serverValue = field(g, "server-value", "serverValue", server?.value || "RustProxy/v0.24.10", "text", { maxLength: 256 });
const syncServer = () => { serverValue.disabled = serverMode.value !== "custom"; serverValue.required = !serverValue.disabled; }; serverMode.addEventListener("change", syncServer); syncServer(); const syncServer = () => { serverValue.disabled = serverMode.value !== "custom"; serverValue.required = !serverValue.disabled; }; serverMode.addEventListener("change", syncServer); syncServer();
const inheritDiagnostics = field(g, "diag-inherit", "diagInherit", !diagnostics, "checkbox"), diagInputs = []; const inheritDiagnostics = field(g, "diag-inherit", "diagInherit", !diagnostics, "checkbox"), diagInputs = [];
for (const [name, key] of [["request_id", "diagRequestId"]]) diagInputs.push(field(g, `diag-${name}`, key, diagnostics?.[name] ?? envelope?.config.diagnostic_headers?.[name], "checkbox")); for (const [name, key] of [["request_id", "diagRequestId"]]) diagInputs.push(field(g, `diag-${name}`, key, diagnostics?.[name] ?? envelope?.config.diagnostic_headers?.[name], "checkbox"));
+2 -2
View File
@@ -41,7 +41,7 @@ use tracing_subscriber::prelude::*;
#[command( #[command(
name = "rust-proxy", name = "rust-proxy",
version, version,
about = "RustProxy - independent Rust reverse proxy 0.24.9" about = "RustProxy - independent Rust reverse proxy 0.24.10"
)] )]
struct Cli { struct Cli {
#[arg( #[arg(
@@ -1515,7 +1515,7 @@ async fn live_logs_command(dir: PathBuf, addr: SocketAddr) -> Result<()> {
} }
}); });
let interactive = io::stdin().is_terminal() && io::stdout().is_terminal(); let interactive = io::stdin().is_terminal() && io::stdout().is_terminal();
println!("RustProxy live logs 0.24.9 - all vhosts. Type 'help' for interactive filters; Ctrl-C or 'quit' exits."); println!("RustProxy live logs 0.24.10 - all vhosts. Type 'help' for interactive filters; Ctrl-C or 'quit' exits.");
if interactive { print!("> "); io::stdout().flush()?; } if interactive { print!("> "); io::stdout().flush()?; }
let mut filters = LiveLogFilters::default(); let mut filters = LiveLogFilters::default();
let mut history = std::collections::BTreeMap::<u64, serde_json::Value>::new(); let mut history = std::collections::BTreeMap::<u64, serde_json::Value>::new();