v0.36.6
This commit is contained in:
1 parent
d7fb869783
commit
0cde2c8a18
21 files changed
+195
-45
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# RustProxy source and documentation maintenance
|
||||
|
||||
Current source version: **0.36.5**. The Cargo package and binary are named `rust-proxy`.
|
||||
Current source version: **0.36.6**. The Cargo package and binary are named `rust-proxy`.
|
||||
|
||||
Before editing, read [README.md](README.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior.
|
||||
|
||||
|
||||
+9
-1
@@ -2,12 +2,20 @@
|
||||
|
||||
Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in the latest release.
|
||||
|
||||
## 0.36.6 — redirect priority, copy and SNI documentation
|
||||
|
||||
- **Deploy script:** `update.sh` / `install.sh` retry the GeoIP download (transient 502/503/504, timeouts) up to four times and, if it still fails, warn and continue with the installed GeoIP data instead of aborting the update with `HTTP Error 504`.
|
||||
- **Redirect priority fix:** the most specific host name now wins between vhosts, redirects and 404 hosts. A wildcard redirect such as `*.gru.one.pl` (or `gru.one.pl`) no longer shadows an exact vhost like `autodiscover.gru.one.pl` on the same listener; the same applies to wildcard 404 hosts.
|
||||
- **Redirect cards:** redirects are shown as host chips → target with a quiet meta line (rules, listeners, certificate) instead of nested boxes, and stack cleanly on narrow screens.
|
||||
- **Certificate mismatch on save:** when the selected certificate does not cover the vhost names, saving now asks whether to force it (and enables "Force an incompatible certificate") instead of failing with a vague error; the server error names the certificate and explains the fix.
|
||||
- **Certificates by SNI explained:** the vhost certificate hint and `docs/CERTIFICATES.md` now describe that a vhost left on "Global SNI / default certificate" gets the certificate per requested name from the stored certificates' SNI names (filled from their SAN entries), so one vhost can serve domains with different certificates. The editor also tells you when no single certificate covers all names but each name is covered. The "Legacy SNI names" field is now called "SNI names".
|
||||
- **Copy:** vhosts, redirects, 404 hosts, streams and ACL profiles have a **Copy** button next to Edit. It opens a new form filled from the original with the ID and name cleared (the ID is generated from the new name or host again) and the values that must be unique cleared too: host names and certificate for vhosts, redirects and 404 hosts, the listen address for streams. Nothing is saved until you press Save.
|
||||
|
||||
## 0.36.5 — automatic certificate choice and clearer host conflicts
|
||||
|
||||
- **Certificate auto-selection:** when creating a vhost, redirect or 404 host, RustProxy picks the stored certificate that covers every host name you enter (an exact name beats a wildcard, the narrowest certificate wins). A hint names the chosen certificate and the selector still lets you pick another one or the global default. Existing entries are never changed.
|
||||
- **Clearer host conflicts:** the "overlapping frontend bindings for hostname …" error now names the entry that already uses the host (`vhost`, `redirect` or `404 host` plus its ID) and the conflicting listener address.
|
||||
- **Clearer validation errors:** duplicate or invalid IDs, repeated hostnames, missing hostnames or listeners, invalid location IDs, regular expressions, exact paths and duplicate location matchers now say which value or location is wrong and how to fix it.
|
||||
- **Deploy script:** `update.sh` / `install.sh` retry the GeoIP download (transient 502/503/504, timeouts) up to four times and, if it still fails, warn and continue with the installed GeoIP data instead of aborting the update with `HTTP Error 504`.
|
||||
|
||||
## 0.36.4 — cache readability and editor UX polish
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -1943,7 +1943,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-proxy"
|
||||
version = "0.36.5"
|
||||
version = "0.36.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rust-proxy"
|
||||
version = "0.36.5"
|
||||
version = "0.36.6"
|
||||
edition = "2021"
|
||||
rust-version = "1.99.1"
|
||||
description = "RustProxy - independent reverse proxy with local administration"
|
||||
|
||||
+20
-20
@@ -1,10 +1,10 @@
|
||||
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/.dockerignore
|
||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/.env.example
|
||||
e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/.gitignore
|
||||
875fa17d64e0cf8f097cd4635ca801686c256d556680a39a9259dcdb1155ed6c /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/AGENTS.md
|
||||
7619bfb852154916eaec3f84ecae54cc58b2ab1607c8a38f158524a5e388a542 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/CHANGELOG.md
|
||||
eae2405a396e300ca4f2997a36239648b74acebf9e724b8c8d095c11969e3fd9 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/Cargo.lock
|
||||
202e5377c7875ef538d420ccb563c26d4cb010dbcd49ce80ca1a1555d267b038 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/Cargo.toml
|
||||
9533bda057fd758323d04b2928ab6c13ee82ed8ed5c9aaa67a0bc293a75ff4d1 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/AGENTS.md
|
||||
39aebadebeafe8a64ce6cbc9b7f9b90e83c6b5767064aa4323a50c85577cf814 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/CHANGELOG.md
|
||||
240f1748925cf041e3bdbb85ff380c64f3a1641943aed23bd762352d625f81aa /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/Cargo.lock
|
||||
168a2e8054b405d0e8baa52e7a80f4f02598f4d4f9446e807ea0a3987522190e /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/Cargo.toml
|
||||
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/Dockerfile
|
||||
0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/LICENSE
|
||||
82d1dee2fc68158f0ea73259151461e64c8b18d807cd1fecfb1ed99dc30cf5f4 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/README.md
|
||||
@@ -31,7 +31,7 @@ e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb /sessions/rcw-
|
||||
5a8821f28a1f529263db462a0ed1f7c082ad91b5936491806913e5cb5b274fd9 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/docs/API.md
|
||||
728b15b9825f547f34c4121219784798dd046fbbdd72ece8906d0cd77e8efc64 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/docs/BACKUP.md
|
||||
4fb55a549535f37467f14536cbf0db7be027ca8a019c2919a384af4f23655300 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/docs/CACHE.md
|
||||
32599d67a11fd0ebbc612e82b9f602789f4ed4103cda482c6870d8741100c8b4 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/docs/CERTIFICATES.md
|
||||
5e7b6668be6bfec77755ae45b9d369aa14135fce7632de392c6b29a7b584245a /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/docs/CERTIFICATES.md
|
||||
02bb383b5c3bce22616c3cc3db30a9469806dc16decb509973e84c60aa30fc5b /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/docs/CONFIGURATION.md
|
||||
fec29ce846d6705eb45819d85d382f28e28a146b265feab2e4c7cce2a35e5883 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/docs/CUSTOM_LOCATIONS.md
|
||||
9c54b8da03adb1bc1a3db96a72fb99924dca25eed5ac662989aba18ca226d066 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/docs/DASHBOARD.md
|
||||
@@ -88,12 +88,12 @@ de6550ab8f48d556d752c252212c38ba65491fd224f972c10fbe552f91ed7520 /sessions/rcw-
|
||||
45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/integrations/routeros-oci-build/.env.routeros.example
|
||||
11aa102de1089ecc375f20c4bd5420fcfd3b2ef23fe0c8bdf04953fcfaf40ef0 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/integrations/routeros-oci-build/Dockerfile.routeros
|
||||
7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/integrations/routeros-oci-build/Dockerfile.routeros.dockerignore
|
||||
8545b871df9ffb6b204f1cf30ba33fb11bd2335287982d464fa6ccd480f7c869 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/integrations/routeros-oci-build/README.md
|
||||
28880995ead34f2f22b43b57525109c5fe915206919509333182d6e514a531cb /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/integrations/routeros-oci-build/README.md
|
||||
3f092a9ab80221eddd67be4d1a776acbc6e04c1eb6011b588280c28e83b2582f /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/integrations/routeros-oci-build/build-routeros-oci.sh
|
||||
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/integrations/routeros-oci-build/docker-entrypoint.sh
|
||||
13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/langs/README.md
|
||||
87d87fb37d3d08ae98b365db24f2fcc64b46b21c3ac5cafdad7720c16d0f3e14 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/langs/en_US.json
|
||||
20989ca82958acaa6859af792176b653008ad962cd3de31a61a9f36000e262c0 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/langs/pl_pl.json
|
||||
64bf3103fa625e687928a0ee125ba679ef25fa972109253afc34790e3c539649 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/langs/en_US.json
|
||||
9a5ea7ced018016fca28c94077828f3ec25d7bb49201ffd70ab0664d45857aa3 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/langs/pl_pl.json
|
||||
9664543e4d03422788a65e42f968c1f026c2235cb5be6fc3e0ad318997077538 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/make_zip.py
|
||||
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/migrations/0002_admin.sql
|
||||
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/migrations/0003_certificate_renewals.sql
|
||||
@@ -165,7 +165,7 @@ d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a /sessions/rcw-
|
||||
77b8c05645969834cc8a58e249a7b60a57d6c47ad1c5cd92ffa5e4a9b5a8e73c /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/file_logs_query.rs
|
||||
743bb7436f77e04015328a0e7e7d425e43ef8aa8d61467bde157e462c1dcdc2e /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/logging_api.rs
|
||||
243f4a9c3fa99781e19c3952354d2632c0ce65e1dafac729567cbe3c177f94cb /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/mod.rs
|
||||
73aeded6ca5a7162000e71c4fe4e210d2e9810d8b9af1877e86e43f0aa896d39 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/openapi.json
|
||||
3a2f3fd6d373f4a5f6085312ec9a0de3a404ad7e219b90bb2e13069106f6f34a /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/openapi.json
|
||||
f7a356109786635dd58cba590c71c796dbf36b47f25845190c75ee5c6a9fcb86 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/operations.rs
|
||||
257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/panel_assets.rs
|
||||
f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/panel_content.rs
|
||||
@@ -189,7 +189,7 @@ faa9735c2a02746b2954bc4955eb609f94afebb685cd5521d2629640cea2777a /sessions/rcw-
|
||||
aed3aea4eef0011c6b51d39736782e7d1ec52662e422f40e0c7b9ddfc19d0378 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/app/certificate-inspection.js
|
||||
f6b0533ffd6d0e6471519120c440619a7fb4b255854ba3dd23d81cb35cd21afb /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/app/chart-scales.js
|
||||
e5b39b5592ba81292571033f26934ac0cd42bcdca7395e91094973cecb3eeaf8 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/app/config-history.js
|
||||
446131e27946dc32df3524e80074f99accc394c004bcfa6cf9770c1b698a0e03 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/app/core.js
|
||||
d5453830975947c453542f4aa175e575a39604902213300fa2db0f77e02df1af /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/app/core.js
|
||||
d695ec90fb5c98c27d7ba1d2bebf54f9840985fc530efcec3fcaf48d74b36c6b /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/app/dashboard-inline-layout.js
|
||||
e4238322a985d3e8b7f2de94b7af31e1c718775590b2c7b3428b878cd43337b7 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/app/dashboard-layout-editor.js
|
||||
a184c50e1ca6bb5a14954e31b15f09ff9978fb939064ca724851bbb4e661dc56 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/app/dashboard-presets.js
|
||||
@@ -214,7 +214,7 @@ b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 /sessions/rcw-
|
||||
ec3ff1b7824a7f03ee6d6b7ae9b6e0cbdb300e4891d44f982b6c04b3870f5c2b /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/css/10-base.css
|
||||
8a1996d37f138790c12a704deedde9de74f733e7dcbb6be816fd68c281e8e0a9 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/css/20-shell.css
|
||||
9495c1b9f6a2cc72c1d99b4ea8c3cf3706835cb4670b53d5d7d71ef41ad62006 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/css/30-overview.css
|
||||
2d703473f7b751b587c32af6954c838a3160e9efa448236b6abe291832486b72 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/css/40-editors.css
|
||||
ca93e41d1c6001d82f4891cacdd712e896a0355d6db9f76cf62dd2f2225a8674 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/css/40-editors.css
|
||||
87df062d7478176641364c07de396ca988a37b1a8bcb892b53cc578fe8471850 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/css/50-settings.css
|
||||
eed482c4b7dd247682c135fac132bb66e501511423ca53d50e9a79f4314cbac5 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/css/60-monitoring.css
|
||||
83cb48e2e4748bff2260dc7d9d84e922781d35812eecb4d2e34831cebb21d015 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/css/70-responsive.css
|
||||
@@ -243,20 +243,20 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c /sessions/rcw-
|
||||
003d382cc8dd69ad7237679632c04a9389366bb71dd7dca8ee64ee06614aa9a5 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/error_auth_provider.html
|
||||
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/favicon.svg
|
||||
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/file-logs.js
|
||||
14377f09e0fd04cca00c4ca58c3a243b10ddb43dcbd0faf58a57209303d73d8e /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/host404.js
|
||||
cd617f7942ed03f814ff828f107c13a50bc1a450ded6d2be744a247a45b18c52 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/host404.js
|
||||
0a7bd0afcc0f65fced7dcfc1dfa1469a8ed6051af274701c23f94885d8e9107d /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/index.html
|
||||
7979c490b7fcfcd8f5e52e99fcf7757d78509337a625341c98f62c6da50da374 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/integrations.js
|
||||
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/login-keys.json
|
||||
ac9f667273918e18406df06cb2613c923f50a88897693a03a18f500e22dc6ec2 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/login.html
|
||||
c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/login.js
|
||||
a74b0d8d9a5aedb4d1ee4b7f7805693ee3cf2152054e35a310bd72e81ea1ca4f /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/management.js
|
||||
44b44790341067304e15974afcbbc7cc99686e88c2e43f62979d285eca6f726e /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/management.js
|
||||
1f3d10e389c9c20e6eb019509fa6e82b50c6b43bbf988c1210a4d42bb73985d0 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/partials/overview-charts.html
|
||||
32b3f9b3ce9d1de928d2f23265d09b65a135fd045d1d24b98e830922be21432f /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/partials/overview-metrics.html
|
||||
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/prometheus-token.js
|
||||
a5c05ab544c7e1ea96f2fd133b55adfd8036837036bed3007259070a6a68e38f /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/redirects.js
|
||||
6f33915d33d3a8bbfaeec424651a63eda3d23b0f296ae028a59ea46463ba9fdf /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/redirects.js
|
||||
76a01f590d49649dcb1f554c221290919b2a8438e6001737f3c096fa6daec74d /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/settings.js
|
||||
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/status_200.html
|
||||
792d59e169991749bd119ec6acb9386182f4fc9be1705e5f9f3f24d7d2bfc057 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/streams.js
|
||||
dc3f01d1bba6fe29223983d869c509ae99bd6a40e362c6ec4605078876a05a2f /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/streams.js
|
||||
ae0259cf32f53d69a22a640b1c34562018d4ffb3864494012de3a31c0bb4ac1e /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/traffic-panel.js
|
||||
675ea169d4cc55fda5e291af536338b882bede1eaa6c9d5cc7dfde0779c7c4d1 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/transform-panel.js
|
||||
87917b0388090f2b27ec6460be56fea96dc09b42735319e0a91330748848e288 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/vhosts/core.js
|
||||
@@ -264,7 +264,7 @@ ae0259cf32f53d69a22a640b1c34562018d4ffb3864494012de3a31c0bb4ac1e /sessions/rcw-
|
||||
3776f72d7051af86ac5fbdd4581040dd88a424c0c6cde789301b032b159094d8 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/vhosts/health.js
|
||||
b3239e67901f1796d7a974ade0796f2e3590ff8064628a5629b78c7f6ce2f32e /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/vhosts/protocol.js
|
||||
8c3da49973c62333559b0e3588c02beb0b425c025eac5cfa82a1bb968f70d9dd /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/vhosts/rates.js
|
||||
903df941cfd04bd2e34178b5b72911f5c0a6be9aa593ce544c685aacbd757da2 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/vhosts/render.js
|
||||
b3b9c94d779f249f09e74894659437649d40f31879d4f3077d5df941d5443454 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/vhosts/render.js
|
||||
4dfdeb7c73c2775eacaca65fd8ef1dc80b275421b15522d319d338f5b96288cc /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/web-server-profiles.js
|
||||
9e8e99e3cdfc32097d0c63dabb19d1e439b3be2264baa67678b46715b3a5bb4e /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/admin/web/web-server.js
|
||||
2d40c2cb32d7365df0c9cd6c8cb6f0483c7a799786c648f4836a0d9740c6c5ef /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/auth/files/mod.rs
|
||||
@@ -345,7 +345,7 @@ fbea01fea1213d98aea812c1b78d5db289e09200b98ae514d742235cd3fa70e1 /sessions/rcw-
|
||||
cc75698be2828000e88f2c482bd26f2fa4050e3432bec1b5a581dca17fef4a37 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/config/model.rs
|
||||
75839458ddbc7480ec4492a95eeb67b617ba7d62f7b67e7a767a3453b74dbcbc /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/config/route_policy_tests.rs
|
||||
e442c6561723e8dbf73d25912227cebf7aa84ac1523e8bda8e6b4bdad1b674df /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/config/routing.rs
|
||||
dd9a6ad8f58e0cdd1ae74f3f972fee0ac14383cd8b8f518403b9a36c169f35bd /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/config/snapshot.rs
|
||||
5bc35ebca158aa399a1d2273c21130e1cfaf5c89cb401689ef863d7deca6b801 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/config/snapshot.rs
|
||||
477cddaa1e1c0b9b605a2f550971a04cf30287519cba70cfbb2e1860e3a93ff2 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/config/tests.rs
|
||||
f35de0ee1516ece6bbfd2b2a7db7d1e0f5048b0ec47cf2a3765dffb799abfd93 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/config/transport_tests.rs
|
||||
a09f361d70510d6226f4dd6785e649c3dc01ccafc70a4c8b0dcd098e224129b7 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/db.rs
|
||||
@@ -433,7 +433,7 @@ d0d64fd4dd89efd4a9fe089ff4551815cbeaf5b883ddcff803f1b13b5e918dc2 /sessions/rcw-
|
||||
ccc54869ffbcf1edae9ddb96f6dc2191739f8804ee0318b7a6844def6c4693e5 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/proxy/forward/success_prepare.rs
|
||||
c965dba1d2639b338f3f3cfc17f760786a30abab708dfb7920ea3bc94d4d734b /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/proxy/forward/upstream.rs
|
||||
5d72200f3b5308215b7a3d7b1f910932eaa4712f9e2954afdfbe2f252e2a2d3c /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/proxy/forward/web_source.rs
|
||||
0b03eed32b00d709e5887c179e49f7bdf0926ac19d2055af511333262039ed19 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/proxy/forward_entry.rs
|
||||
602879a0f8f475fe63b5e27238f4b371277a6780aa970c6b140c8d7fd3074e37 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/proxy/forward_entry.rs
|
||||
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/proxy/h2_graceful.rs
|
||||
30057732c35c369740a176f7da285e35a4157571a04328f6c6f1494d4ec8550b /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/proxy/headers/authority_tests.rs
|
||||
e87437ccf80e50e0159d204dfdd1f8dc0739b6e375be192fce0af749810258cf /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/proxy/headers/mod.rs
|
||||
@@ -498,7 +498,7 @@ df98becda799b6bf4ff5e2fec7d0e0a486e6b7eca6f9f5ad49ffdafd49a4f498 /sessions/rcw-
|
||||
93e0576ac9077e645a72d164bad34cd75fe6032dd0516284d08bddd2fbda4b7c /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/tls/certificates.rs
|
||||
68cc35b3cae43b0321cf667a613310a4b9ec5d8994461d6f3543b1852771bf08 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/tls/external_paths.rs
|
||||
937b95bb8621ab98d7cad3d223e4dee305350fbd0149b7527396d3bdcc28a68b /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/tls/external_paths_tests.rs
|
||||
f02bb2775e662b2d75810f631ad3da4e0b56a64ae72b36c0edeae37e979b0fb5 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/tls/server_config.rs
|
||||
1ff95e95d38eedeefcf8a931569d628763d5a328aa5573c73c928f06795037ba /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/tls/server_config.rs
|
||||
94b70c52fdc7bd6e32fba9eed9124d030c692246f6990d61e01b891765275add /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/tls/tests.rs
|
||||
ce51a3280331d67c8406d2c5bc156ac0b492afa58e1f328bcda76db0720a6a9d /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/tls/types.rs
|
||||
a897c48e3528565fef8d4e32f0c3c078adfb68655c80e1af011f44a8bcfac6f6 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/src/traffic/mod.rs
|
||||
@@ -561,7 +561,7 @@ f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e /sessions/rcw-
|
||||
102b83171a6429b6375d8001d6c5294702385cd58ee55727cb657b1f3566e877 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/tests/panel_icons_browser.py
|
||||
52e61c389a60d147184ee76925cac76e454f8f2e3cb91039bb06e17787ab900f /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/tests/panel_icons_contract.js
|
||||
01af3008be27fabb486c141c67099af104f2725cc56f4c656aea9741a3c150cd /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/tests/panel_interface_contract.py
|
||||
e7b9656dc584e441ca50ac289ec0fd9a37ffc4e7872c486f683e6712c806b9bd /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/tests/panel_refresh_contract.js
|
||||
200802ca30f82d8da50240573251dd976de500034608ad4384ddd95631ec904d /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/tests/panel_refresh_contract.js
|
||||
e24a0f84f1bfc46964df865ac387d651f7b384d781cbc8f3a9a5b0b60f3d69ca /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/tests/vhost_auth_selection.js
|
||||
eca68a07c2b1e45fdb8f512294f5a432db3ae5a6aec8fb200b47f155cd9a31d6 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/tools/README.md
|
||||
7c30c280d0b0a8bf855eeffab583ca34dc161255f7ef6124148aa979c17c26d3 /sessions/rcw-01mmsgszuju6d7f2h8egrxc4/mnt/RustProxy/tools/dev/memory_load.py
|
||||
|
||||
+12
-1
@@ -61,6 +61,17 @@ When another service owns the certificate, add a manual certificate with the act
|
||||
/etc/letsencrypt/live/example.com/privkey.pem
|
||||
```
|
||||
|
||||
### One vhost, several certificates (SNI selection)
|
||||
|
||||
A vhost normally uses one certificate for all of its names. When a single vhost serves domains that have **different** certificates (for example several `autodiscover.<domain>` names, each with its own certificate), leave the vhost certificate on **Global SNI / default certificate**. RustProxy then picks the certificate for every TLS connection by the requested server name (SNI) from the stored certificates whose **SNI names** include it. Wildcard names match one label (`*.example.com` matches `a.example.com`, not `a.b.example.com`).
|
||||
|
||||
1. Open **Certificates**, edit the certificate and open **Existing files / advanced**.
|
||||
2. Check **SNI names**: it is filled automatically from the certificate's SAN entries and can be edited.
|
||||
3. Repeat for each certificate and keep the vhost on **Global SNI / default certificate**.
|
||||
|
||||
While you add host names to a new vhost, the editor selects a stored certificate automatically when one certificate covers every name. When no single certificate does but each name is covered by some stored certificate, it tells you to keep Global SNI. Selecting a certificate explicitly in the vhost overrides the SNI names for that vhost's hosts; if it does not cover them, saving asks whether to force it.
|
||||
|
||||
|
||||
### Permission commands and automatic SNI names
|
||||
|
||||
Select **External files (absolute paths)** and enter both paths. A permission-command block is generated **below the certificate and private-key fields** and changes as the paths change. It uses the supplied paths, resolves symlink targets on the server when run, removes group/other write permission on the resolved PEM files and other-user read permission on the private key, then grants traversal of the relevant parent directories and read access to the files for `rustproxy`. Install the Linux `acl` tools if needed and review/run the commands as an administrator on that server. The panel generates commands; it does not execute them or change filesystem ownership.
|
||||
@@ -69,7 +80,7 @@ The paths must be absolute, without `.`/`..` components or control characters, a
|
||||
|
||||
The existing trusted-path rules still apply: real PEM files and ancestors must be owned by root or the RustProxy service user, ancestors must not be group/world-writable, and a private key must not be world-readable or group/world-writable. ACLs alone do not make an untrusted path acceptable. Container mounts and mandatory access-control rules need matching deployment settings; see [Installation](INSTALL.md#linux-debrpm-read-only-access-to-external-paths).
|
||||
|
||||
After a public certificate becomes readable, the editor automatically fills **Legacy SNI names** from its DNS Subject Alternative Name (SAN) entries. Wildcard SANs are retained; filenames and a certificate's Common Name are not used to invent DNS names. Inspection applies to managed certificate filenames and external paths, reads only the public PEM, and does not activate or save the certificate. The certificate/key pair is still validated when applied.
|
||||
After a public certificate becomes readable, the editor automatically fills **SNI names** from its DNS Subject Alternative Name (SAN) entries. Wildcard SANs are retained; filenames and a certificate's Common Name are not used to invent DNS names. Inspection applies to managed certificate filenames and external paths, reads only the public PEM, and does not activate or save the certificate. The certificate/key pair is still validated when applied.
|
||||
|
||||
Automatic reads preserve manually edited names, intentional clearing and existing saved mappings. Results from an earlier path or form are ignored. Use **Read names from certificate** to retry after fixing filesystem permissions or to deliberately replace the current field with the certificate's names. Prefer explicit certificate assignment in the intended Vhost; auto-filled legacy SNI names do not create Vhosts or replace their assignments.
|
||||
|
||||
|
||||
@@ -77,10 +77,10 @@ ENV_FILE=integrations/routeros-oci-build/.env \
|
||||
bash integrations/routeros-oci-build/build-routeros-oci.sh push
|
||||
```
|
||||
|
||||
For example, with RustProxy version `0.36.5` and the defaults this publishes:
|
||||
For example, with RustProxy version `0.36.6` and the defaults this publishes:
|
||||
|
||||
```text
|
||||
zot.example.net/network/rustproxy:0.36.5
|
||||
zot.example.net/network/rustproxy:0.36.6
|
||||
zot.example.net/network/rustproxy:latest
|
||||
zot.example.net/network/rustproxy:routeros
|
||||
```
|
||||
|
||||
+5
-2
@@ -638,13 +638,13 @@
|
||||
"certCheckDomains": "Domains to verify (comma-separated)",
|
||||
"certAcmeLater": "Issued certificates are imported into the library and assigned per vhost.",
|
||||
"certAdvanced": "Existing files / advanced",
|
||||
"certLegacyNames": "Legacy SNI names (optional; prefer per-vhost assignment)",
|
||||
"certLegacyNames": "SNI names (optional; used when a vhost is set to Global SNI / default certificate)",
|
||||
"certAssigned": "Assigned vhosts",
|
||||
"certUnassigned": "Not assigned",
|
||||
"certInUse": "Certificate is in use. Reassign vhosts and change the default before removal.",
|
||||
"certDuplicate": "This certificate ID exists. Use a new ID for safe rotation.",
|
||||
"certManage": "Manage certificate library",
|
||||
"certSelectHint": "Select an imported certificate. Uploads and rotation are in the Certificates tab. Assignments are validated against vhost names.",
|
||||
"certSelectHint": "Select an imported certificate to use it for every name of this vhost. Leave \"Global SNI / default certificate\" to let RustProxy pick the certificate per requested name (SNI) from the stored certificates whose SNI names or SAN entries include it — useful when one vhost serves domains with different certificates. Uploads and rotation are in the Certificates tab. Assignments are validated against vhost names.",
|
||||
"vhTinyauth": "Authentication",
|
||||
"vhTabAuth": "Authentication",
|
||||
"taUrl": "Tinyauth URL / verification endpoint",
|
||||
@@ -1732,6 +1732,9 @@
|
||||
"autoIdChange": "Change",
|
||||
"autoIdPending": "Generated from the name",
|
||||
"autoIdBadge": "auto",
|
||||
"vhCertificateUncoveredPrompt": "Certificate \"{certificate}\" does not cover: {hosts}.\n\nSave anyway and force this certificate for these names? Browsers will show a name-mismatch warning. Choose Cancel to pick a matching certificate instead.",
|
||||
"autoCertSniHint": "No single certificate covers all names, but each name is covered by a stored certificate. Keep \"Global SNI / default certificate\" and RustProxy will pick the right certificate for each name (SNI).",
|
||||
"copyEntry": "Copy",
|
||||
"autoCertHint": "Certificate \"{id}\" was selected automatically because it covers all host names. You can choose another one.",
|
||||
"dockerLiveAccessLogsHelp": "Live HTTP access logs for this Docker vhost, not the container stdout/stderr.",
|
||||
"networkInterfacesHistory": "Traffic by interface",
|
||||
|
||||
+5
-2
@@ -638,13 +638,13 @@
|
||||
"certCheckDomains": "Domeny do sprawdzenia (po przecinku)",
|
||||
"certAcmeLater": "Wydane certyfikaty trafiaja do biblioteki i sa przypisywane do vhostow.",
|
||||
"certAdvanced": "Istniejące pliki / zaawansowane",
|
||||
"certLegacyNames": "Stare nazwy SNI (opcjonalne; zalecane przypisanie w vhoście)",
|
||||
"certLegacyNames": "Nazwy SNI (opcjonalne; używane, gdy vhost ma Globalne SNI / certyfikat domyślny)",
|
||||
"certAssigned": "Przypisane vhosty",
|
||||
"certUnassigned": "Nieprzypisany",
|
||||
"certInUse": "Certyfikat jest używany. Zmień przypisania vhostów i certyfikat domyślny przed usunięciem.",
|
||||
"certDuplicate": "Ten identyfikator certyfikatu istnieje. Do bezpiecznej rotacji użyj nowego.",
|
||||
"certManage": "Zarządzaj biblioteką certyfikatów",
|
||||
"certSelectHint": "Wybierz zaimportowany certyfikat. Import i rotacja są w zakładce Certyfikaty. Przypisania są sprawdzane względem nazw vhosta.",
|
||||
"certSelectHint": "Wybierz zaimportowany certyfikat, aby użyć go dla wszystkich nazw tego vhosta. Zostaw \"Globalne SNI / certyfikat domyślny\", a RustProxy sam dobierze certyfikat do żądanej nazwy (SNI) spośród zapisanych certyfikatów, których nazwy SNI lub wpisy SAN ją zawierają — przydatne, gdy jeden vhost obsługuje domeny z różnymi certyfikatami. Import i rotacja są w zakładce Certyfikaty. Przypisania są sprawdzane względem nazw vhosta.",
|
||||
"vhTinyauth": "Uwierzytelnianie",
|
||||
"vhTabAuth": "Uwierzytelnianie",
|
||||
"taUrl": "URL Tinyauth / punkt weryfikacji",
|
||||
@@ -1732,6 +1732,9 @@
|
||||
"autoIdChange": "Zmień",
|
||||
"autoIdPending": "Generowane z nazwy",
|
||||
"autoIdBadge": "auto",
|
||||
"vhCertificateUncoveredPrompt": "Certyfikat \"{certificate}\" nie obejmuje: {hosts}.\n\nZapisać mimo to i wymusić ten certyfikat dla tych nazw? Przeglądarki pokażą ostrzeżenie o niezgodności nazwy. Wybierz Anuluj, aby wskazać pasujący certyfikat.",
|
||||
"autoCertSniHint": "Żaden pojedynczy certyfikat nie obejmuje wszystkich nazw, ale każda nazwa jest objęta jakimś zapisanym certyfikatem. Zostaw \"Globalne SNI / certyfikat domyślny\", a RustProxy dobierze właściwy certyfikat dla każdej nazwy (SNI).",
|
||||
"copyEntry": "Kopiuj",
|
||||
"autoCertHint": "Certyfikat \"{id}\" został wybrany automatycznie, bo pokrywa wszystkie nazwy hostów. Możesz wybrać inny.",
|
||||
"dockerLiveAccessLogsHelp": "Logi żądań HTTP dla tego Docker vhosta, nie stdout/stderr kontenera.",
|
||||
"networkInterfacesHistory": "Ruch według interfejsów",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "RustProxy Administration API",
|
||||
"version": "0.36.5",
|
||||
"version": "0.36.6",
|
||||
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only."
|
||||
},
|
||||
"servers": [
|
||||
|
||||
@@ -773,7 +773,8 @@ const rustProxyAutoCertificate = (() => {
|
||||
const list = hosts || current.options.hosts();
|
||||
const best = suggest(list, current.options.certificates());
|
||||
if (select.value !== best) { select.value = best; select.dispatchEvent(new Event("change", { bubbles: true })); }
|
||||
current.hint.hidden = !best; current.hint.textContent = best ? t("autoCertHint").replace("{id}", best) : "";
|
||||
const certificates = current.options.certificates(), splitCoverage = !best && list.length > 1 && list.every(host => certificates.some(cert => suggest([host], [cert])));
|
||||
current.hint.hidden = !best && !splitCoverage; current.hint.textContent = best ? t("autoCertHint").replace("{id}", best) : (splitCoverage ? t("autoCertSniHint") : "");
|
||||
};
|
||||
return { refresh };
|
||||
}
|
||||
|
||||
@@ -2020,3 +2020,20 @@ h2+.fine {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Redirect cards (0.36.5): host chips → target flow plus a quiet meta line, no nested boxes */
|
||||
@media (min-width: 761px) { .redirect-card .vhost-card-head { align-items: center; } }
|
||||
.redirect-flow { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1.4fr); gap: 12px; align-items: center; margin-top: 12px; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--rp-radius-surface, 0); background: var(--bg); }
|
||||
.redirect-flow-from, .redirect-flow-to { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; align-items: center; }
|
||||
.redirect-flow-to { flex-direction: column; align-items: flex-start; gap: 3px; }
|
||||
.redirect-flow-arrow { color: var(--muted); font-size: 16px; line-height: 1; }
|
||||
.redirect-host-chip { max-width: 100%; padding: 2px 6px; border: 1px solid var(--line); border-radius: var(--rp-radius-control, 0); font: 11px/1.45 var(--mono); color: var(--text); overflow-wrap: anywhere; }
|
||||
.redirect-target { font: 12px/1.45 var(--mono); font-weight: 600; color: var(--green); overflow-wrap: anywhere; min-width: 0; }
|
||||
.redirect-target-more { font-size: 11px; color: var(--muted); }
|
||||
.redirect-meta { display: flex; flex-wrap: wrap; gap: 4px 18px; margin-top: 10px; font-size: 11px; color: var(--muted); }
|
||||
.redirect-meta-item { display: inline-flex; gap: 6px; min-width: 0; }
|
||||
.redirect-meta-item b { color: var(--text); font-weight: 600; overflow-wrap: anywhere; }
|
||||
@media (max-width: 760px) {
|
||||
.redirect-flow { grid-template-columns: minmax(0, 1fr); gap: 6px; }
|
||||
.redirect-flow-arrow { transform: rotate(90deg); justify-self: start; }
|
||||
}
|
||||
@@ -82,7 +82,7 @@
|
||||
list.replaceChildren(...entries.map(item => {
|
||||
const card = el("article", undefined, "vhost-card redirect-card"), head = el("div", undefined, "vhost-card-head"), identity = el("div", undefined, "vhost-card-identity"), title = el("div", undefined, "vhost-title-line");
|
||||
title.append(el("strong", item.name || item.id, "entity-title")); if (item.enabled === false) title.append(el("span", t("vhDisabled"), "pill")); identity.append(title, el("div", (item.frontend?.hosts || []).join(" · "), "vhost-hosts")); head.append(identity);
|
||||
const actions = el("div", undefined, "entity-actions"); actions.append(action(t("edit"), () => { window.rustProxyDrafts.prepare(); load(structuredClone(item)); }), action(t("remove"), async () => { if (busy || !(await appConfirm(t("confirmDelete"), { danger: true }))) return; const next = (envelope.config.not_found_hosts || []).filter(value => value.id !== item.id); if (await saveSection("not-found-hosts", next)) { if (editing === item.id) dialog.close(); render(); } })); head.append(actions); card.append(head);
|
||||
const actions = el("div", undefined, "entity-actions"); actions.append(action(t("edit"), () => { window.rustProxyDrafts.prepare(); load(structuredClone(item)); }), action(t("copyEntry"), () => { window.rustProxyDrafts.prepare(); const copy = structuredClone(item); copy.id = ""; copy.name = ""; copy.frontend = { ...(copy.frontend || {}), hosts: [] }; copy.certificate_id = null; load(copy); }), action(t("remove"), async () => { if (busy || !(await appConfirm(t("confirmDelete"), { danger: true }))) return; const next = (envelope.config.not_found_hosts || []).filter(value => value.id !== item.id); if (await saveSection("not-found-hosts", next)) { if (editing === item.id) dialog.close(); render(); } })); head.append(actions); card.append(head);
|
||||
const detail = el("div", undefined, "vhost-detail-strip"); for (const [key, text] of [["notfoundStatus", String(item.page?.status ?? 404)], ["notfoundMode", t(`notfoundMode${(item.page?.kind || "welcome").replace(/^./, c => c.toUpperCase())}`)], ["redirectListeners", (item.frontend?.listener_ids || []).join(", ")], ["vhCertificate", item.certificate_id || t("vhCertificateDefault")]]) { const node = el("div", undefined, "vhost-detail"); node.append(el("span", t(key), "small-label"), el("b", text)); detail.append(node); } card.append(detail); return card;
|
||||
}));
|
||||
if (!entries.length) list.append(el("p", t(query ? "notfoundSearchEmpty" : "notfoundEmpty"), "fine"));
|
||||
|
||||
@@ -376,6 +376,10 @@
|
||||
countryEnabled.checked = cMode !== "off"; countriesInput.value = (profile.country_access?.countries || []).join("\n");
|
||||
setMode(inferMode(profile)); renderRuleRows(profile.acl?.rules || []); $("acl-profile-editing").textContent = t("aclProfileEdit"); dirty = false; syncPolicy();
|
||||
}
|
||||
function copyProfile(profile) {
|
||||
edit(profile); editing = null; $("acl-profile-id").readOnly = false; $("acl-profile-id").value = ""; $("acl-profile-name").value = ""; attachAclProfileId();
|
||||
$("acl-profile-editing").textContent = t("aclProfileNew"); dirty = true; $("acl-profile-name").focus();
|
||||
}
|
||||
function friendlySummary(profile) {
|
||||
const mode = inferMode(profile), countries = profile.country_access?.countries || [], rulesList = profile.acl?.rules || [];
|
||||
if (mode === "allow_exceptions" || mode === "block_exceptions") {
|
||||
@@ -422,7 +426,7 @@
|
||||
attached.append(el("summary", `${t("aclProfileAttached")} (${users.length})`), el("p", users.join(", "), "entity-details"));
|
||||
box.append(attached);
|
||||
}
|
||||
const actions = el("div", undefined, "entity-actions"); actions.append(action(t("edit"), () => edit(profile)), action(t("remove"), async () => {
|
||||
const actions = el("div", undefined, "entity-actions"); actions.append(action(t("edit"), () => edit(profile)), action(t("copyEntry"), () => copyProfile(profile)), action(t("remove"), async () => {
|
||||
if (users.length) { toast(`${t("aclProfileInUse")}: ${users.join(", ")}`, true); return; }
|
||||
if (!(await appConfirm(t("confirmDelete"), { danger: true }))) return; const next = structuredClone(envelope.config); next.acl_profiles = (next.acl_profiles || []).filter(item => item.id !== profile.id); if (await saveSection("acl-profiles", next.acl_profiles || [])) clear();
|
||||
})); box.append(actions); return box;
|
||||
|
||||
@@ -120,11 +120,18 @@
|
||||
entries.sort((left, right) => { const a = sortValue(left), b = sortValue(right); const result = typeof a === "number" && typeof b === "number" ? a - b : String(a).localeCompare(String(b), locale(), { numeric: true, sensitivity: "base" }); return listPrefs.descending ? -result : result; });
|
||||
list.replaceChildren(...entries.map(item => {
|
||||
const card = el("article", undefined, "vhost-card redirect-card"); const head = el("div", undefined, "vhost-card-head"), identity = el("div", undefined, "vhost-card-identity"), title = el("div", undefined, "vhost-title-line");
|
||||
title.append(el("strong", item.name || item.id, "entity-title")); if (item.enabled === false) title.append(el("span", t("vhDisabled"), "pill")); identity.append(title, el("div", (item.frontend?.hosts || []).join(" · "), "vhost-hosts")); head.append(identity);
|
||||
const actions = el("div", undefined, "entity-actions"); actions.append(action(t("edit"), () => { window.rustProxyDrafts.prepare(); load(structuredClone(item)); }), action(t("remove"), async () => { if (busy || !(await appConfirm(t("confirmDelete"), { danger: true }))) return; const next = (envelope.config.redirects || []).filter(value => value.id !== item.id); if (await saveSection("redirects", next)) { if (editing === item.id) dialog.close(); render(); } })); head.append(actions); card.append(head);
|
||||
title.append(el("strong", item.name || item.id, "entity-title")); if (item.enabled === false) title.append(el("span", t("vhDisabled"), "pill")); identity.append(title); head.append(identity);
|
||||
const actions = el("div", undefined, "entity-actions"); actions.append(action(t("edit"), () => { window.rustProxyDrafts.prepare(); load(structuredClone(item)); }), action(t("copyEntry"), () => { window.rustProxyDrafts.prepare(); const copy = structuredClone(item); copy.id = ""; copy.name = ""; copy.frontend = { ...(copy.frontend || {}), hosts: [] }; copy.certificate_id = null; load(copy); }), action(t("remove"), async () => { if (busy || !(await appConfirm(t("confirmDelete"), { danger: true }))) return; const next = (envelope.config.redirects || []).filter(value => value.id !== item.id); if (await saveSection("redirects", next)) { if (editing === item.id) dialog.close(); render(); } })); head.append(actions); card.append(head);
|
||||
const targets = [...new Set((item.rules || []).filter(rule => rule.enabled !== false).map(rule => rule.target).filter(Boolean))];
|
||||
const targetSummary = targets.slice(0, 2).join(" · ") + (targets.length > 2 ? ` · +${targets.length - 2}` : "");
|
||||
const detail = el("div", undefined, "vhost-detail-strip"); for (const [k, v] of [["redirectTarget", targetSummary || "-"], ["redirectRules", String((item.rules || []).length)], ["redirectListeners", (item.frontend?.listener_ids || []).join(", ")], ["vhCertificate", item.certificate_id || t("vhCertificateDefault")]]) { const node = el("div", undefined, "vhost-detail"); node.append(el("span", t(k), "small-label"), el("b", v)); detail.append(node); } card.append(detail); return card;
|
||||
const flow = el("div", undefined, "redirect-flow"), from = el("div", undefined, "redirect-flow-from"), to = el("div", undefined, "redirect-flow-to");
|
||||
for (const host of item.frontend?.hosts || []) from.append(el("span", host, "redirect-host-chip"));
|
||||
if (!targets.length) to.append(el("span", "-", "redirect-target"));
|
||||
for (const target of targets.slice(0, 3)) to.append(el("span", target, "redirect-target"));
|
||||
if (targets.length > 3) to.append(el("span", `+${targets.length - 3}`, "redirect-target-more"));
|
||||
flow.append(from, el("span", "→", "redirect-flow-arrow"), to); card.append(flow);
|
||||
const meta = el("div", undefined, "redirect-meta");
|
||||
for (const [k, v] of [["redirectRules", String((item.rules || []).length)], ["redirectListeners", (item.frontend?.listener_ids || []).join(", ") || "-"], ["vhCertificate", item.certificate_id || t("vhCertificateDefault")]]) { const node = el("span", undefined, "redirect-meta-item"); node.append(el("span", t(k)), el("b", v)); meta.append(node); }
|
||||
card.append(meta); return card;
|
||||
}));
|
||||
if (!entries.length) list.append(el("p", t(query ? "redirectSearchEmpty" : "redirectEmpty"), "fine"));
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
if (stream.enabled === false) title.append(el("span", t("vhDisabled"), "pill"));
|
||||
identity.append(title, el("div", stream.id, "vhost-hosts")); head.append(identity);
|
||||
const actions = el("div", undefined, "entity-actions");
|
||||
actions.append(button("edit", () => { window.rustProxyDrafts.prepare(); load(structuredClone(stream)); }), button("remove", async () => {
|
||||
actions.append(button("edit", () => { window.rustProxyDrafts.prepare(); load(structuredClone(stream)); }), button("copyEntry", () => { window.rustProxyDrafts.prepare(); const copy = structuredClone(stream); copy.id = ""; copy.name = ""; copy.listen = ""; load(copy); }), button("remove", async () => {
|
||||
if (busy || !(await appConfirm(t("streamDelete"), { danger: true }))) return; const next = structuredClone(envelope.config); next.streams = (next.streams || []).filter(item => item.id !== stream.id); if (await saveSection("streams", next.streams || [])) { if (editing === stream.id) load(); render(); }
|
||||
}));
|
||||
head.append(actions); box.append(head);
|
||||
|
||||
@@ -327,7 +327,7 @@ function render() {
|
||||
openButton.disabled = !browseHost || v.enabled === false;
|
||||
const quickToggle = button(v.enabled === false ? "vhQuickEnable" : "vhQuickDisable", () => toggleVhostEnabled(v), `quiet vhost-quick-toggle ${v.enabled === false ? "is-enable" : "is-disable"}`);
|
||||
quickToggle.setAttribute("aria-pressed", String(v.enabled !== false));
|
||||
actions.append(quickToggle, openButton, button("edit", () => { window.rustProxyDrafts.prepare(); load(structuredClone(v)); }), button("remove", async () => {
|
||||
actions.append(quickToggle, openButton, button("edit", () => { window.rustProxyDrafts.prepare(); load(structuredClone(v)); }), button("copyEntry", () => { window.rustProxyDrafts.prepare(); const copy = structuredClone(v); copy.id = ""; copy.frontend = { ...(copy.frontend || {}), hosts: [] }; copy.certificate_id = null; copy.allow_certificate_mismatch = false; copy.www_redirect_target = null; load(copy); }), button("remove", async () => {
|
||||
if (busy || !(await appConfirm(t("vhDelete"), { danger: true }))) return;
|
||||
const next = structuredClone(envelope.config); next.vhosts = (next.vhosts || []).filter(item => item.id !== v.id);
|
||||
if (await saveSection("vhosts", next.vhosts || [])) { if (editing === v.id) load(); render(); }
|
||||
@@ -590,6 +590,15 @@ form.addEventListener("submit", async event => {
|
||||
wwwRedirect: item?.www_redirect === true,
|
||||
wwwTarget: item?.www_redirect_target || null
|
||||
});
|
||||
if (value.certificate_id && !value.allow_certificate_mismatch) {
|
||||
const certificate = [...(envelope?.config.tls?.certificates || []), ...stagedCertificates].find(item => item.id === value.certificate_id);
|
||||
const inventory = certificateInventory.get(value.certificate_id)?.covered_hosts || [], coverage = inventory.length ? inventory : (certificate?.covered_hosts || []);
|
||||
const uncovered = coverage.length ? (value.frontend?.hosts || []).filter(host => !coverage.some(pattern => window.rustProxyAutoCertificate.covers(pattern, host))) : [];
|
||||
if (uncovered.length) {
|
||||
if (!(await appConfirm(t("vhCertificateUncoveredPrompt").replace("{certificate}", value.certificate_id).replace("{hosts}", uncovered.join(", ")), { danger: true }))) { activateTab("ssl"); return; }
|
||||
value.allow_certificate_mismatch = true;
|
||||
}
|
||||
}
|
||||
if (value.allow_certificate_mismatch && (!oldVhost?.allow_certificate_mismatch || certificateScope(value) !== certificateScope(oldVhost)) && !(await appConfirm(t("vhCertificateMismatchConfirm"), { danger: true }))) return;
|
||||
if (next.vhosts.some(v => v.id === value.id && v.id !== editing)) throw new Error(t("vhConflict"));
|
||||
if (editing && value.id !== editing && !(await appConfirm(t("vhIdRenameConfirm").replace("{old}", editing).replace("{new}", value.id), { danger: true }))) return;
|
||||
|
||||
@@ -139,6 +139,73 @@ impl Snapshot {
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Specificity of the vhost frontend matching `host` (an exact name beats any wildcard,
|
||||
/// a longer wildcard suffix beats a shorter one).
|
||||
pub fn frontend_rank(&self, host: &str, local: SocketAddr, tls: bool) -> Option<usize> {
|
||||
let mut rank = None;
|
||||
Self::find_host_pattern(host, |pattern| {
|
||||
let found = self.frontends.get(pattern).is_some_and(|frontends| {
|
||||
frontends
|
||||
.iter()
|
||||
.any(|f| f.bindings.iter().any(|l| l.matches(local, tls)))
|
||||
});
|
||||
if found {
|
||||
rank = Some(host_pattern_rank(pattern, host));
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
rank
|
||||
}
|
||||
/// Specificity of the redirect host matching `host` and `path` (see `frontend_rank`).
|
||||
pub fn redirect_rank(
|
||||
&self,
|
||||
host: &str,
|
||||
path: &str,
|
||||
local: SocketAddr,
|
||||
tls: bool,
|
||||
) -> Option<usize> {
|
||||
let mut rank = None;
|
||||
Self::find_host_pattern(host, |pattern| {
|
||||
let found = self.redirects.get(pattern).is_some_and(|redirects| {
|
||||
redirects.iter().any(|redirect| {
|
||||
redirect
|
||||
.bindings
|
||||
.iter()
|
||||
.any(|binding| binding.matches(local, tls))
|
||||
&& redirect.rules.iter().any(|rule| rule.matches(path, tls))
|
||||
})
|
||||
});
|
||||
if found {
|
||||
rank = Some(host_pattern_rank(pattern, host));
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
rank
|
||||
}
|
||||
/// Specificity of the 404 host matching `host` (see `frontend_rank`).
|
||||
pub fn not_found_rank(&self, host: &str, local: SocketAddr, tls: bool) -> Option<usize> {
|
||||
let mut rank = None;
|
||||
Self::find_host_pattern(host, |pattern| {
|
||||
let found = self.not_found_hosts.get(pattern).is_some_and(|items| {
|
||||
items.iter().any(|item| {
|
||||
item.bindings
|
||||
.iter()
|
||||
.any(|binding| binding.matches(local, tls))
|
||||
})
|
||||
});
|
||||
if found {
|
||||
rank = Some(host_pattern_rank(pattern, host));
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
rank
|
||||
}
|
||||
pub fn redirect_on(
|
||||
&self,
|
||||
host: &str,
|
||||
@@ -204,6 +271,13 @@ impl Snapshot {
|
||||
})
|
||||
}
|
||||
}
|
||||
fn host_pattern_rank(pattern: &str, host: &str) -> usize {
|
||||
if pattern == host {
|
||||
usize::MAX
|
||||
} else {
|
||||
pattern.len()
|
||||
}
|
||||
}
|
||||
fn select_route<'a, F>(
|
||||
routes: &'a [Arc<CompiledRoute>],
|
||||
path: &str,
|
||||
|
||||
@@ -139,8 +139,21 @@ async fn forward_request(
|
||||
}
|
||||
let websocket_legacy_support =
|
||||
handshake.is_some() && frontend.is_some_and(|frontend| frontend.websocket_legacy_support);
|
||||
let redirect = snapshot.redirect_on(&host, &path, local.0, tls);
|
||||
let local_host = snapshot.not_found_host_on(&host, local.0, tls);
|
||||
// The most specific host name wins: an exact vhost such as autodiscover.example.com must not be
|
||||
// shadowed by a wildcard redirect or 404 host (*.example.com) that happens to be evaluated first.
|
||||
let frontend_rank = frontend.and_then(|_| snapshot.frontend_rank(&host, local.0, tls));
|
||||
let redirect = snapshot
|
||||
.redirect_on(&host, &path, local.0, tls)
|
||||
.filter(|_| match (frontend_rank, snapshot.redirect_rank(&host, &path, local.0, tls)) {
|
||||
(Some(vhost), Some(redirect)) => redirect >= vhost,
|
||||
_ => true,
|
||||
});
|
||||
let local_host = snapshot
|
||||
.not_found_host_on(&host, local.0, tls)
|
||||
.filter(|_| match (frontend_rank, snapshot.not_found_rank(&host, local.0, tls)) {
|
||||
(Some(vhost), Some(local_host)) => local_host >= vhost,
|
||||
_ => true,
|
||||
});
|
||||
if request.version() == Version::HTTP_2
|
||||
&& (frontend.is_some_and(|frontend| !frontend.http2_enabled)
|
||||
|| redirect.is_some_and(|(redirect, _)| !redirect.http2_enabled)
|
||||
|
||||
@@ -367,7 +367,7 @@ pub fn compile(settings: &Settings, directory: Option<&Path>) -> Result<Option<A
|
||||
let mut checker = ResolvesServerCertUsingSni::new();
|
||||
checker
|
||||
.add(&validation_name, key.clone())
|
||||
.with_context(|| format!("certificate does not cover {host}; explicit Vhost consent is required to override"))?;
|
||||
.with_context(|| format!("certificate {} does not cover {host}. Select a certificate that covers it, or enable \"Force an incompatible certificate\" in the vhost SSL tab (not available for redirects and 404 hosts)", cert.id))?;
|
||||
} else {
|
||||
tracing::warn!(certificate = %cert.id, hostname = %host,
|
||||
"Explicitly serving a TLS certificate without hostname verification; clients may reject it");
|
||||
|
||||
@@ -51,5 +51,5 @@ const events = openapi.paths["/api/events"].get;
|
||||
const logsFlag = (events.parameters || []).find(parameter => parameter.name === "logs");
|
||||
assert.equal(logsFlag?.schema?.type, "boolean");
|
||||
assert.equal(logsFlag?.schema?.default, true);
|
||||
assert.equal(openapi.info.version, "0.36.5");
|
||||
assert.equal(openapi.info.version, "0.36.6");
|
||||
console.log("Panel refresh/API contract checks: OK");
|
||||
Reference in new issue
Block a user