v0.30.1
This commit is contained in:
1 parent
f3dd34dae4
commit
f213d853e2
28 files changed
+795
-119
No files matched your search
@@ -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.30.0.
|
compatibility. Current source version: 0.30.1.
|
||||||
|
|
||||||
## Delivery
|
## Delivery
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,13 @@
|
|||||||
> test-only changes and packaging implementation notes were moved to
|
> test-only changes and packaging implementation notes were moved to
|
||||||
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
|
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
|
||||||
|
|
||||||
|
## 0.30.1
|
||||||
|
|
||||||
|
- Fix protocol chart legend dots for HTTP and WebSocket (H1/H2/H3) so their colors always follow the configured line palette.
|
||||||
|
- Remove ACME mode and certificate Python diagnostics from About; provide **Settings -> System information** with live OS/process/hardware information, existing runtime/data paths, and actual selected Python/pip/Certbot versions.
|
||||||
|
- Use the existing `sysinfo` library for cross-platform OS/kernel identification instead of Linux-only parsers. Read bounded Python `pyvenv.cfg` and installed pip/Certbot `.dist-info` metadata without launching diagnostic subprocesses; report missing or ambiguous versions as unavailable. Improve Windows `.exe` tool discovery. Keep application settings, SQLite schema 9 and Cold Cache format 3 compatible.
|
||||||
|
- Rust compilation/live acceptance remain required on a Rust-equipped target.
|
||||||
|
|
||||||
## 0.30.0
|
## 0.30.0
|
||||||
|
|
||||||
- Place per-backend health-check editing in the vhost **Health** tab with a selector containing only monitored backends. Keep independent edits when switching, and preserve overrides while health monitoring is disabled. Existing vhost JSON stays compatible.
|
- Place per-backend health-check editing in the vhost **Health** tab with a selector containing only monitored backends. Keep independent edits when switching, and preserve overrides while health monitoring is disabled. Existing vhost JSON stays compatible.
|
||||||
|
|||||||
Generated
+33
-1
@@ -1270,6 +1270,15 @@ dependencies = [
|
|||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
@@ -1294,6 +1303,25 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-foundation"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-io-kit"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
@@ -1688,7 +1716,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-proxy"
|
name = "rust-proxy"
|
||||||
version = "0.30.0"
|
version = "0.30.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
@@ -2147,6 +2175,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
|
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"memchr",
|
||||||
|
"ntapi",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-io-kit",
|
||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rust-proxy"
|
name = "rust-proxy"
|
||||||
version = "0.30.0"
|
version = "0.30.1"
|
||||||
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"
|
||||||
@@ -58,7 +58,7 @@ h3 = { package = "hpx-h3", version = "=2.5.19", features = ["quinn"] }
|
|||||||
quinn = { version = "0.11.12", default-features = false, features = ["runtime-tokio", "rustls-ring"] }
|
quinn = { version = "0.11.12", default-features = false, features = ["runtime-tokio", "rustls-ring"] }
|
||||||
|
|
||||||
metrics-process = { version = "=2.4.3", default-features = false, features = ["dummy"] }
|
metrics-process = { version = "=2.4.3", default-features = false, features = ["dummy"] }
|
||||||
sysinfo = { version = "=0.38.4", default-features = false, features = ["network"] }
|
sysinfo = { version = "=0.38.4", default-features = false, features = ["network", "system"] }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "windows"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "windows"))'.dependencies]
|
||||||
systemstat = "=0.2.7"
|
systemstat = "=0.2.7"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# RustProxy 0.30.0
|
# RustProxy 0.30.1
|
||||||
|
|
||||||
Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration,
|
Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration,
|
||||||
streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools.
|
streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools.
|
||||||
|
|
||||||
**0.30.0 release source.**
|
**0.30.1 release source.**
|
||||||
|
|
||||||
## Layer-4 streams
|
## Layer-4 streams
|
||||||
|
|
||||||
@@ -11,7 +11,14 @@ 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.30.0
|
## What changed in 0.30.1
|
||||||
|
|
||||||
|
- Match H1/H2/H3 legend markers to the corresponding protocol-chart line colors across chart palettes.
|
||||||
|
- Keep **About** focused on the product, author, license and source/package links. Move Python/ACME diagnostics out of the About dialog.
|
||||||
|
- Add **Settings -> System information** (`/settings?tab=system-info`), showing OS release/kernel/architecture where available, CPU, RAM, uptime, process metrics, ACME interpreter/pip/Certbot versions where discoverable from local metadata, ACME mode/state, and data paths. OS/kernel details come from the existing `sysinfo` Rust library. Version display reads bounded `pyvenv.cfg` and Python `.dist-info/METADATA` files at ACME initialization; it does not execute diagnostic commands. Some system-installed tools lack these metadata files, so their versions will be shown as unavailable rather than guessed. Operational ACME checks and certificate issuance still run Python/Certbot as required.
|
||||||
|
- The authenticated status API exposes the extra read-only runtime fields; SQLite schema 9, Cold Cache format 3 and existing configurations are unchanged. Real Rust compilation and live integration verification are still required.
|
||||||
|
|
||||||
|
## Previously in 0.30.0
|
||||||
|
|
||||||
- Edit individual active health-check policies in **Vhosts → Health**. The backend selector lists only members with monitoring enabled in **Backends**. Choose a member and optionally customize its TCP/HTTP mode, path, Host, interval, timeout, thresholds and expected status range. Other backends retain their unsaved edits; disabling monitoring hides its editor but preserves the saved override for future re-enablement. No API or SQLite schema changes.
|
- Edit individual active health-check policies in **Vhosts → Health**. The backend selector lists only members with monitoring enabled in **Backends**. Choose a member and optionally customize its TCP/HTTP mode, path, Host, interval, timeout, thresholds and expected status range. Other backends retain their unsaved edits; disabling monitoring hides its editor but preserves the saved override for future re-enablement. No API or SQLite schema changes.
|
||||||
- Improve sustained-memory handling: reclaim selective PURGE-generation bookkeeping, avoid unnecessary analytics-label allocations, reuse serialized access JSON across JSONL/Logstash/Loki, and avoid duplicate directory listing vectors/HTML buffers. Stored Logs discovery retains at most 256 per-vhost and 64 rotated-gzip candidates. See [memory stress verification](docs/MEMORY_STRESS.md) for the audit and real-world RSS/load test procedure; source-only checks cannot rule out runtime leaks.
|
- Improve sustained-memory handling: reclaim selective PURGE-generation bookkeeping, avoid unnecessary analytics-label allocations, reuse serialized access JSON across JSONL/Logstash/Loki, and avoid duplicate directory listing vectors/HTML buffers. Stored Logs discovery retains at most 256 per-vhost and 64 rotated-gzip candidates. See [memory stress verification](docs/MEMORY_STRESS.md) for the audit and real-world RSS/load test procedure; source-only checks cannot rule out runtime leaks.
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# RustProxy roadmap
|
# RustProxy roadmap
|
||||||
|
|
||||||
|
## 0.30.1 - Protocol legend and system information
|
||||||
|
|
||||||
|
- [x] Color the H1/H2/H3 legend dots consistently with their themed chart strokes.
|
||||||
|
- [x] Remove ACME/Python runtime details from About and provide a read-only System information settings tab.
|
||||||
|
- [x] Read available ACME Python/pip/Certbot version metadata and OS/hardware/process details with existing libraries, without subprocesses for information gathering or new configuration fields.
|
||||||
|
- [ ] Compile and run Rust tests, verify OS/kernel and metadata-based ACME versions on Linux, BSD, macOS and Windows, and perform live UI/status checks before production acceptance.
|
||||||
|
|
||||||
## 0.30.0 - Per-backend Health editor and logging resource review
|
## 0.30.0 - Per-backend Health editor and logging resource review
|
||||||
|
|
||||||
- [x] Provide an intuitive Health-tab backend selector restricted to monitored members, with independently editable TCP/HTTP policies.
|
- [x] Provide an intuitive Health-tab backend selector restricted to monitored members, with independently editable TCP/HTTP policies.
|
||||||
|
|||||||
+27
-24
@@ -1,18 +1,18 @@
|
|||||||
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
|
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
|
||||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||||
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
||||||
14cfdff48d3df448f246af54f80ddb5c4dc906f824fab7e6c219406243714cc3 AGENTS.md
|
2af2dd8a13e8e1f16639fbca5b72ad0d82f6abf3a2e3ac146341a6a007547904 AGENTS.md
|
||||||
793c2a2ebd9168745f39559d2ac4658517d407d578beee54d9c70594cb75c3c9 CHANGELOG.md
|
64179555a0e536fa58e4e265ad98cdd09e3809b81cdf19ad0ba8609c35d28ddc CHANGELOG.md
|
||||||
fc20847abf0b96bdcb54ddbfb1df4b134301f15fa2cbfd4ea11dd4d368296484 Cargo.lock
|
e0409a737959eb4b8122efa279401eda5b3b78155a6e9d2a4fdc891bfb4a1754 Cargo.lock
|
||||||
d5d77528a9703ee0a0a0c736267f856a7caaf9400a9b9c6a411589feea7a18c3 Cargo.toml
|
125a8a3c3f785675190518b398f64aee12fb0c82120d29be61012865391b3636 Cargo.toml
|
||||||
75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile
|
75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile
|
||||||
39e7600cbf426c2f780b58b6d9c9f26726388b34146a38af815c7ba933d61dd7 README.md
|
b8aca9e4be342f43b64a2eb04522c6582243edbfb764457afdf58ee894131480 README.md
|
||||||
0a5d19aa357cb6645ee6b48926201155510fabf3c7da07c90446f9e8b405a82e ROADMAP.md
|
40f21bcecdf85e951d07059fe5bd5a49b844a9f22e943410534a0d8b308a26ac ROADMAP.md
|
||||||
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
||||||
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
||||||
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
|
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
|
||||||
4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service
|
4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service
|
||||||
2661e66c34cdfcd6d6460b5c6e8194724d17d98b4fdb9e2c4020d9ff3087479c dev.sh
|
550197e512e5a3b3c1dec66f69ab31368e82a317dfac653ecd60b11dac45195e dev.sh
|
||||||
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
|
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
|
||||||
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
|
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
|
||||||
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
|
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
|
||||||
@@ -32,7 +32,7 @@ f054206a09bed81018708608cdeecbfd09018ee7cec9d3abc038f8a37d47b815 docs/API_ACCES
|
|||||||
4c5288c9ed427cfb919bd023ed9a321dcf6d64bc4cad3e1768393356c5d835ba docs/ARCHITECTURE.md
|
4c5288c9ed427cfb919bd023ed9a321dcf6d64bc4cad3e1768393356c5d835ba docs/ARCHITECTURE.md
|
||||||
598298303036912c19e0a4be1431acc7b7d3df7333f3e756e8268ac0e8c16921 docs/CACHE.md
|
598298303036912c19e0a4be1431acc7b7d3df7333f3e756e8268ac0e8c16921 docs/CACHE.md
|
||||||
9113f3abbfe81bafc4855d8680df8aa2a381ecc358cc3e1a0f78b6b756421117 docs/CACHE_DIAGNOSTICS.md
|
9113f3abbfe81bafc4855d8680df8aa2a381ecc358cc3e1a0f78b6b756421117 docs/CACHE_DIAGNOSTICS.md
|
||||||
d069a1ffcc2c791ca0ea0a26b2533b694db1ff48f416e6e7a5525236da3f8cbe docs/CHANGELOG_TECHNICAL_ARCHIVE.md
|
29cc1237ab205fdbe1cc0afc0e905c211048d9b4de2c349d1e4a8427bd53c001 docs/CHANGELOG_TECHNICAL_ARCHIVE.md
|
||||||
c8e69e492179ce39958cc63615cf46c4ac08cfdb7f3483aaab66868a3abcd687 docs/CONTAINER.md
|
c8e69e492179ce39958cc63615cf46c4ac08cfdb7f3483aaab66868a3abcd687 docs/CONTAINER.md
|
||||||
befa50579e3475b22589f26380d85b3345b80e4fa947d49649a3b91a8dd8b8c2 docs/CUSTOM_LOCATIONS.md
|
befa50579e3475b22589f26380d85b3345b80e4fa947d49649a3b91a8dd8b8c2 docs/CUSTOM_LOCATIONS.md
|
||||||
16d8f59908c66e595d6003495cbf5c068c68b10aa9495eb8612c38b2de0e4a7c docs/DOCKER_DISCOVERY.md
|
16d8f59908c66e595d6003495cbf5c068c68b10aa9495eb8612c38b2de0e4a7c docs/DOCKER_DISCOVERY.md
|
||||||
@@ -60,9 +60,9 @@ a2bb837cdd3559d58158880c1f832badf64035376a3623050bb4e40c2bca104c docs/ROADMAP_H
|
|||||||
759bf2987f2b602488829d1856fa33d57c2a3b23bc681ef5a931b9633af3a533 docs/SECRET_STORE_BACKUP.md
|
759bf2987f2b602488829d1856fa33d57c2a3b23bc681ef5a931b9633af3a533 docs/SECRET_STORE_BACKUP.md
|
||||||
5dbdf45fddba198a28f511dbbd2f12bf7d2b88c41c3cbedbc20dfea3858779d3 docs/SECURITY.md
|
5dbdf45fddba198a28f511dbbd2f12bf7d2b88c41c3cbedbc20dfea3858779d3 docs/SECURITY.md
|
||||||
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
|
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
|
||||||
d5c5c59f6e63b8d83dad5d159657c4d4039dfeb4cc1d8af210169b576adbf7e2 docs/SETTINGS.md
|
4c584a657606f0423d40670b72c8c95c814368be4f6635b4d6adc8c729780d0f docs/SETTINGS.md
|
||||||
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
|
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
|
||||||
b4c88af2c27c8dea853ea55bc451ca4cca2a59ec086b59e52ade1227794ac097 docs/TEST_REPORT.md
|
ff24c09a3bde9607001fdc49acbb71b413a1c6479c8a59252da821492d7593f5 docs/TEST_REPORT.md
|
||||||
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
|
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
|
||||||
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
|
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
|
||||||
660687fc961f71aae2bdf71595aa47a336fa782f4e78ceee8c1d1e63e46393ee docs/TRANSPORT.md
|
660687fc961f71aae2bdf71595aa47a336fa782f4e78ceee8c1d1e63e46393ee docs/TRANSPORT.md
|
||||||
@@ -134,8 +134,8 @@ cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/r
|
|||||||
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
|
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
|
||||||
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
|
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
|
||||||
71cde48daa538431dfac51e438b1ed23b061abfdb4e144ba704237d75ac6270c langs/README.md
|
71cde48daa538431dfac51e438b1ed23b061abfdb4e144ba704237d75ac6270c langs/README.md
|
||||||
400b61050ff437aa8476f05a9cd05be6f3b8b2853ddf563591712d118bc95199 langs/en_US.json
|
86b95d536c1e81883f9e4002c7e72f19976436d5ece285cb103432b195ac648f langs/en_US.json
|
||||||
2b4564cfdacfc0ad7e5ec1554bae3af09842a77a45cece7652044506787c8135 langs/pl_pl.json
|
6a4e4fd967d29c38ee7e9a373004db85400bb8a1ecb51e1842f2da93b6247353 langs/pl_pl.json
|
||||||
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py
|
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae 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
|
||||||
@@ -172,8 +172,9 @@ ea272433e7f92e983509a8ab1aa54242440abef21df526c2f99944ef279e09b9 src/acl/tests.
|
|||||||
c5334de6c732d76b2bea7bfc17b3ac305a250bbaad31a5420d6a7d7abb200449 src/acme/providers.rs
|
c5334de6c732d76b2bea7bfc17b3ac305a250bbaad31a5420d6a7d7abb200449 src/acme/providers.rs
|
||||||
b9e57db4bd80ef37564dfc533a96897d8a494671d53acea9fb4bf28d23ba370d src/acme/providers/tests.rs
|
b9e57db4bd80ef37564dfc533a96897d8a494671d53acea9fb4bf28d23ba370d src/acme/providers/tests.rs
|
||||||
29ff0647417b0f1d50a4cc075ab51afecff9ddf47db3eb7948d51db5a38f33f3 src/acme/runtime_bootstrap.rs
|
29ff0647417b0f1d50a4cc075ab51afecff9ddf47db3eb7948d51db5a38f33f3 src/acme/runtime_bootstrap.rs
|
||||||
c8941a929dfef9e8875adf534394ca01a1532e567d5099fefe3657826886ee25 src/acme/runtime_env.rs
|
87f64198770ae026c03a242fa542db2590ca734b02e756f1e5c28d66236f753a src/acme/runtime_env.rs
|
||||||
7acbaf34e7b953b753ab074893e47b5024f0cd756dc4c7a1074067dd936c2e8e src/acme/tests.rs
|
f8075a5ef9ae5651d72a33ac0f0fd5b44ad0ae30f6e2d2318412177e0ed837f5 src/acme/runtime_versions.rs
|
||||||
|
e38331a1b186b49fe9d04051de52b603e1fa028def37e4e2f52e386b4d7b29ac src/acme/tests.rs
|
||||||
bc9eb32f0d2785bac122d5eff44742cd24306c503daa065564383d46416b2211 src/admin/access.rs
|
bc9eb32f0d2785bac122d5eff44742cd24306c503daa065564383d46416b2211 src/admin/access.rs
|
||||||
019f4aa33e3596bc9c11ef8cf5703bcfffe46c15288c0eba83c8dd9e38055fb2 src/admin/certificates.rs
|
019f4aa33e3596bc9c11ef8cf5703bcfffe46c15288c0eba83c8dd9e38055fb2 src/admin/certificates.rs
|
||||||
1de4c7c116eeab2452ccccbaba3f2b7b63c6eb1c1e146ab870d19c5a5ca16f80 src/admin/documentation.rs
|
1de4c7c116eeab2452ccccbaba3f2b7b63c6eb1c1e146ab870d19c5a5ca16f80 src/admin/documentation.rs
|
||||||
@@ -184,13 +185,13 @@ f84cdf7dc40553b6c5086446bafd5c700496f048230ebd95a01839858adff071 src/admin/docu
|
|||||||
d7aa68a78a4cb5a61342d92c1f3209af230d0f9ca3fea1115f67078372e6f37c src/admin/file_logs_io.rs
|
d7aa68a78a4cb5a61342d92c1f3209af230d0f9ca3fea1115f67078372e6f37c src/admin/file_logs_io.rs
|
||||||
b3e2c11dbc11bb8930117afdac64779111acc2ca7b2857f6e359a5108fc9ced8 src/admin/logging_api.rs
|
b3e2c11dbc11bb8930117afdac64779111acc2ca7b2857f6e359a5108fc9ced8 src/admin/logging_api.rs
|
||||||
58802528128f53e493f7339063433a747b605007a8842b5efc670132d897d6f7 src/admin/mod.rs
|
58802528128f53e493f7339063433a747b605007a8842b5efc670132d897d6f7 src/admin/mod.rs
|
||||||
13aa83f3a10b19205c63349c964b2e86322c1d0486c2d1d52429080ac9d9d401 src/admin/openapi.json
|
53347ce9fcb8ce297ff3565ac3d2af5060a56eb6fff51eeac4791c0bd9f5b296 src/admin/openapi.json
|
||||||
6c2b9a0ecb2f4172f110a363c22a6be32895dbcff9605fe7aa94efe91e0a536a src/admin/operations.rs
|
6c2b9a0ecb2f4172f110a363c22a6be32895dbcff9605fe7aa94efe91e0a536a src/admin/operations.rs
|
||||||
5287fff6b3ad436ac695e9b66feb1b0b5705b744a9f00cec7ea6a3e951e5eebf src/admin/preferences.rs
|
5287fff6b3ad436ac695e9b66feb1b0b5705b744a9f00cec7ea6a3e951e5eebf src/admin/preferences.rs
|
||||||
31bd90ee5f5ec2984920225ff6b57e07db3e0758092fb55d7c230e3590c1b015 src/admin/public.rs
|
31bd90ee5f5ec2984920225ff6b57e07db3e0758092fb55d7c230e3590c1b015 src/admin/public.rs
|
||||||
9edf51cfbeb51ec5062fa24803247f6117a45dfe9ac731215470c3d034296020 src/admin/router.rs
|
9edf51cfbeb51ec5062fa24803247f6117a45dfe9ac731215470c3d034296020 src/admin/router.rs
|
||||||
8708427263cd4caf8454b9115e89acb09172483a090c6b0bd43811dd9e52c7f6 src/admin/session.rs
|
8708427263cd4caf8454b9115e89acb09172483a090c6b0bd43811dd9e52c7f6 src/admin/session.rs
|
||||||
bebf42cd1808a99a0e669d483951a441ffa22c8d5a0a289c64491adf13c8841a src/admin/status.rs
|
c7dd05f83efccb8df5a8e4ea22bda072643e9d9cd52610d4c8a68f072270903b src/admin/status.rs
|
||||||
cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs
|
cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs
|
||||||
5a8e2c4d0065c98f4686105c6f00362dd515f70f1ed662187f943ba4b76c54ad src/admin/tests.rs
|
5a8e2c4d0065c98f4686105c6f00362dd515f70f1ed662187f943ba4b76c54ad src/admin/tests.rs
|
||||||
5df98246c46135a176b4b8b705c92fd328719e82eb7342f088256abb976d5ae7 src/admin/tests/part_01.rs
|
5df98246c46135a176b4b8b705c92fd328719e82eb7342f088256abb976d5ae7 src/admin/tests/part_01.rs
|
||||||
@@ -203,7 +204,7 @@ cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail
|
|||||||
54ed2130e85239ffa4576417dd68fbb9349e396210b75f5da862897137d7e876 src/admin/tests/part_08.rs
|
54ed2130e85239ffa4576417dd68fbb9349e396210b75f5da862897137d7e876 src/admin/tests/part_08.rs
|
||||||
2ae5bc857dd62882f16d1cfcee9691c9ba8afc149f4c0435addd48b564487517 src/admin/web/acme.js
|
2ae5bc857dd62882f16d1cfcee9691c9ba8afc149f4c0435addd48b564487517 src/admin/web/acme.js
|
||||||
446011c2c7afddf7734ceb268c75a3b4146dbe8e915b11986495e956b6c62c69 src/admin/web/app/config-history.js
|
446011c2c7afddf7734ceb268c75a3b4146dbe8e915b11986495e956b6c62c69 src/admin/web/app/config-history.js
|
||||||
ed063b8a414cf3b5e539c900e8722aa82d84c561a5cd54d875650793a25529e2 src/admin/web/app/core.js
|
bd55186a8b02507cffe9fbd2dfb3cc5d3c54509a2a13853b5d93c5f6ec76e688 src/admin/web/app/core.js
|
||||||
cb5c4c2dd4964795b61393b05537cc1528c72d7c1390953f4d119a5e55c65c7c src/admin/web/app/preferences.js
|
cb5c4c2dd4964795b61393b05537cc1528c72d7c1390953f4d119a5e55c65c7c src/admin/web/app/preferences.js
|
||||||
de17987bda89dfab54b6751007474ff9e7cc8da35f7e9c46a2eff922a50fb085 src/admin/web/app/runtime.js
|
de17987bda89dfab54b6751007474ff9e7cc8da35f7e9c46a2eff922a50fb085 src/admin/web/app/runtime.js
|
||||||
887e34c7e4b3e197e54151f4bb48932e4f7b9f8e64e3d3e8c9c7ee0835fbe4b0 src/admin/web/app/settings.js
|
887e34c7e4b3e197e54151f4bb48932e4f7b9f8e64e3d3e8c9c7ee0835fbe4b0 src/admin/web/app/settings.js
|
||||||
@@ -223,16 +224,16 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
|
|||||||
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
|
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
|
||||||
af6e7a2e890df2fb7385a374bcb3fba52fb5a53dbe18adc1fb7865660fcba842 src/admin/web/file-logs.js
|
af6e7a2e890df2fb7385a374bcb3fba52fb5a53dbe18adc1fb7865660fcba842 src/admin/web/file-logs.js
|
||||||
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
|
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
|
||||||
62fe27711ced37f0c4908c84b42ab29336ac2af207fecf5c7eb5557c30ce2868 src/admin/web/index.html
|
8a7f5ba3a7d5f0810351a540b829377b8a4a20fd40f6a955529e2d2839ffe328 src/admin/web/index.html
|
||||||
24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js
|
24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js
|
||||||
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
|
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
|
||||||
af0fbd94d9b88ce1cce28e39dfd2c35de031adcba412dd4a9a17d9b416c256dd src/admin/web/login.html
|
af0fbd94d9b88ce1cce28e39dfd2c35de031adcba412dd4a9a17d9b416c256dd src/admin/web/login.html
|
||||||
99fe9b8ba7a8a24e5bbe9ae40999484fe13b0faf5eb1d7a9193701f667e51ca5 src/admin/web/login.js
|
99fe9b8ba7a8a24e5bbe9ae40999484fe13b0faf5eb1d7a9193701f667e51ca5 src/admin/web/login.js
|
||||||
810e7e5bf1af302b51d90771c3ec3c20227eca392f8c7d91d027e11a999bc236 src/admin/web/management.js
|
810e7e5bf1af302b51d90771c3ec3c20227eca392f8c7d91d027e11a999bc236 src/admin/web/management.js
|
||||||
90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.js
|
90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.js
|
||||||
7fc4c3661524f11448bc43ebe8f494ed44b8b0a414e63ce5bb8717f314c33808 src/admin/web/settings.js
|
f5ba448163d462ab1f0d421686d1af7ae465b1e5bd78e38fc0899cf0f6f5d13f src/admin/web/settings.js
|
||||||
bb67e77af5514478485cfb69f339d63d7eb0cbadd093fa054814a416260ccc1d src/admin/web/streams.js
|
bb67e77af5514478485cfb69f339d63d7eb0cbadd093fa054814a416260ccc1d src/admin/web/streams.js
|
||||||
db0ea03125967ea35c4fa6a4d9284fa5dcc5782cd655fcdbc4254ce3e24b7133 src/admin/web/style.css
|
b12ce34bcd2617b52bb38f9dfe9be5e1f5c11304c6c18b77a7cb379fa1cafd88 src/admin/web/style.css
|
||||||
a8a73a4fa849ae49391fcd6143e900c515c8b577f71b55cbae9701fa9f5bbe47 src/admin/web/traffic-panel.js
|
a8a73a4fa849ae49391fcd6143e900c515c8b577f71b55cbae9701fa9f5bbe47 src/admin/web/traffic-panel.js
|
||||||
e686d68fd6a368110a85dc4c5dc64e8093950d20d63e9aa44790c03dea6fc193 src/admin/web/transform-panel.js
|
e686d68fd6a368110a85dc4c5dc64e8093950d20d63e9aa44790c03dea6fc193 src/admin/web/transform-panel.js
|
||||||
57a2b975716bdab32c33ec1bf9427b25998550e81ce191440ee66ffb7e2a249e src/admin/web/vhosts/core.js
|
57a2b975716bdab32c33ec1bf9427b25998550e81ce191440ee66ffb7e2a249e src/admin/web/vhosts/core.js
|
||||||
@@ -503,18 +504,20 @@ b04362ad082a32c9ccc4cf38c46f44d71347adecf7aba3224b3af3ea4872e3d0 tools/README.m
|
|||||||
358c1e94dd2b4a756430e5335747f0b437345b1713670a9972c5bcf78b837ccb tools/audit/admin_audit.py
|
358c1e94dd2b4a756430e5335747f0b437345b1713670a9972c5bcf78b837ccb tools/audit/admin_audit.py
|
||||||
412255d383fcc8306c6086784fb416d9814e3ec790e440a0927431ca615f3411 tools/checks/backend_cache_0297_ui_test.mjs
|
412255d383fcc8306c6086784fb416d9814e3ec790e440a0927431ca615f3411 tools/checks/backend_cache_0297_ui_test.mjs
|
||||||
2a493dbce2991a23d807c01952f58608a364f541560836b4cb385e75e108e9e7 tools/checks/backend_health_0300_ui_test.mjs
|
2a493dbce2991a23d807c01952f58608a364f541560836b4cb385e75e108e9e7 tools/checks/backend_health_0300_ui_test.mjs
|
||||||
e36b2774f9e2510c760d885cb116bda1e9a14a13f477c96c250f3532258add8f tools/checks/check_source.py
|
84c090840926fb8fc5ed74aa779e7ca028bc0c684ca5ca15f5ae105e65a8dd3b tools/checks/check_source.py
|
||||||
297dc71c84b5ead2b6e7cef6469596854f5d5e21a83d6b9b3861ea45cf23f672 tools/checks/docs_check.py
|
5362f6c455cf697aacb59336d6664df0fd2a36956bedbbdcb090887ff20b7049 tools/checks/docs_check.py
|
||||||
e33a9cc51d58bc92a637c0e6a1c88cbd24ba4605be55748212e296c3c35a4060 tools/checks/high_traffic_memory_model.py
|
e33a9cc51d58bc92a637c0e6a1c88cbd24ba4605be55748212e296c3c35a4060 tools/checks/high_traffic_memory_model.py
|
||||||
4f7821e7e25b09ce251d25a0b3fb4d8ce8396c36a5ed971c2637aeb325425909 tools/checks/langs_check.py
|
4f7821e7e25b09ce251d25a0b3fb4d8ce8396c36a5ed971c2637aeb325425909 tools/checks/langs_check.py
|
||||||
f8f87ba891338b565249d02c3cf5f02cec44e606b4205de99fcbea1eb2c24d57 tools/checks/memory_contracts.py
|
f8f87ba891338b565249d02c3cf5f02cec44e606b4205de99fcbea1eb2c24d57 tools/checks/memory_contracts.py
|
||||||
456f92623672ec6a7e891e4a642cc6f0b75c38f930e698d9401e3fd906cb3bc6 tools/checks/preflight.py
|
c2e9b21e6c4acdff5d846843fbf7a3babeeed10b9b59ed001c519feea815d90e tools/checks/preflight.py
|
||||||
2478e8dbb056830ddc6b39abc4bdf97978b6024973c59f21fa933992dfd8b86b tools/checks/release_smoke.py
|
32ae78d788aa841e2c01b792aab9c2cf3f075a1508f8abcde4499153c5f778df tools/checks/release_smoke.py
|
||||||
f69845bc26ec33160fc756953991cf32aa8ad6a7fb4a131caa2093db91e783ef tools/checks/rust_module_contracts.py
|
f69845bc26ec33160fc756953991cf32aa8ad6a7fb4a131caa2093db91e783ef tools/checks/rust_module_contracts.py
|
||||||
ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed tools/checks/rust_source.py
|
ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed tools/checks/rust_source.py
|
||||||
adf84a983e0dbba228e47d71ec2d5c9964f7ca9989d5139de58f96ee961b035a tools/checks/security_contracts.py
|
adf84a983e0dbba228e47d71ec2d5c9964f7ca9989d5139de58f96ee961b035a tools/checks/security_contracts.py
|
||||||
23b9250245e6bec5ef728c73cee4c88f64924afc3597f8a9b58a1924235df98e tools/checks/security_dependency_contracts.py
|
23b9250245e6bec5ef728c73cee4c88f64924afc3597f8a9b58a1924235df98e tools/checks/security_dependency_contracts.py
|
||||||
2e125a2a807553ac81d04428f337f5042eed968cb807244487114fdd8ea6b5d9 tools/checks/source_module_contracts.py
|
2e125a2a807553ac81d04428f337f5042eed968cb807244487114fdd8ea6b5d9 tools/checks/source_module_contracts.py
|
||||||
|
37dafd2db24beba50fa155c302299dc4a8a8171531f86ac5b4e53bb0e269c1f7 tools/checks/system_info_0301_ui_test.mjs
|
||||||
|
7505e10a00969ea705987dd68c6ba6f5c556e0b2d5bde9926ac4f41e72381700 tools/checks/system_info_no_subprocess_contracts.py
|
||||||
987b4c593b51a7c340d8f01f9a2eea1387043cdd997eeb932c8341bbb8c0bd64 tools/checks/version_contracts.py
|
987b4c593b51a7c340d8f01f9a2eea1387043cdd997eeb932c8341bbb8c0bd64 tools/checks/version_contracts.py
|
||||||
2020e171be1a044f7ca2ab83af7604a014c36fe4ff04791d0620ccbf1bf8e000 tools/checks/vhost_origin_errors_0297_ui_test.mjs
|
2020e171be1a044f7ca2ab83af7604a014c36fe4ff04791d0620ccbf1bf8e000 tools/checks/vhost_origin_errors_0297_ui_test.mjs
|
||||||
0c071c26fc316934bdfd2bfd19f5fed9521790d8c887760eaabda81a9be96703 tools/checks/vhost_www_ui_test.mjs
|
0c071c26fc316934bdfd2bfd19f5fed9521790d8c887760eaabda81a9be96703 tools/checks/vhost_www_ui_test.mjs
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ python3 tools/checks/preflight.py --repair-known
|
|||||||
if [[ "$mode" == "repair" ]]; then printf 'Source preflight: OK\n'; exit 0; fi
|
if [[ "$mode" == "repair" ]]; then printf 'Source preflight: OK\n'; exit 0; fi
|
||||||
if [[ "$mode" == "test" ]]; then
|
if [[ "$mode" == "test" ]]; then
|
||||||
python3 tools/checks/rust_module_contracts.py
|
python3 tools/checks/rust_module_contracts.py
|
||||||
|
python3 tools/checks/system_info_no_subprocess_contracts.py
|
||||||
python3 tools/checks/source_module_contracts.py
|
python3 tools/checks/source_module_contracts.py
|
||||||
python3 tools/checks/version_contracts.py
|
python3 tools/checks/version_contracts.py
|
||||||
python3 tools/checks/check_source.py
|
python3 tools/checks/check_source.py
|
||||||
@@ -103,6 +104,7 @@ if [[ "$mode" == "test" ]]; then
|
|||||||
node tools/checks/web_service_ui_test.mjs
|
node tools/checks/web_service_ui_test.mjs
|
||||||
node tools/checks/backend_cache_0297_ui_test.mjs
|
node tools/checks/backend_cache_0297_ui_test.mjs
|
||||||
node tools/checks/backend_health_0300_ui_test.mjs
|
node tools/checks/backend_health_0300_ui_test.mjs
|
||||||
|
node tools/checks/system_info_0301_ui_test.mjs
|
||||||
node tools/checks/vhost_origin_errors_0297_ui_test.mjs
|
node tools/checks/vhost_origin_errors_0297_ui_test.mjs
|
||||||
node tools/checks/www_redirect_ui_test.mjs
|
node tools/checks/www_redirect_ui_test.mjs
|
||||||
node tools/checks/vhost_www_ui_test.mjs
|
node tools/checks/vhost_www_ui_test.mjs
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.30.1 same-version sysinfo compiler feature correction
|
||||||
|
|
||||||
|
- Fix four compiler `E0433` errors in the system information endpoint by enabling `sysinfo`'s `system` feature alongside `network` with `default-features = false` unchanged. No dependency version or API change.
|
||||||
|
- Remove unused Tokio `AsyncReadExt` imported by the ACME runtime, and add the explicit manifest feature check to the offline source regressions.
|
||||||
|
- The change addresses the supplied Rust compiler log. A full Rust build and multi-OS runtime check remain outstanding in the source packaging environment.
|
||||||
|
|
||||||
## 0.29.7 same-version `E0308` source correction
|
## 0.29.7 same-version `E0308` source correction
|
||||||
|
|
||||||
- Defer Reverse Proxy error interception from the upstream `Response<hyper::body::Incoming>` to the adapted `Response<axum::body::Body>` in `success_prepare.rs`. This preserves the H2 WebSocket tunnel's `Incoming` body type and ensures replacement still precedes response transformation and cache admission. Reapply response rules and enforce `no-store` for replaced application errors.
|
- Defer Reverse Proxy error interception from the upstream `Response<hyper::body::Incoming>` to the adapted `Response<axum::body::Body>` in `success_prepare.rs`. This preserves the H2 WebSocket tunnel's `Incoming` body type and ensures replacement still precedes response transformation and cache admission. Reapply response rules and enforce `no-store` for replaced application errors.
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ The Settings page includes a local search field that filters category tabs using
|
|||||||
|
|
||||||
Unfinished Web Server, Vhost, Redirect and Stream forms use a session-only draft dock. Closing a dirty editor minimizes its exact browser state to the bottom-right dock so another editor can be opened independently. The dock follows the square-corner panel styling of the dashboard and intentionally avoids rounded/glass presentation. Drafts are intentionally not written to SQLite because form state can contain credentials or other secrets; they are discarded on logout/session reset.
|
Unfinished Web Server, Vhost, Redirect and Stream forms use a session-only draft dock. Closing a dirty editor minimizes its exact browser state to the bottom-right dock so another editor can be opened independently. The dock follows the square-corner panel styling of the dashboard and intentionally avoids rounded/glass presentation. Drafts are intentionally not written to SQLite because form state can contain credentials or other secrets; they are discarded on logout/session reset.
|
||||||
|
|
||||||
|
## System information (0.30.1)
|
||||||
|
|
||||||
|
**Settings -> System information** (`/settings?tab=system-info`) is a read-only view of the authenticated `/api/status` endpoint. It reports the OS name/kernel on Linux when available, executable architecture, logical CPU count, CPU and RAM telemetry, host/process uptime, RustProxy version, process RSS, thread and file-descriptor counts, and the existing statistics source and data/database locations. Values not supported by the host/platform display as unavailable, never as zero.
|
||||||
|
|
||||||
|
ACME diagnostics formerly shown in About and under Administration are now on this tab: mode/state, certificate environment path, and the active Python/pip/Certbot versions. Versions are probed by the ACME runtime setup with bounded subprocess output/timeouts and published from stored runtime status. They are not queried at every page refresh or status API request. Failed/unavailable probes leave the version empty. No new persistent settings or permissions are introduced.
|
||||||
|
|
||||||
## Configuration and update semantics
|
## Configuration and update semantics
|
||||||
|
|
||||||
GET `/api/config` returns a revisioned envelope. PUT the entire document with
|
GET `/api/config` returns a revisioned envelope. PUT the entire document with
|
||||||
|
|||||||
+22
-1
@@ -1,4 +1,25 @@
|
|||||||
# Verification report - RustProxy 0.30.0
|
# Verification report - RustProxy 0.30.1
|
||||||
|
|
||||||
|
## 0.30.1 sysinfo compiler-feature fix (2026-10-10)
|
||||||
|
|
||||||
|
- The operator's Rust compiler reported four `E0433` errors because the pinned `sysinfo = 0.38.4` dependency enabled only `network`, but the new OS/kernel status methods require the `system` feature. Enabled both `network` and `system` without changing dependency versions or status API behavior.
|
||||||
|
- Removed the reported unused Tokio `AsyncReadExt` import from `src/acme/runtime_env.rs`.
|
||||||
|
- Added a guard for both `sysinfo` features to `system_info_no_subprocess_contracts.py`, wired that source check into `./dev.sh test`, and synchronized the older manifest assertions in `check_source.py` and `release_smoke.py`. Cargo compilation on the operator's machine is still required to verify the full Rust build.
|
||||||
|
- Offline checks passed after the correction: system-info feature/source contracts, Rust and browser module contracts, version/source/release-smoke (SQLite/schema 9 and upgrade), memory, web server, security/dependency, simulated system-info UI, docs/translations, and Bash syntax. These static checks do not establish Rust compile success.
|
||||||
|
|
||||||
|
## 0.30.1 no-subprocess system information refinement (2026-10-10)
|
||||||
|
|
||||||
|
- Reused `sysinfo::System::{long_os_version, name, os_version, kernel_version}` for OS and kernel metadata on supported Linux/BSD/macOS/Windows targets; results remain cached. The status endpoint no longer reads Linux `/etc/os-release` or `/proc/sys/kernel/osrelease` directly.
|
||||||
|
- Removed the extra Python, pip and Certbot version subprocesses. At ACME startup, bounded local reads of the selected interpreter's `pyvenv.cfg` and the selected installation's `pip-*.dist-info/METADATA` and `certbot-*.dist-info/METADATA` provide versions when possible; missing/ambiguous values remain `null`. System Python full patch versions cannot be guaranteed without an executable probe, so they may be unavailable. ACME operational dependency checks and actual issuance still require subprocesses; those are **not** system-information collection.
|
||||||
|
- Added Windows `.exe` lookup for system Python/Certbot, metadata source unit regressions (not executed without Cargo) and compiler-independent source-contract regression `system_info_no_subprocess_contracts.py`. No new Rust dependencies, schema or API property changes.
|
||||||
|
- Compiler-independent checks passed: `system_info_no_subprocess_contracts.py`, `rust_module_contracts.py`, `source_module_contracts.py`, `version_contracts.py`, `check_source.py`, `release_smoke.py` (SQLite/schema 9 and upgrade), `memory_contracts.py`, `web_server_contracts.py`, `security_contracts.py`, `security_dependency_contracts.py`, `docs_check.py`, `langs_check.py`, all shipped Node UI regressions (including no-metadata fallback) and `high_traffic_memory_model.py` (simulated PURGE + 2,000 requests against a Python peer, **not RustProxy**). Rust/Cargo are absent, so `cargo check`, Rust unit tests and live multi-OS acceptance were not performed; production acceptance remains open.
|
||||||
|
|
||||||
|
## 0.30.1 system information and legend change (2026-10-10)
|
||||||
|
|
||||||
|
- Corrected H1/H2/H3 Overview legend markers to use exactly the protocol series theme variables (HTTP and WebSocket cards).
|
||||||
|
- Removed certificate runtime details from About, moved existing authenticated runtime metadata to Settings -> System information, added OS/version/kernel/architecture and existing process telemetry. Initial implementation used bounded ACME version subprocess probes for the selected binaries; the later refinement above replaces them with non-executing metadata reads, and leaves missing versions unavailable.
|
||||||
|
- Updated locale packs (English/Polish), authenticated OpenAPI status schema and release identity. SQLite schema 9, Cold Cache format 3 and persisted configuration have not changed.
|
||||||
|
- Passed offline: `rust_module_contracts.py`, `source_module_contracts.py`, `version_contracts.py`, `check_source.py`, `release_smoke.py` (including SQLite/schema 9 and source upgrade), `memory_contracts.py`, `high_traffic_memory_model.py` (simulated PURGE plus 2,000 requests against a Python peer, **not RustProxy**), `web_server_contracts.py`, `security_contracts.py`, `security_dependency_contracts.py`, all seven shipped Node UI regressions (including `system_info_0301_ui_test.mjs`), `docs_check.py` and `langs_check.py`. The full `./dev.sh test` pipeline exceeded this environment's execution limit after passing through the Web Server contracts; remaining checks were run individually. `./dev.sh check` exited 127 because Rust/Cargo are absent. No Rust compilation, Rust unit tests or real managed/system Certbot runtime checks were performed. Production acceptance remains open.
|
||||||
|
|
||||||
## 0.30.0 same-version sustained-memory audit (2026-10-10)
|
## 0.30.0 same-version sustained-memory audit (2026-10-10)
|
||||||
|
|
||||||
|
|||||||
+20
-2
@@ -202,7 +202,7 @@
|
|||||||
"processMemory": "Process memory",
|
"processMemory": "Process memory",
|
||||||
"systemLoad": "SYSTEM LOAD",
|
"systemLoad": "SYSTEM LOAD",
|
||||||
"systemChart": "Recent system CPU usage",
|
"systemChart": "Recent system CPU usage",
|
||||||
"systemMemory": "System RAM",
|
"systemMemory": "RAM used / total",
|
||||||
"systemSwap": "System swap",
|
"systemSwap": "System swap",
|
||||||
"systemUptime": "System uptime",
|
"systemUptime": "System uptime",
|
||||||
"loadAverage": "Load 1 / 5 / 15 min",
|
"loadAverage": "Load 1 / 5 / 15 min",
|
||||||
@@ -2286,6 +2286,24 @@
|
|||||||
"vhostInterceptOriginErrors": "Replace application error bodies with RustProxy error pages",
|
"vhostInterceptOriginErrors": "Replace application error bodies with RustProxy error pages",
|
||||||
"vhostInterceptOriginStatuses": "Intercept HTTP statuses (comma-separated)",
|
"vhostInterceptOriginStatuses": "Intercept HTTP statuses (comma-separated)",
|
||||||
"vhostInterceptOriginErrorsHelp": "Example: 404, 500, 502, 503, 504. For selected codes, RustProxy keeps the HTTP error status and returns the vhost/global custom error page, or a built-in one. Application content/cookies are removed; the replacement is no-store. Off by default. Internal named-location fallbacks take precedence. Applies to reverse proxy and Web Server (including PHP).",
|
"vhostInterceptOriginErrorsHelp": "Example: 404, 500, 502, 503, 504. For selected codes, RustProxy keeps the HTTP error status and returns the vhost/global custom error page, or a built-in one. Application content/cookies are removed; the replacement is no-store. Off by default. Internal named-location fallbacks take precedence. Applies to reverse proxy and Web Server (including PHP).",
|
||||||
"vhostInterceptOriginInvalid": "Select one or more unique supported HTTP codes: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503, 504."
|
"vhostInterceptOriginInvalid": "Select one or more unique supported HTTP codes: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503, 504.",
|
||||||
|
"systemInformation": "System information",
|
||||||
|
"systemInformationHelp": "Live, read-only information from the authenticated status API. Unavailable metrics are not estimated.",
|
||||||
|
"systemHostHeading": "Operating system and hardware",
|
||||||
|
"systemOsVersion": "Operating system",
|
||||||
|
"systemKernel": "Kernel",
|
||||||
|
"systemArchitecture": "Architecture",
|
||||||
|
"systemLogicalCpus": "Logical CPUs",
|
||||||
|
"systemCpuLoad": "CPU usage",
|
||||||
|
"systemProcessHeading": "RustProxy process",
|
||||||
|
"systemAppVersion": "RustProxy version",
|
||||||
|
"systemProcessUptime": "Process uptime",
|
||||||
|
"systemProcessMemory": "Process RAM (RSS)",
|
||||||
|
"systemProcessThreads": "Process threads",
|
||||||
|
"systemOpenFds": "Open file descriptors",
|
||||||
|
"systemCertificateHeading": "ACME / Python environment",
|
||||||
|
"systemCertificateHelp": "Versions are detected from the interpreter and tools actually selected for certificate management.",
|
||||||
|
"systemAcmeStatus": "ACME status",
|
||||||
|
"systemPathsHeading": "Data locations"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+21
-3
@@ -202,9 +202,9 @@
|
|||||||
"processMemory": "Pamięć procesu",
|
"processMemory": "Pamięć procesu",
|
||||||
"systemLoad": "OBCIĄŻENIE SYSTEMU",
|
"systemLoad": "OBCIĄŻENIE SYSTEMU",
|
||||||
"systemChart": "Ostatnie próbki obciążenia CPU",
|
"systemChart": "Ostatnie próbki obciążenia CPU",
|
||||||
"systemMemory": "RAM systemu",
|
"systemMemory": "RAM użyta / łącznie",
|
||||||
"systemSwap": "Swap systemu",
|
"systemSwap": "Swap systemu",
|
||||||
"systemUptime": "Czas działania systemu",
|
"systemUptime": "Czas pracy systemu",
|
||||||
"loadAverage": "Load 1 / 5 / 15 min",
|
"loadAverage": "Load 1 / 5 / 15 min",
|
||||||
"cacheOverview": "Cache RAM",
|
"cacheOverview": "Cache RAM",
|
||||||
"cacheObjects": "Obiekty / pamięć",
|
"cacheObjects": "Obiekty / pamięć",
|
||||||
@@ -2286,6 +2286,24 @@
|
|||||||
"vhostInterceptOriginErrors": "Zastępuj treść błędów aplikacji stronami błędów RustProxy",
|
"vhostInterceptOriginErrors": "Zastępuj treść błędów aplikacji stronami błędów RustProxy",
|
||||||
"vhostInterceptOriginStatuses": "Przechwytywane kody HTTP (oddziel przecinkami)",
|
"vhostInterceptOriginStatuses": "Przechwytywane kody HTTP (oddziel przecinkami)",
|
||||||
"vhostInterceptOriginErrorsHelp": "Przykład: 404, 500, 502, 503, 504. Dla wybranych kodów RustProxy zachowa kod błędu, ale wyświetli stronę skonfigurowaną dla vhosta/globalnie albo wbudowaną. Treść i ciasteczka aplikacji zostaną usunięte, a odpowiedź otrzyma no-store. Domyślnie wyłączone. Wewnętrzne lokalizacje błędów mają pierwszeństwo. Działa dla Reverse Proxy i Web Server (także PHP).",
|
"vhostInterceptOriginErrorsHelp": "Przykład: 404, 500, 502, 503, 504. Dla wybranych kodów RustProxy zachowa kod błędu, ale wyświetli stronę skonfigurowaną dla vhosta/globalnie albo wbudowaną. Treść i ciasteczka aplikacji zostaną usunięte, a odpowiedź otrzyma no-store. Domyślnie wyłączone. Wewnętrzne lokalizacje błędów mają pierwszeństwo. Działa dla Reverse Proxy i Web Server (także PHP).",
|
||||||
"vhostInterceptOriginInvalid": "Podaj co najmniej jeden unikalny obsługiwany kod HTTP: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503, 504."
|
"vhostInterceptOriginInvalid": "Podaj co najmniej jeden unikalny obsługiwany kod HTTP: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503, 504.",
|
||||||
|
"systemInformation": "Informacje o systemie",
|
||||||
|
"systemInformationHelp": "Bieżące dane tylko do odczytu z uwierzytelnionego API statusu. Brakujących wartości nie szacujemy.",
|
||||||
|
"systemHostHeading": "System operacyjny i sprzęt",
|
||||||
|
"systemOsVersion": "System operacyjny",
|
||||||
|
"systemKernel": "Jądro systemu",
|
||||||
|
"systemArchitecture": "Architektura",
|
||||||
|
"systemLogicalCpus": "Procesory logiczne",
|
||||||
|
"systemCpuLoad": "Użycie CPU",
|
||||||
|
"systemProcessHeading": "Proces RustProxy",
|
||||||
|
"systemAppVersion": "Wersja RustProxy",
|
||||||
|
"systemProcessUptime": "Czas pracy procesu",
|
||||||
|
"systemProcessMemory": "Pamięć procesu (RSS)",
|
||||||
|
"systemProcessThreads": "Wątki procesu",
|
||||||
|
"systemOpenFds": "Otwarte deskryptory plików",
|
||||||
|
"systemCertificateHeading": "Środowisko ACME / Python",
|
||||||
|
"systemCertificateHelp": "Wersje są odczytywane z interpretera i narzędzi rzeczywiście używanych do obsługi certyfikatów.",
|
||||||
|
"systemAcmeStatus": "Stan ACME",
|
||||||
|
"systemPathsHeading": "Ścieżki danych"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+49
-5
@@ -24,6 +24,10 @@ pub struct RuntimeStatus {
|
|||||||
pub python: bool,
|
pub python: bool,
|
||||||
pub pip: bool,
|
pub pip: bool,
|
||||||
pub certbot: bool,
|
pub certbot: bool,
|
||||||
|
/// Versions of the executables actually selected for ACME, never a guessed system default.
|
||||||
|
pub python_version: Option<String>,
|
||||||
|
pub pip_version: Option<String>,
|
||||||
|
pub certbot_version: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RuntimeStatus {
|
impl Default for RuntimeStatus {
|
||||||
@@ -36,6 +40,9 @@ impl Default for RuntimeStatus {
|
|||||||
python: false,
|
python: false,
|
||||||
pip: false,
|
pip: false,
|
||||||
certbot: false,
|
certbot: false,
|
||||||
|
python_version: None,
|
||||||
|
pip_version: None,
|
||||||
|
certbot_version: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,11 +75,22 @@ fn find_on_path(name: &str) -> Option<PathBuf> {
|
|||||||
if candidate.components().count() > 1 {
|
if candidate.components().count() > 1 {
|
||||||
return candidate.is_file().then(|| candidate.to_owned());
|
return candidate.is_file().then(|| candidate.to_owned());
|
||||||
}
|
}
|
||||||
env::var_os("PATH")
|
for directory in env::var_os("PATH").into_iter().flat_map(|paths| env::split_paths(&paths).collect::<Vec<_>>()) {
|
||||||
.into_iter()
|
let path = directory.join(name);
|
||||||
.flat_map(|value| env::split_paths(&value).collect::<Vec<_>>())
|
if path.is_file() {
|
||||||
.map(|directory| directory.join(name))
|
return Some(path);
|
||||||
.find(|path| path.is_file())
|
}
|
||||||
|
// Windows PATH lookup uses PATHEXT; Path::is_file alone does not.
|
||||||
|
// Python and Certbot are installed as .exe launchers on Windows.
|
||||||
|
#[cfg(windows)]
|
||||||
|
if path.extension().is_none() {
|
||||||
|
let executable = path.with_extension("exe");
|
||||||
|
if executable.is_file() {
|
||||||
|
return Some(executable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn python_candidates() -> impl Iterator<Item = PathBuf> {
|
fn python_candidates() -> impl Iterator<Item = PathBuf> {
|
||||||
@@ -137,6 +155,8 @@ async fn command_success(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
include!("runtime_versions.rs");
|
||||||
|
|
||||||
async fn managed_environment_valid(
|
async fn managed_environment_valid(
|
||||||
directory: &Path,
|
directory: &Path,
|
||||||
shutdown: &tokio_util::sync::CancellationToken,
|
shutdown: &tokio_util::sync::CancellationToken,
|
||||||
@@ -203,12 +223,14 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
|
|||||||
python: false,
|
python: false,
|
||||||
pip: false,
|
pip: false,
|
||||||
certbot: false,
|
certbot: false,
|
||||||
|
..RuntimeStatus::default()
|
||||||
});
|
});
|
||||||
return Err("acme.runtime_data_dir_missing".into());
|
return Err("acme.runtime_data_dir_missing".into());
|
||||||
};
|
};
|
||||||
let managed = data_dir.join("acme-python");
|
let managed = data_dir.join("acme-python");
|
||||||
let managed_directory = Some(managed.to_string_lossy().into_owned());
|
let managed_directory = Some(managed.to_string_lossy().into_owned());
|
||||||
if managed_environment_valid(&managed, &state.shutdown).await {
|
if managed_environment_valid(&managed, &state.shutdown).await {
|
||||||
|
let versions = runtime_versions(&venv_python(&managed), true, Some(&venv_certbot(&managed)));
|
||||||
state.acme.set_runtime(RuntimeStatus {
|
state.acme.set_runtime(RuntimeStatus {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
mode: "managed",
|
mode: "managed",
|
||||||
@@ -217,6 +239,9 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
|
|||||||
python: true,
|
python: true,
|
||||||
pip: true,
|
pip: true,
|
||||||
certbot: true,
|
certbot: true,
|
||||||
|
python_version: versions.python,
|
||||||
|
pip_version: versions.pip,
|
||||||
|
certbot_version: versions.certbot,
|
||||||
});
|
});
|
||||||
return Ok(RuntimeLaunch {
|
return Ok(RuntimeLaunch {
|
||||||
python: venv_python(&managed),
|
python: venv_python(&managed),
|
||||||
@@ -241,10 +266,16 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
|
|||||||
python: false,
|
python: false,
|
||||||
pip: false,
|
pip: false,
|
||||||
certbot: distro_certbot_available,
|
certbot: distro_certbot_available,
|
||||||
|
..RuntimeStatus::default()
|
||||||
});
|
});
|
||||||
return Err("acme.runtime_python_missing".into());
|
return Err("acme.runtime_python_missing".into());
|
||||||
};
|
};
|
||||||
let system_pip = system_has_pip(&python, &state.shutdown).await;
|
let system_pip = system_has_pip(&python, &state.shutdown).await;
|
||||||
|
let system_versions = runtime_versions(
|
||||||
|
&python,
|
||||||
|
system_pip,
|
||||||
|
distro_certbot.as_deref().filter(|_| distro_certbot_available),
|
||||||
|
);
|
||||||
state.acme.set_runtime(RuntimeStatus {
|
state.acme.set_runtime(RuntimeStatus {
|
||||||
status: "initializing",
|
status: "initializing",
|
||||||
mode: "managed",
|
mode: "managed",
|
||||||
@@ -253,10 +284,14 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
|
|||||||
python: true,
|
python: true,
|
||||||
pip: system_pip,
|
pip: system_pip,
|
||||||
certbot: distro_certbot_available,
|
certbot: distro_certbot_available,
|
||||||
|
python_version: system_versions.python.clone(),
|
||||||
|
pip_version: system_versions.pip.clone(),
|
||||||
|
certbot_version: system_versions.certbot.clone(),
|
||||||
});
|
});
|
||||||
let build = build_managed_environment(&python, &managed, system_pip, &state.shutdown).await;
|
let build = build_managed_environment(&python, &managed, system_pip, &state.shutdown).await;
|
||||||
if build.is_ok() {
|
if build.is_ok() {
|
||||||
tracing::info!(path = %managed.display(), "managed ACME runtime ready");
|
tracing::info!(path = %managed.display(), "managed ACME runtime ready");
|
||||||
|
let versions = runtime_versions(&venv_python(&managed), true, Some(&venv_certbot(&managed)));
|
||||||
state.acme.set_runtime(RuntimeStatus {
|
state.acme.set_runtime(RuntimeStatus {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
mode: "managed",
|
mode: "managed",
|
||||||
@@ -265,6 +300,9 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
|
|||||||
python: true,
|
python: true,
|
||||||
pip: true,
|
pip: true,
|
||||||
certbot: true,
|
certbot: true,
|
||||||
|
python_version: versions.python,
|
||||||
|
pip_version: versions.pip,
|
||||||
|
certbot_version: versions.certbot,
|
||||||
});
|
});
|
||||||
return Ok(RuntimeLaunch {
|
return Ok(RuntimeLaunch {
|
||||||
python: venv_python(&managed),
|
python: venv_python(&managed),
|
||||||
@@ -282,6 +320,9 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
|
|||||||
python: true,
|
python: true,
|
||||||
pip: system_pip,
|
pip: system_pip,
|
||||||
certbot: true,
|
certbot: true,
|
||||||
|
python_version: system_versions.python,
|
||||||
|
pip_version: system_versions.pip,
|
||||||
|
certbot_version: system_versions.certbot,
|
||||||
});
|
});
|
||||||
return Ok(RuntimeLaunch {
|
return Ok(RuntimeLaunch {
|
||||||
python,
|
python,
|
||||||
@@ -312,6 +353,9 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
|
|||||||
python: true,
|
python: true,
|
||||||
pip: system_pip,
|
pip: system_pip,
|
||||||
certbot: false,
|
certbot: false,
|
||||||
|
python_version: system_versions.python,
|
||||||
|
pip_version: system_versions.pip,
|
||||||
|
certbot_version: None,
|
||||||
});
|
});
|
||||||
Err(message.into())
|
Err(message.into())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
// Version information is diagnostic, not a health check. Read package metadata
|
||||||
|
// for the *selected* runtime without launching Python, pip, Certbot or a shell.
|
||||||
|
// A missing/ambiguous version stays None; do not guess an interpreter patch
|
||||||
|
// version from an executable name or report a hard-coded Certbot pin.
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
const MAX_VERSION_METADATA_BYTES: u64 = 16 * 1024;
|
||||||
|
const MAX_SITE_DIRECTORIES: usize = 64;
|
||||||
|
const MAX_PACKAGES_PER_SITE: usize = 4096;
|
||||||
|
|
||||||
|
fn metadata_text(path: &Path) -> Option<String> {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
std::fs::File::open(path)
|
||||||
|
.ok()?
|
||||||
|
.take(MAX_VERSION_METADATA_BYTES)
|
||||||
|
.read_to_end(&mut bytes)
|
||||||
|
.ok()?;
|
||||||
|
String::from_utf8(bytes).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_version(raw: &str) -> Option<String> {
|
||||||
|
let version = raw.trim();
|
||||||
|
(version.len() <= 48
|
||||||
|
&& version.bytes().next().is_some_and(|byte| byte.is_ascii_digit())
|
||||||
|
&& version
|
||||||
|
.bytes()
|
||||||
|
.all(|byte| byte.is_ascii_alphanumeric() || b".+-_".contains(&byte)))
|
||||||
|
.then(|| version.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn python_environment_root(executable: &Path) -> Option<std::path::PathBuf> {
|
||||||
|
let parent = executable.parent()?;
|
||||||
|
let name = parent.file_name()?.to_str()?;
|
||||||
|
let root = if name.eq_ignore_ascii_case("bin") || name.eq_ignore_ascii_case("scripts") {
|
||||||
|
parent.parent()?
|
||||||
|
} else {
|
||||||
|
parent
|
||||||
|
};
|
||||||
|
Some(root.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpreter_version(root: &Path) -> Option<String> {
|
||||||
|
let content = metadata_text(&root.join("pyvenv.cfg"))?;
|
||||||
|
// CPython venv writes `version = X.Y.Z`. `version_info` from virtualenv
|
||||||
|
// is a fallback when its standard `version` field is not present.
|
||||||
|
for key in ["version", "version_info"] {
|
||||||
|
for line in content.lines() {
|
||||||
|
if let Some((name, value)) = line.split_once('=') {
|
||||||
|
if name.trim().eq_ignore_ascii_case(key) {
|
||||||
|
if let Some(version) = safe_version(value) {
|
||||||
|
return Some(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn python_minor(version: &str) -> Option<String> {
|
||||||
|
let mut parts = version.split('.');
|
||||||
|
let major = parts.next()?;
|
||||||
|
let minor = parts.next()?;
|
||||||
|
(major.bytes().all(|b| b.is_ascii_digit())
|
||||||
|
&& minor.bytes().all(|b| b.is_ascii_digit()))
|
||||||
|
.then(|| format!("python{major}.{minor}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn package_sites(root: &Path, python_version: Option<&str>) -> Vec<std::path::PathBuf> {
|
||||||
|
let mut sites = vec![root.join("Lib/site-packages"), root.join("lib/python3/dist-packages")];
|
||||||
|
for base in [root.join("lib"), root.join("lib64")] {
|
||||||
|
if let Some(minor) = python_version.and_then(python_minor) {
|
||||||
|
sites.push(base.join(&minor).join("site-packages"));
|
||||||
|
sites.push(base.join(&minor).join("dist-packages"));
|
||||||
|
} else if let Ok(entries) = std::fs::read_dir(&base) {
|
||||||
|
// Python installations with no pyvenv.cfg may contain multiple
|
||||||
|
// interpreter versions. Never choose between conflicting results.
|
||||||
|
for entry in entries.take(MAX_SITE_DIRECTORIES).flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if entry.file_name().to_string_lossy().starts_with("python3.") && path.is_dir() {
|
||||||
|
sites.push(path.join("site-packages"));
|
||||||
|
sites.push(path.join("dist-packages"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sites
|
||||||
|
}
|
||||||
|
|
||||||
|
fn package_version(root: &Path, package: &str, python_version: Option<&str>) -> Option<String> {
|
||||||
|
let prefix = format!("{package}-");
|
||||||
|
let mut result: Option<String> = None;
|
||||||
|
for site in package_sites(root, python_version) {
|
||||||
|
let Ok(entries) = std::fs::read_dir(site) else { continue };
|
||||||
|
for entry in entries.take(MAX_PACKAGES_PER_SITE).flatten() {
|
||||||
|
let name = entry.file_name();
|
||||||
|
let Some(dir_name) = name.to_str() else { continue };
|
||||||
|
let Some(suffix) = dir_name.strip_prefix(prefix.as_str())
|
||||||
|
.and_then(|name| name.strip_suffix(".dist-info")) else { continue };
|
||||||
|
let Some(directory_version) = safe_version(suffix) else { continue };
|
||||||
|
let Some(content) = metadata_text(&entry.path().join("METADATA")) else { continue };
|
||||||
|
let mut reported_name = None;
|
||||||
|
let mut reported_version = None;
|
||||||
|
for line in content.lines() {
|
||||||
|
if let Some(value) = line.strip_prefix("Name: ") {
|
||||||
|
reported_name = Some(value.trim());
|
||||||
|
} else if let Some(value) = line.strip_prefix("Version: ") {
|
||||||
|
reported_version = safe_version(value);
|
||||||
|
}
|
||||||
|
if reported_name.is_some() && reported_version.is_some() { break; }
|
||||||
|
}
|
||||||
|
if !reported_name.is_some_and(|name| name.eq_ignore_ascii_case(package))
|
||||||
|
|| reported_version.as_deref() != Some(directory_version.as_str()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if result.as_ref().is_some_and(|previous| previous != &directory_version) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
result = Some(directory_version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct RuntimeVersions {
|
||||||
|
python: Option<String>,
|
||||||
|
pip: Option<String>,
|
||||||
|
certbot: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runtime_versions(python: &Path, has_pip: bool, certbot: Option<&Path>) -> RuntimeVersions {
|
||||||
|
let root = python_environment_root(python);
|
||||||
|
let python_version = root.as_deref().and_then(interpreter_version);
|
||||||
|
let pip_version = if has_pip {
|
||||||
|
root.as_deref().and_then(|root| package_version(root, "pip", python_version.as_deref()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let certbot_version = certbot.and_then(|path| {
|
||||||
|
let certbot_root = python_environment_root(path)?;
|
||||||
|
let certbot_python_version = if root.as_deref() == Some(certbot_root.as_path()) {
|
||||||
|
python_version.as_deref()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
package_version(&certbot_root, "certbot", certbot_python_version)
|
||||||
|
});
|
||||||
|
RuntimeVersions { python: python_version, pip: pip_version, certbot: certbot_version }
|
||||||
|
}
|
||||||
|
|
||||||
@@ -146,3 +146,65 @@ mod renewal_regressions {
|
|||||||
stop(app).await;
|
stop(app).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mod runtime_metadata_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn write_package(site: &Path, package: &str, version: &str, metadata: &str) {
|
||||||
|
let folder = site.join(format!("{package}-{version}.dist-info"));
|
||||||
|
std::fs::create_dir_all(&folder).unwrap();
|
||||||
|
std::fs::write(folder.join("METADATA"), metadata).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn managed_python_pip_and_certbot_versions_use_metadata_only() {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let root = temp.path();
|
||||||
|
let python = venv_python(root);
|
||||||
|
let certbot = venv_certbot(root);
|
||||||
|
std::fs::create_dir_all(venv_bin(root)).unwrap();
|
||||||
|
std::fs::write(&python, "").unwrap();
|
||||||
|
std::fs::write(&certbot, "").unwrap();
|
||||||
|
std::fs::write(root.join("pyvenv.cfg"), "home = /usr/bin\nversion = 3.13.5\n").unwrap();
|
||||||
|
let site = if cfg!(windows) {
|
||||||
|
root.join("Lib/site-packages")
|
||||||
|
} else {
|
||||||
|
root.join("lib/python3.13/site-packages")
|
||||||
|
};
|
||||||
|
write_package(&site, "pip", "25.2", "Metadata-Version: 2.3\nName: pip\nVersion: 25.2\n");
|
||||||
|
write_package(&site, "certbot", "5.8.0", "Name: certbot\nVersion: 5.8.0\n");
|
||||||
|
let versions = runtime_versions(&python, true, Some(&certbot));
|
||||||
|
assert_eq!(versions.python.as_deref(), Some("3.13.5"));
|
||||||
|
assert_eq!(versions.pip.as_deref(), Some("25.2"));
|
||||||
|
assert_eq!(versions.certbot.as_deref(), Some("5.8.0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn absent_or_unverified_versions_are_not_invented() {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let root = temp.path();
|
||||||
|
let executable = root.join("bin/python3");
|
||||||
|
std::fs::create_dir_all(root.join("bin")).unwrap();
|
||||||
|
std::fs::write(&executable, "").unwrap();
|
||||||
|
let site = root.join("lib/python3/dist-packages");
|
||||||
|
write_package(&site, "pip", "99.0", "Name: other-package\nVersion: 99.0\n");
|
||||||
|
let versions = runtime_versions(&executable, true, None);
|
||||||
|
assert!(versions.python.is_none());
|
||||||
|
assert!(versions.pip.is_none());
|
||||||
|
assert!(versions.certbot.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn conflicting_site_distributions_do_not_return_stale_versions() {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let root = temp.path();
|
||||||
|
let executable = root.join("bin/python3");
|
||||||
|
std::fs::create_dir_all(root.join("bin")).unwrap();
|
||||||
|
std::fs::write(&executable, "").unwrap();
|
||||||
|
write_package(&root.join("lib/python3/dist-packages"), "pip", "23.0", "Name: pip\nVersion: 23.0\n");
|
||||||
|
write_package(&root.join("lib/python3.13/site-packages"), "pip", "25.0", "Name: pip\nVersion: 25.0\n");
|
||||||
|
assert!(runtime_versions(&executable, true, None).pip.is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+52
-5
@@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.0.3",
|
"openapi": "3.0.3",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "RustProxy Administration API",
|
"title": "RustProxy Administration API",
|
||||||
"version": "0.30.0",
|
"version": "0.30.1",
|
||||||
"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": [
|
||||||
@@ -549,7 +549,19 @@
|
|||||||
"description": "Off by default. For these origin response HTTP statuses, replace the application response with a RustProxy error page (no-store). Does not change RustProxy-generated routing/security errors.",
|
"description": "Off by default. For these origin response HTTP statuses, replace the application response with a RustProxy error page (no-store). Does not change RustProxy-generated routing/security errors.",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"enum": [400, 401, 403, 404, 408, 413, 429, 500, 502, 503, 504]
|
"enum": [
|
||||||
|
400,
|
||||||
|
401,
|
||||||
|
403,
|
||||||
|
404,
|
||||||
|
408,
|
||||||
|
413,
|
||||||
|
429,
|
||||||
|
500,
|
||||||
|
502,
|
||||||
|
503,
|
||||||
|
504
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4737,7 +4749,7 @@
|
|||||||
},
|
},
|
||||||
"certificate_runtime": {
|
"certificate_runtime": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Python/pip/Certbot capability used by managed certificate automation.",
|
"description": "ACME Python/pip/Certbot capability and versions read from local runtime/package metadata without executing diagnostic commands. Missing metadata returns null.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -4771,6 +4783,21 @@
|
|||||||
},
|
},
|
||||||
"certbot": {
|
"certbot": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"python_version": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Version declared by the selected runtime (pyvenv.cfg for Python, dist-info/METADATA for pip and Certbot); null if missing or ambiguous."
|
||||||
|
},
|
||||||
|
"pip_version": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Version declared by the selected runtime (pyvenv.cfg for Python, dist-info/METADATA for pip and Certbot); null if missing or ambiguous."
|
||||||
|
},
|
||||||
|
"certbot_version": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Version declared by the selected runtime (pyvenv.cfg for Python, dist-info/METADATA for pip and Certbot); null if missing or ambiguous."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -4780,8 +4807,25 @@
|
|||||||
"managed_directory",
|
"managed_directory",
|
||||||
"python",
|
"python",
|
||||||
"pip",
|
"pip",
|
||||||
"certbot"
|
"certbot",
|
||||||
|
"python_version",
|
||||||
|
"pip_version",
|
||||||
|
"certbot_version"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"architecture": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Architecture of the RustProxy executable."
|
||||||
|
},
|
||||||
|
"operating_system": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Host OS release name from the cross-platform sysinfo library, when supported."
|
||||||
|
},
|
||||||
|
"kernel_version": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Host kernel version from the cross-platform sysinfo library, when available."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -4789,7 +4833,10 @@
|
|||||||
"statistics_source",
|
"statistics_source",
|
||||||
"data_directory",
|
"data_directory",
|
||||||
"database_path",
|
"database_path",
|
||||||
"certificate_runtime"
|
"certificate_runtime",
|
||||||
|
"architecture",
|
||||||
|
"operating_system",
|
||||||
|
"kernel_version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CertificateApiCredential": {
|
"CertificateApiCredential": {
|
||||||
|
|||||||
@@ -1,3 +1,33 @@
|
|||||||
|
// Reuse sysinfo's native OS APIs on Linux, BSD, macOS and Windows. The
|
||||||
|
// diagnostics endpoint must never spawn system commands or parse platform files.
|
||||||
|
// Host identification is cached because it changes only after a reboot.
|
||||||
|
fn valid_host_text(value: String) -> Option<String> {
|
||||||
|
let value = value.trim();
|
||||||
|
(!value.is_empty() && value.len() <= 128 && !value.chars().any(char::is_control))
|
||||||
|
.then(|| value.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn os_release() -> Option<&'static str> {
|
||||||
|
static VALUE: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
|
||||||
|
VALUE.get_or_init(|| {
|
||||||
|
sysinfo::System::long_os_version()
|
||||||
|
.or_else(|| {
|
||||||
|
let name = sysinfo::System::name()?;
|
||||||
|
Some(match sysinfo::System::os_version() {
|
||||||
|
Some(version) if !version.is_empty() => format!("{name} {version}"),
|
||||||
|
_ => name,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.and_then(valid_host_text)
|
||||||
|
}).as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kernel_release() -> Option<&'static str> {
|
||||||
|
static VALUE: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
|
||||||
|
VALUE.get_or_init(|| sysinfo::System::kernel_version().and_then(valid_host_text))
|
||||||
|
.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
fn status_payload(state: &App) -> Value {
|
fn status_payload(state: &App) -> Value {
|
||||||
let snapshot = state.config.load_full();
|
let snapshot = state.config.load_full();
|
||||||
let listeners = json!({
|
let listeners = json!({
|
||||||
@@ -180,6 +210,9 @@ fn status_payload(state: &App) -> Value {
|
|||||||
});
|
});
|
||||||
payload["runtime"] = json!({
|
payload["runtime"] = json!({
|
||||||
"platform": system.platform,
|
"platform": system.platform,
|
||||||
|
"operating_system": os_release(),
|
||||||
|
"kernel_version": kernel_release(),
|
||||||
|
"architecture": std::env::consts::ARCH,
|
||||||
"statistics_source": system.source,
|
"statistics_source": system.source,
|
||||||
"data_directory": state.policy.data_dir.as_ref().map(|path| path.display().to_string()),
|
"data_directory": state.policy.data_dir.as_ref().map(|path| path.display().to_string()),
|
||||||
"database_path": state.policy.data_dir.as_ref().map(|path| path.join("config.sqlite3").display().to_string()),
|
"database_path": state.policy.data_dir.as_ref().map(|path| path.join("config.sqlite3").display().to_string()),
|
||||||
|
|||||||
@@ -192,21 +192,8 @@ function enhanceEnabledControl(input, hintKey = "entityEnableHint") {
|
|||||||
}
|
}
|
||||||
window.rustProxyEnableControl = enhanceEnabledControl;
|
window.rustProxyEnableControl = enhanceEnabledControl;
|
||||||
function action(label, fn) { const b = el("button", label, "quiet"); b.type = "button"; b.addEventListener("click", fn); return b; }
|
function action(label, fn) { const b = el("button", label, "quiet"); b.type = "button"; b.addEventListener("click", fn); return b; }
|
||||||
function renderAboutRuntime(status = latestStatus) {
|
|
||||||
const value = status?.runtime?.certificate_runtime || {};
|
|
||||||
const availability = flag => flag === true ? t("runtimeAvailable") : flag === false ? t("runtimeUnavailable") : t("unavailable");
|
|
||||||
const mode = value.status === "unavailable" ? t("certificateRuntimeUnavailable") : value.status === "ready" && value.mode === "managed" ? t("certificateRuntimeManaged") : value.status === "ready" && value.mode === "system" ? t("certificateRuntimeSystem") : t("certificateRuntimePreparing");
|
|
||||||
const modeNode = $("about-acme-mode"), runtimeNode = $("about-python-runtime"), details = $("about-python-details");
|
|
||||||
if (modeNode) modeNode.textContent = mode;
|
|
||||||
if (runtimeNode) runtimeNode.textContent = `${t("runtimePython")}: ${availability(value.python)} · ${t("runtimePip")}: ${availability(value.pip)} · ${t("runtimeCertbot")}: ${availability(value.certbot)}`;
|
|
||||||
if (details) {
|
|
||||||
const parts = [value.message ? t(value.message) : ""];
|
|
||||||
if (value.managed_directory) parts.push(`${t("certificateRuntimePath")}: ${value.managed_directory}`);
|
|
||||||
details.textContent = parts.filter(Boolean).join(" · ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function renderStatus(s) {
|
function renderStatus(s) {
|
||||||
const firstStatus = latestStatus === null; latestStatus = s; renderAboutRuntime(s); window.rustProxySettings?.render(); if (firstStatus) renderSettings();
|
const firstStatus = latestStatus === null; latestStatus = s; window.rustProxySettings?.render(); if (firstStatus) renderSettings();
|
||||||
connected = true; $("connection-label").textContent = t("connected"); $("signal").classList.remove("status-error"); const m = s.metrics;
|
connected = true; $("connection-label").textContent = t("connected"); $("signal").classList.remove("status-error"); const m = s.metrics;
|
||||||
$("metric-ws").textContent = m.websocket_active || 0;
|
$("metric-ws").textContent = m.websocket_active || 0;
|
||||||
$("metric-ws-h1").textContent = m.websocket_h1_active || 0;
|
$("metric-ws-h1").textContent = m.websocket_h1_active || 0;
|
||||||
@@ -384,7 +371,7 @@ function stopTunnelRefresh() { if (tunnelRefreshTimer) clearInterval(tunnelRefre
|
|||||||
function startTunnelRefresh() { stopTunnelRefresh(); tunnelRefreshTimer = setInterval(() => { if ($("tunnels-dialog")?.open) void loadTunnels(); }, 2000); }
|
function startTunnelRefresh() { stopTunnelRefresh(); tunnelRefreshTimer = setInterval(() => { if ($("tunnels-dialog")?.open) void loadTunnels(); }, 2000); }
|
||||||
$("login-form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; authMessage(""); try { await withFormSubmitState(form, event.submitter, async () => { const username = $("username").value.trim(); const password = $("password").value; if (setupRequired) { if (password !== $("password-confirm").value) throw fieldError("password-confirm", t("passwordMismatch")); await api("/api/auth/setup", { method: "POST", body: JSON.stringify({ token: $("setup-token").value.trim(), username, password }) }); setupRequired = false; syncAuthForm(); } const info = await api("/api/auth/login", { method: "POST", body: JSON.stringify({ username, password }) }); await activate(info); clearTimeout(toastTimer); hideToast(); }); } catch (e) { if (e.code === "auth.already_initialized") { setupRequired = false; syncAuthForm(); } $("password").value = ""; $("password-confirm").value = ""; authMessage(e.message); } });
|
$("login-form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; authMessage(""); try { await withFormSubmitState(form, event.submitter, async () => { const username = $("username").value.trim(); const password = $("password").value; if (setupRequired) { if (password !== $("password-confirm").value) throw fieldError("password-confirm", t("passwordMismatch")); await api("/api/auth/setup", { method: "POST", body: JSON.stringify({ token: $("setup-token").value.trim(), username, password }) }); setupRequired = false; syncAuthForm(); } const info = await api("/api/auth/login", { method: "POST", body: JSON.stringify({ username, password }) }); await activate(info); clearTimeout(toastTimer); hideToast(); }); } catch (e) { if (e.code === "auth.already_initialized") { setupRequired = false; syncAuthForm(); } $("password").value = ""; $("password-confirm").value = ""; authMessage(e.message); } });
|
||||||
$("logout").addEventListener("click", async () => { try { await api("/api/auth/logout", { method: "POST" }); lock(); toast(t("locked")); } catch (e) { if (e.status === 401) lock(); toast(e.message, true); } });
|
$("logout").addEventListener("click", async () => { try { await api("/api/auth/logout", { method: "POST" }); lock(); toast(t("locked")); } catch (e) { if (e.status === 401) lock(); toast(e.message, true); } });
|
||||||
$("about-open").addEventListener("click", () => { renderAboutRuntime(); const dialog = $("about-dialog"); if (!dialog.open) dialog.showModal(); });
|
$("about-open").addEventListener("click", () => { const dialog = $("about-dialog"); if (!dialog.open) dialog.showModal(); });
|
||||||
$("about-close").addEventListener("click", () => $("about-dialog").close());
|
$("about-close").addEventListener("click", () => $("about-dialog").close());
|
||||||
const mobileMenuToggle = $("mobile-menu-toggle"), dashboardSidebar = document.querySelector(".sidebar");
|
const mobileMenuToggle = $("mobile-menu-toggle"), dashboardSidebar = document.querySelector(".sidebar");
|
||||||
function setMobileMenu(open) {
|
function setMobileMenu(open) {
|
||||||
|
|||||||
+45
-29
@@ -1768,6 +1768,9 @@
|
|||||||
aria-controls="settings-page-administration" aria-selected="false"
|
aria-controls="settings-page-administration" aria-selected="false"
|
||||||
data-i18n="administration" data-settings-tab="administration"
|
data-i18n="administration" data-settings-tab="administration"
|
||||||
id="settings-tab-administration" role="tab" tabindex="-1" type="button"></button><button
|
id="settings-tab-administration" role="tab" tabindex="-1" type="button"></button><button
|
||||||
|
aria-controls="settings-page-system-info" aria-selected="false"
|
||||||
|
data-i18n="systemInformation" data-settings-tab="system-info"
|
||||||
|
id="settings-tab-system-info" role="tab" tabindex="-1" type="button"></button><button
|
||||||
aria-controls="settings-page-certificate-api" aria-selected="false"
|
aria-controls="settings-page-certificate-api" aria-selected="false"
|
||||||
data-i18n="certificateApiTitle" data-settings-tab="certificate-api"
|
data-i18n="certificateApiTitle" data-settings-tab="certificate-api"
|
||||||
id="settings-tab-certificate-api" role="tab" tabindex="-1" type="button"></button><button
|
id="settings-tab-certificate-api" role="tab" tabindex="-1" type="button"></button><button
|
||||||
@@ -2698,30 +2701,52 @@
|
|||||||
<div class="form-actions"><button class="primary" data-i18n="apply"
|
<div class="form-actions"><button class="primary" data-i18n="apply"
|
||||||
type="submit">Validate, save and apply</button></div>
|
type="submit">Validate, save and apply</button></div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
<div aria-labelledby="settings-tab-system-info" class="settings-page"
|
||||||
|
data-settings-page="system-info" hidden="" id="settings-page-system-info" role="tabpanel">
|
||||||
<article class="card edit-form runtime-information-card">
|
<article class="card edit-form runtime-information-card">
|
||||||
<div class="card-heading">
|
<div class="card-heading">
|
||||||
<h2 data-i18n="runtimeInformation">Runtime information</h2><span
|
<h2 data-i18n="systemInformation">System information</h2><span class="pill">API</span>
|
||||||
class="pill">API</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="fine" data-i18n="runtimeInformationHelp">Reported by the running RustProxy
|
<p class="fine" data-i18n="systemInformationHelp">Live, read-only information from the authenticated RustProxy status API. Unavailable metrics are not estimated.</p>
|
||||||
process and available only from the authenticated administrator status API.</p>
|
<h3 data-i18n="systemHostHeading">Operating system and hardware</h3>
|
||||||
<div class="runtime-information-grid">
|
<div class="runtime-information-grid">
|
||||||
<div><span class="small-label" data-i18n="runtimePlatform">Platform</span><code
|
<div><span class="small-label" data-i18n="runtimePlatform">Platform</span><code id="runtime-platform">-</code></div>
|
||||||
id="runtime-platform">-</code></div>
|
<div><span class="small-label" data-i18n="systemOsVersion">Operating system</span><code id="runtime-operating-system">-</code></div>
|
||||||
<div><span class="small-label" data-i18n="statisticsSource">Statistics
|
<div><span class="small-label" data-i18n="systemKernel">Kernel</span><code id="runtime-kernel-version">-</code></div>
|
||||||
source</span><code id="runtime-statistics-source">-</code></div>
|
<div><span class="small-label" data-i18n="systemArchitecture">Architecture</span><code id="runtime-architecture">-</code></div>
|
||||||
<div><span class="small-label" data-i18n="dataDirectory">Data directory</span><code
|
<div><span class="small-label" data-i18n="systemLogicalCpus">Logical CPUs</span><code id="runtime-logical-cpus">-</code></div>
|
||||||
id="runtime-data-directory">-</code></div>
|
<div><span class="small-label" data-i18n="systemCpuLoad">CPU usage</span><code id="runtime-cpu-usage">-</code></div>
|
||||||
<div><span class="small-label" data-i18n="databaseLocation">Database</span><code
|
<div><span class="small-label" data-i18n="systemMemory">RAM used / total</span><code id="runtime-system-memory">-</code></div>
|
||||||
id="runtime-database-path">-</code></div>
|
<div><span class="small-label" data-i18n="systemUptime">System uptime</span><code id="runtime-system-uptime">-</code></div>
|
||||||
<div><span class="small-label" data-i18n="runtimeAcmeMode">ACME mode</span><code
|
</div>
|
||||||
id="runtime-acme-mode">-</code></div>
|
<h3 data-i18n="systemProcessHeading">RustProxy process</h3>
|
||||||
<div><span class="small-label" data-i18n="runtimePython">Python</span><code
|
<div class="runtime-information-grid">
|
||||||
id="runtime-python">-</code></div>
|
<div><span class="small-label" data-i18n="systemAppVersion">RustProxy version</span><code id="runtime-version">-</code></div>
|
||||||
<div><span class="small-label" data-i18n="runtimePip">pip</span><code
|
<div><span class="small-label" data-i18n="systemProcessUptime">Process uptime</span><code id="runtime-process-uptime">-</code></div>
|
||||||
id="runtime-pip">-</code></div>
|
<div><span class="small-label" data-i18n="systemProcessMemory">Process RAM (RSS)</span><code id="runtime-process-memory">-</code></div>
|
||||||
<div><span class="small-label" data-i18n="runtimeCertbot">Certbot</span><code
|
<div><span class="small-label" data-i18n="systemProcessThreads">Process threads</span><code id="runtime-process-threads">-</code></div>
|
||||||
id="runtime-certbot">-</code></div>
|
<div><span class="small-label" data-i18n="systemOpenFds">Open file descriptors</span><code id="runtime-open-fds">-</code></div>
|
||||||
|
<div><span class="small-label" data-i18n="statisticsSource">Statistics source</span><code id="runtime-statistics-source">-</code></div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="card edit-form runtime-information-card">
|
||||||
|
<div class="card-heading"><h2 data-i18n="systemCertificateHeading">ACME / Python environment</h2></div>
|
||||||
|
<p class="fine" data-i18n="systemCertificateHelp">Versions refer to the interpreter and tools actually selected for certificate management, not an arbitrary Python installation.</p>
|
||||||
|
<div class="runtime-information-grid">
|
||||||
|
<div><span class="small-label" data-i18n="runtimeAcmeMode">ACME mode</span><code id="runtime-acme-mode">-</code></div>
|
||||||
|
<div><span class="small-label" data-i18n="systemAcmeStatus">ACME status</span><code id="runtime-acme-status">-</code></div>
|
||||||
|
<div><span class="small-label" data-i18n="runtimePython">Python</span><code id="runtime-python">-</code></div>
|
||||||
|
<div><span class="small-label" data-i18n="runtimePip">pip</span><code id="runtime-pip">-</code></div>
|
||||||
|
<div><span class="small-label" data-i18n="runtimeCertbot">Certbot</span><code id="runtime-certbot">-</code></div>
|
||||||
|
<div><span class="small-label" data-i18n="certificateRuntimePath">Managed environment</span><code id="runtime-acme-directory">-</code></div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="card edit-form runtime-information-card">
|
||||||
|
<div class="card-heading"><h2 data-i18n="systemPathsHeading">Data locations</h2></div>
|
||||||
|
<div class="runtime-information-grid">
|
||||||
|
<div><span class="small-label" data-i18n="dataDirectory">Data directory</span><code id="runtime-data-directory">-</code></div>
|
||||||
|
<div><span class="small-label" data-i18n="databaseLocation">Database</span><code id="runtime-database-path">-</code></div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
@@ -3659,18 +3684,9 @@ log_errors = On</code></pre>
|
|||||||
<dt data-i18n="aboutLicense">License</dt>
|
<dt data-i18n="aboutLicense">License</dt>
|
||||||
<dd data-i18n="aboutLicenseValue">Open Source</dd>
|
<dd data-i18n="aboutLicenseValue">Open Source</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<dt data-i18n="runtimeAcmeMode">ACME mode</dt>
|
|
||||||
<dd id="about-acme-mode">-</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt data-i18n="aboutPythonRuntime">Certificate Python runtime</dt>
|
|
||||||
<dd id="about-python-runtime">-</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
</dl>
|
||||||
<p data-i18n="aboutOpenSourceText">RustProxy is an open-source project. Its source code is publicly
|
<p data-i18n="aboutOpenSourceText">RustProxy is an open-source project. Its source code is publicly
|
||||||
available for inspection, development and community contribution.</p>
|
available for inspection, development and community contribution.</p>
|
||||||
<p class="about-python-details" id="about-python-details"></p>
|
|
||||||
<a class="outline about-repository" href="https://git.linuxiarz.pl/gru/RustProxy" rel="noopener noreferrer"
|
<a class="outline about-repository" href="https://git.linuxiarz.pl/gru/RustProxy" rel="noopener noreferrer"
|
||||||
target="_blank"><span data-i18n="aboutRepository">Official source
|
target="_blank"><span data-i18n="aboutRepository">Official source
|
||||||
repository</span><span>git.linuxiarz.pl/gru/RustProxy</span></a>
|
repository</span><span>git.linuxiarz.pl/gru/RustProxy</span></a>
|
||||||
|
|||||||
@@ -219,13 +219,44 @@ curl --fail --show-error --request POST \
|
|||||||
const syncAdminTls = () => { $("admin-certificate").disabled = !$("admin-tls").checked; }; syncAdminTls();
|
const syncAdminTls = () => { $("admin-certificate").disabled = !$("admin-tls").checked; }; syncAdminTls();
|
||||||
$("admin-status").textContent = adminStatus?.restart_required ? t("restartPending") : adminStatus?.active?.tls ? `${t("administrationActiveHttps")}: ${adminStatus.active.listen}` : `${t("administrationActiveHttp")}: ${adminStatus?.active?.listen || latestStatus?.admin_listen || "-"}`;
|
$("admin-status").textContent = adminStatus?.restart_required ? t("restartPending") : adminStatus?.active?.tls ? `${t("administrationActiveHttps")}: ${adminStatus.active.listen}` : `${t("administrationActiveHttp")}: ${adminStatus?.active?.listen || latestStatus?.admin_listen || "-"}`;
|
||||||
const runtime = latestStatus?.runtime || {};
|
const runtime = latestStatus?.runtime || {};
|
||||||
for (const [id, key] of [["runtime-platform", "platform"], ["runtime-statistics-source", "statistics_source"], ["runtime-data-directory", "data_directory"], ["runtime-database-path", "database_path"]]) {
|
const system = latestStatus?.system || {};
|
||||||
const node = $(id); if (node) node.textContent = runtime[key] || t("unavailable");
|
const hasMetric = value => value !== null && value !== undefined && Number.isFinite(Number(value));
|
||||||
}
|
const humanCount = value => hasMetric(value) ? Number(value).toLocaleString(locale()) : t("unavailable");
|
||||||
|
const humanBytes = value => hasMetric(value) ? bytes(Number(value)) : t("unavailable");
|
||||||
|
const humanUptime = value => hasMetric(value) ? uptime(Number(value)) : t("unavailable");
|
||||||
|
const percent = value => hasMetric(value) ? `${Number(value).toLocaleString(locale(), { maximumFractionDigits: 1 })}%` : t("unavailable");
|
||||||
|
const ram = hasMetric(system.memory_used_bytes) || hasMetric(system.memory_total_bytes)
|
||||||
|
? `${humanBytes(system.memory_used_bytes)} / ${humanBytes(system.memory_total_bytes)}` : t("unavailable");
|
||||||
|
for (const [id, value] of [
|
||||||
|
["runtime-version", latestStatus?.version],
|
||||||
|
["runtime-platform", runtime.platform],
|
||||||
|
["runtime-operating-system", runtime.operating_system],
|
||||||
|
["runtime-kernel-version", runtime.kernel_version],
|
||||||
|
["runtime-architecture", runtime.architecture],
|
||||||
|
["runtime-logical-cpus", humanCount(system.logical_cpus)],
|
||||||
|
["runtime-cpu-usage", percent(system.cpu_percent)],
|
||||||
|
["runtime-system-memory", ram],
|
||||||
|
["runtime-system-uptime", humanUptime(system.system_uptime_seconds)],
|
||||||
|
["runtime-process-uptime", humanUptime(latestStatus?.uptime_seconds)],
|
||||||
|
["runtime-process-memory", humanBytes(system.process_rss_bytes)],
|
||||||
|
["runtime-process-threads", humanCount(system.process_threads)],
|
||||||
|
["runtime-open-fds", humanCount(system.open_fds)],
|
||||||
|
["runtime-statistics-source", runtime.statistics_source],
|
||||||
|
["runtime-data-directory", runtime.data_directory],
|
||||||
|
["runtime-database-path", runtime.database_path]
|
||||||
|
]) { const node = $(id); if (node) node.textContent = value ?? t("unavailable"); }
|
||||||
const certificateRuntime = runtime.certificate_runtime || {};
|
const certificateRuntime = runtime.certificate_runtime || {};
|
||||||
const availability = flag => flag === true ? t("runtimeAvailable") : flag === false ? t("runtimeUnavailable") : t("unavailable");
|
const availability = flag => flag === true ? t("runtimeAvailable") : flag === false ? t("runtimeUnavailable") : t("unavailable");
|
||||||
const modeLabel = certificateRuntime.status === "unavailable" ? t("certificateRuntimeUnavailable") : certificateRuntime.status === "ready" && certificateRuntime.mode === "managed" ? t("certificateRuntimeManaged") : certificateRuntime.status === "ready" && certificateRuntime.mode === "system" ? t("certificateRuntimeSystem") : t("certificateRuntimePreparing");
|
const modeLabel = certificateRuntime.status === "unavailable" ? t("certificateRuntimeUnavailable") : certificateRuntime.status === "ready" && certificateRuntime.mode === "managed" ? t("certificateRuntimeManaged") : certificateRuntime.status === "ready" && certificateRuntime.mode === "system" ? t("certificateRuntimeSystem") : t("certificateRuntimePreparing");
|
||||||
for (const [id, text] of [["runtime-acme-mode", modeLabel], ["runtime-python", availability(certificateRuntime.python)], ["runtime-pip", availability(certificateRuntime.pip)], ["runtime-certbot", availability(certificateRuntime.certbot)]]) { const node = $(id); if (node) node.textContent = text; }
|
const versioned = (flag, version) => version && flag === true ? `${availability(flag)} · ${version}` : availability(flag);
|
||||||
|
for (const [id, value] of [
|
||||||
|
["runtime-acme-mode", modeLabel],
|
||||||
|
["runtime-acme-status", certificateRuntime.message ? t(certificateRuntime.message) : t("unavailable")],
|
||||||
|
["runtime-python", versioned(certificateRuntime.python, certificateRuntime.python_version)],
|
||||||
|
["runtime-pip", versioned(certificateRuntime.pip, certificateRuntime.pip_version)],
|
||||||
|
["runtime-certbot", versioned(certificateRuntime.certbot, certificateRuntime.certbot_version)],
|
||||||
|
["runtime-acme-directory", certificateRuntime.managed_directory]
|
||||||
|
]) { const node = $(id); if (node) node.textContent = value || t("unavailable"); }
|
||||||
|
|
||||||
const policy = envelope.config.tls?.policy || { tls12: true, tls13: true, cipher_suites: [] };
|
const policy = envelope.config.tls?.policy || { tls12: true, tls13: true, cipher_suites: [] };
|
||||||
if (!settingsDirty.has("tls-policy-form")) { $("tls-policy-12").checked = policy.tls12 !== false; $("tls-policy-13").checked = policy.tls13 !== false; $("tls-policy-ciphers").value = (policy.cipher_suites || []).join("\n"); }
|
if (!settingsDirty.has("tls-policy-form")) { $("tls-policy-12").checked = policy.tls12 !== false; $("tls-policy-13").checked = policy.tls13 !== false; $("tls-policy-ciphers").value = (policy.cipher_suites || []).join("\n"); }
|
||||||
|
|||||||
+12
-1
@@ -7606,6 +7606,11 @@ body[data-density="compact"] .page-footer {
|
|||||||
background: currentColor;
|
background: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The legend keys must use the same theme/preset palette as the SVG strokes. */
|
||||||
|
.stat-chart .stat-chart-legend .legend-h1 i { background: var(--traffic-h1); }
|
||||||
|
.stat-chart .stat-chart-legend .legend-h2 i { background: var(--traffic-h2); }
|
||||||
|
.stat-chart .stat-chart-legend .legend-h3 i { background: var(--traffic-h3); }
|
||||||
|
|
||||||
.stat-chart .stat-chart-legend b {
|
.stat-chart .stat-chart-legend b {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -7809,7 +7814,13 @@ body[data-density="compact"] .page-footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.runtime-information-card {
|
.runtime-information-card {
|
||||||
margin-top: 12px;
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runtime-information-card h3 {
|
||||||
|
margin: 16px 0 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.runtime-information-grid {
|
.runtime-information-grid {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def read_docker_agent_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.30.0"
|
assert manifest["package"]["version"] == "0.30.1"
|
||||||
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"]
|
||||||
@@ -32,14 +32,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.30.0"' in lock
|
assert 'name = "rust-proxy"\nversion = "0.30.1"' 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.30.0",
|
"README.md": "# RustProxy 0.30.1",
|
||||||
"CHANGELOG.md": "## 0.30.0",
|
"CHANGELOG.md": "## 0.30.1",
|
||||||
"ROADMAP.md": "## 0.30.0",
|
"ROADMAP.md": "## 0.30.1",
|
||||||
"AGENTS.md": "Current source version: 0.30.0.",
|
"AGENTS.md": "Current source version: 0.30.1.",
|
||||||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.30.0",
|
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.30.1",
|
||||||
}.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"
|
||||||
@@ -285,7 +285,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa
|
|||||||
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
|
assert '"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.30.0"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
for marker in ['"version": "0.30.1"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
||||||
assert marker in openapi, marker
|
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
|
||||||
@@ -585,7 +585,7 @@ h2_settings_controls = integrations_js.split('function http2SettingsControls', 1
|
|||||||
assert 'listener-h2' not in h2_settings_controls, 'HTTP/2 enablement belongs to the listener editor, not the tuning page'
|
assert 'listener-h2' not in h2_settings_controls, 'HTTP/2 enablement belongs to the listener editor, not the tuning page'
|
||||||
h3_settings_controls = integrations_js.split('function http3SettingsControls', 1)[1].split('function addRow', 1)[0]
|
h3_settings_controls = integrations_js.split('function http3SettingsControls', 1)[1].split('function addRow', 1)[0]
|
||||||
assert 'h3Enable' not in h3_settings_controls, 'HTTP/3 enablement belongs to the listener editor, not the tuning page'
|
assert 'h3Enable' not in h3_settings_controls, 'HTTP/3 enablement belongs to the listener editor, not the tuning page'
|
||||||
for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'id="about-python-runtime"', 'id="runtime-python"', 'id="acme-runtime-components"']:
|
for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'data-settings-tab="system-info"', 'id="runtime-python"', 'id="acme-runtime-components"']:
|
||||||
assert marker in html, marker
|
assert marker in html, marker
|
||||||
for language in ['langs/en_US.json', 'langs/pl_pl.json']:
|
for language in ['langs/en_US.json', 'langs/pl_pl.json']:
|
||||||
language_text = (root / language).read_text(encoding='utf-8')
|
language_text = (root / language).read_text(encoding='utf-8')
|
||||||
@@ -1363,7 +1363,7 @@ assert metrics_dep["version"] == "=2.4.3" and metrics_dep["default-features"] is
|
|||||||
assert metrics_dep["features"] == ["dummy"]
|
assert metrics_dep["features"] == ["dummy"]
|
||||||
sysinfo_dep = manifest["dependencies"]["sysinfo"]
|
sysinfo_dep = manifest["dependencies"]["sysinfo"]
|
||||||
assert sysinfo_dep["version"] == "=0.38.4" and sysinfo_dep["default-features"] is False
|
assert sysinfo_dep["version"] == "=0.38.4" and sysinfo_dep["default-features"] is False
|
||||||
assert sysinfo_dep["features"] == ["network"]
|
assert set(sysinfo_dep["features"]) == {"network", "system"}
|
||||||
for marker_0241 in [
|
for marker_0241 in [
|
||||||
"systemstat::{Platform, System}", "metrics_process::collector::collect",
|
"systemstat::{Platform, System}", "metrics_process::collector::collect",
|
||||||
"sysinfo::Networks::new_with_refreshed_list", "collect_network_systemstat", "merge_network_interfaces",
|
"sysinfo::Networks::new_with_refreshed_list", "collect_network_systemstat", "merge_network_interfaces",
|
||||||
@@ -1396,7 +1396,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log"
|
|||||||
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
|
assert '"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 '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.30.0"' in openapi
|
assert '"RuntimeInformation"' in openapi and '"version": "0.30.1"' in openapi
|
||||||
|
|
||||||
# 0.24.1 compiler and hostname-cardinality regressions.
|
# 0.24.1 compiler and hostname-cardinality regressions.
|
||||||
tls_0241 = rust_source(root, "src/tls.rs")
|
tls_0241 = rust_source(root, "src/tls.rs")
|
||||||
@@ -1635,5 +1635,5 @@ for marker_0282 in [
|
|||||||
assert 'Zewnętrzne ścieżki plików' in (root / 'langs/pl_pl.json').read_text()
|
assert 'Zewnętrzne ścieżki plików' in (root / 'langs/pl_pl.json').read_text()
|
||||||
assert 'setfacl -m u:rustproxy:--x /etc/ssl/' in (root / 'docs/V0.28.2_IMPLEMENTATION.md').read_text()
|
assert 'setfacl -m u:rustproxy:--x /etc/ssl/' in (root / 'docs/V0.28.2_IMPLEMENTATION.md').read_text()
|
||||||
|
|
||||||
print("PASS: 0.30.0 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v9 migrations.")
|
print("PASS: 0.30.1 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v9 migrations.")
|
||||||
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
|
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Rust-independent documentation/Reader consistency checks for the 0.30.0 release."""
|
"""Rust-independent documentation/Reader consistency checks for the 0.30.1 release."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
from rust_source import rust_source
|
from rust_source import rust_source
|
||||||
@@ -64,7 +64,7 @@ assert '"/docs/content.json"' in admin
|
|||||||
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
||||||
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
|
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
|
||||||
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
|
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
|
||||||
assert readme.startswith("# RustProxy 0.30.0")
|
assert readme.startswith("# RustProxy 0.30.1")
|
||||||
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
|
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
|
||||||
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
|
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
|
||||||
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
|
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
|
||||||
|
|||||||
@@ -75,10 +75,10 @@ def check(root: Path, repair_known: bool = False) -> list[str]:
|
|||||||
raise ValueError(f"Conflicting Rust modules: {flat.relative_to(root)} and {module.relative_to(root)}")
|
raise ValueError(f"Conflicting Rust modules: {flat.relative_to(root)} and {module.relative_to(root)}")
|
||||||
for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/config/internal_routing.rs", "src/proxy/websocket.rs", "src/proxy/error_fallback.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/vhost/error_fallback.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/health.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/admin/web/drafts.js", "src/pages.rs", "tools/dev/mock_server.py", "tools/dev/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.5_AUDIT.md", "docs/V0.25.4_AUDIT.md", "docs/V0.25.3_AUDIT.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "tools/checks/docs_check.py", "tools/checks/web_server_contracts.py", "tools/checks/rust_source.py", "tools/checks/rust_module_contracts.py", "tools/checks/source_module_contracts.py", "scripts/runtime/acme_provider.py", "src/basic_auth.rs", "scripts/deploy/deploy.py", "scripts/deploy/deploy_core.py", "scripts/deploy/deploy_runtime.py", "scripts/migration/migrate_npm.py", "scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/build/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json"]:
|
for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/config/internal_routing.rs", "src/proxy/websocket.rs", "src/proxy/error_fallback.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/vhost/error_fallback.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/health.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/admin/web/drafts.js", "src/pages.rs", "tools/dev/mock_server.py", "tools/dev/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.5_AUDIT.md", "docs/V0.25.4_AUDIT.md", "docs/V0.25.3_AUDIT.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "tools/checks/docs_check.py", "tools/checks/web_server_contracts.py", "tools/checks/rust_source.py", "tools/checks/rust_module_contracts.py", "tools/checks/source_module_contracts.py", "scripts/runtime/acme_provider.py", "src/basic_auth.rs", "scripts/deploy/deploy.py", "scripts/deploy/deploy_core.py", "scripts/deploy/deploy_runtime.py", "scripts/migration/migrate_npm.py", "scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/build/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json"]:
|
||||||
if not (root / path).is_file():
|
if not (root / path).is_file():
|
||||||
raise ValueError(f"Incomplete RustProxy 0.30.0 source tree: missing {path}")
|
raise ValueError(f"Incomplete RustProxy 0.30.1 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.30.0 entry point; extract the complete package, not individual files.")
|
raise ValueError("src/lib.rs is not the 0.30.1 entry point; extract the complete package, not individual files.")
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ 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.30.0"' in (target / "Cargo.toml").read_text(encoding="utf-8")
|
assert 'version = "0.30.1"' 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()
|
||||||
@@ -146,7 +146,7 @@ def upgrade_smoke(tmp: Path) -> None:
|
|||||||
assert (target / "docker-agent/certificate.go").is_file()
|
assert (target / "docker-agent/certificate.go").is_file()
|
||||||
assert (target / "docker-agent/server.go").is_file()
|
assert (target / "docker-agent/server.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.30.0"
|
assert manifest["to_version"] == "0.30.1"
|
||||||
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()
|
||||||
@@ -434,7 +434,7 @@ def ui_contract_smoke() -> None:
|
|||||||
assert 'stream_list_sort' in list_sort_sources['streams']
|
assert 'stream_list_sort' in list_sort_sources['streams']
|
||||||
for marker in ['id="pool-search"', 'id="pool-sort"', 'data-pool-column-toggle="vhost"']:
|
for marker in ['id="pool-search"', 'id="pool-sort"', 'data-pool-column-toggle="vhost"']:
|
||||||
assert marker in html, marker
|
assert marker in html, marker
|
||||||
for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'id="about-python-runtime"', 'id="runtime-python"', 'id="acme-runtime-components"']:
|
for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'data-settings-tab="system-info"', 'id="runtime-python"', 'id="acme-runtime-components"']:
|
||||||
assert marker in html, marker
|
assert marker in html, marker
|
||||||
for language in ['langs/en_US.json', 'langs/pl_pl.json']:
|
for language in ['langs/en_US.json', 'langs/pl_pl.json']:
|
||||||
text = (ROOT / language).read_text(encoding='utf-8')
|
text = (ROOT / language).read_text(encoding='utf-8')
|
||||||
@@ -586,7 +586,7 @@ def source_contract_smoke() -> None:
|
|||||||
sysinfo_network = manifest["dependencies"]["sysinfo"]
|
sysinfo_network = manifest["dependencies"]["sysinfo"]
|
||||||
assert sysinfo_network["version"] == "=0.38.4"
|
assert sysinfo_network["version"] == "=0.38.4"
|
||||||
assert sysinfo_network["default-features"] is False
|
assert sysinfo_network["default-features"] is False
|
||||||
assert sysinfo_network["features"] == ["network"]
|
assert set(sysinfo_network["features"]) == {"network", "system"}
|
||||||
target_key = 'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "windows"))'
|
target_key = 'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "windows"))'
|
||||||
assert manifest["target"][target_key]["dependencies"]["systemstat"] == "=0.2.7"
|
assert manifest["target"][target_key]["dependencies"]["systemstat"] == "=0.2.7"
|
||||||
for marker in [
|
for marker in [
|
||||||
@@ -619,7 +619,7 @@ 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.30.0"' in openapi_0241
|
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.30.1"' 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"', 'id="error-log-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
|
||||||
@@ -1109,7 +1109,7 @@ def source_contract_smoke() -> None:
|
|||||||
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")
|
||||||
assert '"/api/config/history/restore"' in openapi
|
assert '"/api/config/history/restore"' in openapi
|
||||||
for marker in ['"version": "0.30.0"', '"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.30.1"', '"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"']:
|
||||||
@@ -1314,7 +1314,7 @@ def main() -> None:
|
|||||||
source_contract_smoke()
|
source_contract_smoke()
|
||||||
container_contract_smoke()
|
container_contract_smoke()
|
||||||
grafana_contract_smoke()
|
grafana_contract_smoke()
|
||||||
print("PASS: 0.30.0 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
|
print("PASS: 0.30.1 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Simulated status and tab DOM. No Rust process or visual browser is run here.
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import vm from 'node:vm';
|
||||||
|
|
||||||
|
const html = fs.readFileSync('src/admin/web/index.html', 'utf8');
|
||||||
|
const css = fs.readFileSync('src/admin/web/style.css', 'utf8');
|
||||||
|
const js = fs.readFileSync('src/admin/web/settings.js', 'utf8');
|
||||||
|
const core = fs.readFileSync('src/admin/web/app/core.js', 'utf8');
|
||||||
|
const api = JSON.parse(fs.readFileSync('src/admin/openapi.json', 'utf8'));
|
||||||
|
const has = (haystack, needle) => assert.ok(haystack.includes(needle), `Missing ${needle}`);
|
||||||
|
|
||||||
|
// The two mini charts share the exact H1/H2/H3 palette as their SVG lines.
|
||||||
|
for (const version of ['h1', 'h2', 'h3']) {
|
||||||
|
has(css, `.stat-chart .stat-chart-legend .legend-${version} i { background: var(--traffic-${version}); }`);
|
||||||
|
assert.match(css, new RegExp(`\\.proto-${version}-line\\s*\\{[^}]*stroke:\\s*var\\(--traffic-${version}\\);`));
|
||||||
|
has(html, `id="http-protocol-${version}"`);
|
||||||
|
has(html, `id="ws-protocol-${version}"`);
|
||||||
|
}
|
||||||
|
assert.equal((html.match(/class="stat-chart-legend"/g) || []).length, 2);
|
||||||
|
|
||||||
|
const about = html.split('<dialog aria-labelledby="about-title"')[1].split('</dialog>')[0];
|
||||||
|
for (const forbidden of ['about-acme-mode', 'about-python-runtime', 'about-python-details', 'runtimeAcmeMode']) {
|
||||||
|
assert.ok(!about.includes(forbidden), `ACME details left in About: ${forbidden}`);
|
||||||
|
}
|
||||||
|
assert.ok(!core.includes('renderAboutRuntime('), 'Obsolete About runtime renderer must be removed');
|
||||||
|
for (const marker of ['aboutAuthor', 'aboutLicense', 'aboutRepository', 'aboutPackagesRepository']) has(about, marker);
|
||||||
|
|
||||||
|
has(html, 'data-settings-tab="system-info"');
|
||||||
|
has(html, 'id="settings-page-system-info"');
|
||||||
|
const tab = html.split('id="settings-page-system-info"')[1].split('id="settings-page-certificate-api"')[0];
|
||||||
|
for (const field of ['runtime-operating-system', 'runtime-kernel-version', 'runtime-architecture',
|
||||||
|
'runtime-logical-cpus', 'runtime-system-memory', 'runtime-process-memory',
|
||||||
|
'runtime-acme-mode', 'runtime-python', 'runtime-pip', 'runtime-certbot',
|
||||||
|
'runtime-acme-directory', 'runtime-data-directory', 'runtime-database-path']) has(tab, `id="${field}"`);
|
||||||
|
assert.ok(!tab.includes('<form'), 'The System information page must be read-only');
|
||||||
|
const admin = html.split('id="settings-page-administration"')[1].split('id="settings-page-system-info"')[0];
|
||||||
|
assert.ok(!admin.includes('runtime-information-card'), 'Runtime data must be relocated, not duplicated');
|
||||||
|
|
||||||
|
// Execute the real status-to-settings rendering code against a synthetic status payload.
|
||||||
|
const code = js.slice(js.indexOf(' const runtime = latestStatus?.runtime || {};'), js.indexOf(' const policy = envelope.config.tls?', js.indexOf(' const runtime = latestStatus?.runtime || {};')));
|
||||||
|
const elements = new Map();
|
||||||
|
const dollar = id => { if (!elements.has(id)) elements.set(id, { textContent: '' }); return elements.get(id); };
|
||||||
|
const status = {
|
||||||
|
version: '0.30.1', uptime_seconds: 4201,
|
||||||
|
runtime: {
|
||||||
|
platform: 'Linux', operating_system: 'Debian GNU/Linux 13', kernel_version: '6.12.0', architecture: 'x86_64',
|
||||||
|
statistics_source: 'systemstat+sysinfo', data_directory: '/var/lib/rustproxy', database_path: '/var/lib/rustproxy/config.sqlite3',
|
||||||
|
certificate_runtime: { status: 'ready', mode: 'managed', message: 'acme.runtime_ready',
|
||||||
|
python: true, pip: true, certbot: true, python_version: '3.13.5', pip_version: '25.2',
|
||||||
|
certbot_version: '5.8.0', managed_directory: '/var/lib/rustproxy/acme-python' }
|
||||||
|
},
|
||||||
|
system: { logical_cpus: 8, cpu_percent: 17.2, memory_total_bytes: 16_384, memory_used_bytes: 8_192,
|
||||||
|
process_rss_bytes: 2_048, system_uptime_seconds: 100, process_threads: 9, open_fds: 67 }
|
||||||
|
};
|
||||||
|
const context = {
|
||||||
|
latestStatus: status, $: dollar, locale: () => 'en-US',
|
||||||
|
t: key => ({ runtimeAvailable: 'Available', runtimeUnavailable: 'Unavailable',
|
||||||
|
certificateRuntimeManaged: 'App-managed Python', 'acme.runtime_ready': 'Ready', unavailable: 'N/A' })[key] || key,
|
||||||
|
bytes: value => `${value} B`, uptime: value => `${value}s`,
|
||||||
|
};
|
||||||
|
vm.runInNewContext(code, context);
|
||||||
|
assert.equal(dollar('runtime-operating-system').textContent, 'Debian GNU/Linux 13');
|
||||||
|
assert.equal(dollar('runtime-kernel-version').textContent, '6.12.0');
|
||||||
|
assert.equal(dollar('runtime-version').textContent, '0.30.1');
|
||||||
|
assert.equal(dollar('runtime-logical-cpus').textContent, '8');
|
||||||
|
assert.equal(dollar('runtime-cpu-usage').textContent, '17.2%');
|
||||||
|
assert.equal(dollar('runtime-python').textContent, 'Available · 3.13.5');
|
||||||
|
assert.equal(dollar('runtime-pip').textContent, 'Available · 25.2');
|
||||||
|
assert.equal(dollar('runtime-certbot').textContent, 'Available · 5.8.0');
|
||||||
|
assert.equal(dollar('runtime-acme-directory').textContent, '/var/lib/rustproxy/acme-python');
|
||||||
|
|
||||||
|
// A working ACME runtime can have no trustworthy version metadata on some OSes.
|
||||||
|
elements.clear();
|
||||||
|
vm.runInNewContext(code, { ...context,
|
||||||
|
latestStatus: { ...status, runtime: { ...status.runtime, certificate_runtime: {
|
||||||
|
...status.runtime.certificate_runtime, python_version: null, pip_version: null, certbot_version: null
|
||||||
|
} } }
|
||||||
|
});
|
||||||
|
for (const field of ['runtime-python', 'runtime-pip', 'runtime-certbot']) {
|
||||||
|
assert.equal(dollar(field).textContent, 'Available');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status fields remain safely unavailable rather than displaying fake zeros.
|
||||||
|
elements.clear();
|
||||||
|
vm.runInNewContext(code, { ...context,
|
||||||
|
latestStatus: { runtime: { certificate_runtime: { status: 'unavailable', mode: 'none',
|
||||||
|
message: 'acme.runtime_python_missing', python: false, pip: false, certbot: false } }, system: {} }
|
||||||
|
});
|
||||||
|
for (const field of ['runtime-cpu-usage', 'runtime-logical-cpus', 'runtime-system-memory', 'runtime-python']) {
|
||||||
|
assert.ok(!/NaN|0 B/.test(dollar(field).textContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exercise real tab switching, deep-linking and search code without a browser.
|
||||||
|
const entry = js.slice(js.indexOf(' const buttons ='), js.indexOf(' const cacheTabs ='));
|
||||||
|
const buttons = ['administration', 'system-info', 'certificate-api'].map(name => ({
|
||||||
|
dataset: {settingsTab:name}, textContent:name, hidden:false, tabIndex:-1,
|
||||||
|
listeners:{}, setAttribute(name,value){this[name]=value;}, addEventListener(name,fn){this.listeners[name]=fn;}, focus(){} }));
|
||||||
|
const pages = buttons.map(b => ({dataset:{settingsPage:b.dataset.settingsTab}, hidden:true,textContent:b.dataset.settingsTab}));
|
||||||
|
const search={value:'',listeners:{},addEventListener(name,fn){this.listeners[name]=fn;}};
|
||||||
|
const searchStatus={textContent:''};
|
||||||
|
const loc = new URL('https://localhost/settings?tab=system-info');
|
||||||
|
let lastUrl='';
|
||||||
|
vm.runInNewContext(`(() => { ${entry} return true; })()`, {
|
||||||
|
document: {querySelectorAll: sel => sel === '[data-settings-tab]' ? buttons : pages,
|
||||||
|
querySelector: sel => pages.find(p => sel.includes(p.dataset.settingsPage))},
|
||||||
|
$: id => id === 'settings-search' ? search : searchStatus,
|
||||||
|
location: loc, history: {state:null,replaceState:(_state,_unused,url) => {lastUrl=url;}},
|
||||||
|
CSS:{escape:x=>x}, URL, URLSearchParams, t:x=>x,
|
||||||
|
});
|
||||||
|
buttons[1].listeners.click();
|
||||||
|
assert.equal(pages[1].hidden,false);
|
||||||
|
assert.equal(pages[0].hidden,true);
|
||||||
|
assert.equal(buttons[1]['aria-selected'],'true');
|
||||||
|
assert.equal(lastUrl,''); // Already at canonical deep link; no unnecessary history rewrite.
|
||||||
|
buttons[0].listeners.click();
|
||||||
|
assert.equal(pages[1].hidden,true);
|
||||||
|
assert.equal(lastUrl,'/settings?tab=administration');
|
||||||
|
|
||||||
|
const runtimeSchema = api.components.schemas.RuntimeInformation;
|
||||||
|
for (const key of ['operating_system','kernel_version','architecture']) assert.ok(runtimeSchema.properties[key]);
|
||||||
|
for (const key of ['python_version','pip_version','certbot_version']) {
|
||||||
|
assert.ok(runtimeSchema.properties.certificate_runtime.properties[key]);
|
||||||
|
assert.equal(runtimeSchema.properties.certificate_runtime.properties[key].nullable, true);
|
||||||
|
}
|
||||||
|
console.log('PASS: 0.30.1 legend palette, About cleanup, system tab/navigation, read-only API schema and simulated status rendering.');
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Offline source guard for subprocess-free system information; not a Rust runtime test."""
|
||||||
|
from pathlib import Path
|
||||||
|
import tomllib
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
manifest = tomllib.loads((ROOT / 'Cargo.toml').read_text())
|
||||||
|
for dependency in ('sysinfo', 'metrics-process'):
|
||||||
|
assert dependency in manifest['dependencies'], dependency
|
||||||
|
sysinfo_features = manifest['dependencies']['sysinfo']['features']
|
||||||
|
assert {'network', 'system'} <= set(sysinfo_features), (
|
||||||
|
'sysinfo::System and sysinfo::Networks require both system and network features'
|
||||||
|
)
|
||||||
|
assert any('systemstat' in target.get('dependencies', {}) for target in manifest.get('target', {}).values())
|
||||||
|
|
||||||
|
status = (ROOT / 'src/admin/status.rs').read_text()
|
||||||
|
assert 'sysinfo::System::long_os_version()' in status
|
||||||
|
assert 'sysinfo::System::kernel_version()' in status
|
||||||
|
assert 'std::sync::OnceLock' in status
|
||||||
|
for forbidden in ('Command::', 'std::process::Command', '/etc/os-release', '/proc/sys/kernel/osrelease'):
|
||||||
|
assert forbidden not in status, f'Status OS identification cannot use {forbidden}'
|
||||||
|
|
||||||
|
metadata = (ROOT / 'src/acme/runtime_versions.rs').read_text()
|
||||||
|
for marker in ('pyvenv.cfg', '.dist-info', 'METADATA', 'MAX_VERSION_METADATA_BYTES', 'MAX_PACKAGES_PER_SITE', 'fn runtime_versions('):
|
||||||
|
assert marker in metadata, marker
|
||||||
|
for forbidden in ('Command::', '.spawn(', 'version_token(', '"--version"', "'-c'", '.await'):
|
||||||
|
assert forbidden not in metadata, f'Diagnostic versions must not execute: {forbidden}'
|
||||||
|
|
||||||
|
runtime = (ROOT / 'src/acme/runtime_env.rs').read_text()
|
||||||
|
assert 'include!("runtime_versions.rs")' in runtime
|
||||||
|
assert 'runtime_versions(&venv_python(&managed), true, Some(&venv_certbot(&managed)))' in runtime
|
||||||
|
assert 'python_version: versions.python' in runtime and 'certbot_version: versions.certbot' in runtime
|
||||||
|
assert 'path.with_extension("exe")' in runtime, 'Windows executable discovery must include .exe'
|
||||||
|
|
||||||
|
regressions = (ROOT / 'src/acme/tests.rs').read_text()
|
||||||
|
for marker in ('managed_python_pip_and_certbot_versions_use_metadata_only',
|
||||||
|
'absent_or_unverified_versions_are_not_invented',
|
||||||
|
'conflicting_site_distributions_do_not_return_stale_versions'):
|
||||||
|
assert marker in regressions, marker
|
||||||
|
print('PASS: shared Rust libraries, portable OS detection, no diagnostic subprocesses, bounded metadata and source regressions.')
|
||||||
Reference in new issue
Block a user