This commit is contained in:
Mateusz Gruszczyński committed 2026-10-05 10:58:50 +02:00
1 parent c819f0a335
commit e1a59f6e56
53 files changed
+817 -380

No files matched your search

+2 -2
View File
@@ -226,8 +226,8 @@ endpoint or separate unsynchronized frontend/backend write API.
includes `id`, `algorithm`, `ready`, `health_mode`, `sticky_mode`, `keepalive`,
`members`. Members expose ID/origin/weight, status, healthy flag, active exchanges,
request/error/byte totals, EWMA byte rate/header latency, last check/result, failure
and success counters, transition count and last upstream status. It does not expose
affinity tokens. Member status is up/down/checking/draining/disabled. In health-off
and success counters, transition count, last upstream status and upstream-client
generation/request/age/rotation counters. It does not expose affinity tokens. Member status is up/down/checking/draining/disabled. In health-off
mode, eligibility is not proof that the origin is live. Draining describes operator
intent; inspect `healthy` as well. `ready` reflects new-assignment availability;
a pool containing only draining members may still serve existing cookie sessions.
+1 -1
View File
@@ -1,4 +1,4 @@
# Administration API access - 0.25.5
# Administration API access - 0.25.6
## Sign-in boundary
+1 -1
View File
@@ -167,7 +167,7 @@ The 0.25.0 development runtime adds two maintenance mechanisms that do not reloa
- every 60 seconds on Linux/glibc, RustProxy calls `malloc_trim(0)` from a blocking maintenance task; this can return allocator pages that are already free, but cannot and does not evict live Rust objects;
- HOT cache maintenance trims only least-recently-used entries that have been idle for the configured delay and only while HOT allocation is above the configured target waterline. The pass is bounded by `hot_eviction_max_objects`; recent/active entries are not purged.
Upstream HTTP keep-alive is not reset on a timer. It is already bounded per backend by `max_idle_per_host` and `idle_timeout_seconds`, so forcing periodic client/snapshot replacement would trade memory symptoms for connection churn and lower proxy efficiency. If RSS remains high while HOT/transient usage and connection-pool bounds are stable, the remaining resident pages may still be active runtime/TLS/QUIC/Hyper/Tokio working set rather than reclaimable cache memory.
Upstream HTTP clients now also have bounded generations. Each backend Hyper client is recycled after 25,000 requests or 3,600 seconds by default (both limits are configurable under vhost keepalive and either can be disabled with `0`). Recycling replaces only the backend client/pool and leaves the vhost snapshot, HOT cache and configuration untouched. HTTP/1 upstream read buffers are capped at 128 KiB by default. This bounds long-lived Hyper/TLS/pool high-water allocations while preserving normal keep-alive reuse inside each generation. The existing 60-second allocator trim can then return already-free glibc pages to the OS.
## Hot and cold tiers
+1 -1
View File
@@ -1,4 +1,4 @@
# Installing and updating RustProxy 0.25.5
# Installing and updating RustProxy 0.25.6
The scripts prepare a Linux/systemd installation. This source archive is **not a
verified production build**: Rust/Cargo, real CA operations, live systemd startup
+4
View File
@@ -1,5 +1,9 @@
# Deployment and configuration migration history
## 0.25.5 -> 0.25.6
There is no SQLite schema or saved-configuration migration. Version 0.25.6 adds the already-exposed upstream client generation/rotation diagnostics to the live Backends table and keeps the 0.25.5 backend client recycling defaults unchanged.
## 0.25.4 -> 0.25.5
There is no SQLite schema or saved-configuration migration. Existing `cloudflare_client_ip` vhost settings remain opt-in. When enabled and the direct peer is a verified Cloudflare edge, RustProxy now prefers a valid `CF-IPCountry` value for country analytics/logging and country policy evaluation, falling back to the installed GeoIP database when that header is unavailable or invalid.
+30 -23
View File
@@ -1,8 +1,8 @@
# Verification report - RustProxy 0.25.5
# Verification report - RustProxy 0.25.6
## Scope
RustProxy 0.25.5 is a patch release for dashboard consistency and observability: Revision History used a separate rounded modal style, Cloudflare-backed vhosts resolved Traffic country from local GeoIP without consulting Cloudflare's verified country header, Prometheus did not expose the bounded Traffic dimensions or an explicit built-in Web Server serving-source view, the mobile dashboard rendered the full section list permanently across the top of the page, and the dashboard lacked a compact About surface. The top bar now exposes project About information and uses the full Documentation label. The patch keeps SQLite schema 8 and the existing configuration format.
RustProxy 0.25.6 is a patch release that keeps the bounded upstream-client memory controls from 0.25.5 and makes their runtime state visible in the main Backends diagnostics table. Each row now shows client generation, requests in the current generation, generation age and total rotations. Those values come from the same backend-pool status object delivered through `/api/events` and `/api/status`, so the dashboard refreshes them with the existing live diagnostics path. The defaults remain 25,000 requests, 3,600 seconds and a 131,072-byte upstream HTTP/1 adaptive read-buffer ceiling. Recycling remains local to the backend client and does not reload the vhost or purge cache. SQLite remains schema 8 and no saved-configuration migration is introduced.
## Source contracts reviewed
@@ -46,7 +46,8 @@ RustProxy 0.25.5 is a patch release for dashboard consistency and observability:
- Logging settings are split into Outputs, JSON format and Error log sub-tabs inside one card, matching the Cache subsection layout. `route`, `cache_effective_enabled` and `transform_effective_enabled` are accepted by the same backend validator that saves custom JSON mappings, and the regression test validates then renders those fields.
- Structured logging exposes `user_agent` and `country`. User-Agent capture defaults on; country capture defaults off. Country resolution is skipped unless capture or an existing country ACL/rate-limit needs it; verified Cloudflare mode prefers trusted `CF-IPCountry`, otherwise local GeoIP is used.
- The authenticated Traffic workspace uses a bounded 2048-sample non-blocking queue, at most 64 vhost buckets and 32-entry approximate top tables for countries, User-Agents, effective client IPs, paths, routes, methods, status classes and cache outcomes. It never writes per-request analytics to SQLite. The Traffic UI is split into Overview, Clients, Requests and Delivery sub-tabs. Countries/methods/status/cache use donut summaries; User-Agent, effective client IP, path, route and request/response byte volume use bounded horizontal bar charts from the same in-memory TOP state.
- Sustained-traffic memory maintenance calls `malloc_trim(0)` every 60 seconds on Linux/glibc to release already-free allocator pages and trims HOT objects only when above the configured waterline and idle past the configured age. It does not periodically reload vhosts, purge active objects or reset bounded upstream keep-alive pools.
- Sustained-traffic memory maintenance calls `malloc_trim(0)` every 60 seconds on Linux/glibc to release already-free allocator pages and trims HOT objects only when above the configured waterline and idle past the configured age. Backend Hyper clients additionally recycle their connection-pool generation after 25,000 requests or 3,600 seconds by default; both triggers are configurable/disableable per vhost, and HTTP/1 upstream adaptive read buffers default to a 128 KiB ceiling. Recycling does not publish a new vhost snapshot or purge cache.
- The live Backends table renders `upstream_client_generation`, `upstream_client_generation_requests`, `upstream_client_generation_age_seconds` and `upstream_client_rotations_total` in the same row as Active/Requests/Errors/Latency/Traffic. `app/core.js` feeds the table from the existing `/api/events` `snapshot.status` payload, so the new fields do not introduce another polling loop or control-plane query.
- Cache compatibility currently includes vhost configuration, so a cache-affecting vhost save can purge cache during snapshot publication; replacing the compiled snapshot also retires its upstream clients after outstanding references drain. These mechanisms explain why a manual vhost save can visibly reduce RSS without implying that reloads are required for steady-state operation.
- The Web Server list now uses the same vhost card/detail-cell visual structure as the Vhosts list. Reverse Proxy vhost creation reports a dedicated “at least one backend” validation error instead of the generic paths/ACL/headers JSON error.
- Web Service cards use only the shared `el(...)` DOM helper; a regression contract rejects the previous undefined `text(...)` call that aborted list rendering. Authenticated-shell startup now verifies `/api/auth/session` first and never reveals the legacy embedded sign-in form for non-authentication JavaScript/API failures, preventing F5 from falsely presenting the user as logged out.
@@ -58,41 +59,47 @@ RustProxy 0.25.5 is a patch release for dashboard consistency and observability:
## Implemented automated coverage
Rust unit tests in `src/web_server/tests.rs` cover GET/HEAD/index/slash redirect, MIME/length, validators, valid/invalid range handling, If-Range, escaped/repeated directory listings, per-directory index-before-listing fallback, generic 403/HEAD behavior, MIME overrides/default MIME, cache rules, source headers, `try_files`, rewrite redirect/internal behavior, `.htaccess` WordPress-style front-controller routing, nested DirectoryIndex/MIME/Header/Expires behavior, strict-mode rejection and PHP-source protection, hardening probe/method blocking and fingerprint-header stripping, precompressed representations/ranges, download headers, PHP FastCGI fixture execution/failure-source-disclosure behavior, request-body 413 enforcement, custom FastCGI/PHP_VALUE validation and reserved-parameter rejection, direct traversal rejection and Unix/Linux symlink policy. `src/traffic/tests.rs` includes bounded-table, effective-client-IP view and fixed Prometheus User-Agent/method family coverage. Configuration tests cover pure static vhosts without a pool, mixed Web Server/Reverse Proxy routing, reuse of one Web Service by multiple vhosts, unknown service references and backward-compatible default source behavior. Vhost tests cover method-policy normalization/allow/block behavior, database tests cover retained configuration revision documents, actor metadata and previous-revision lookup, and admin tests cover restoring revision 1 as revision 3 while preserving the previous document and emitting `config.restore`. ACL tests include encoded traversal/separator/malformed-encoding cases.
Rust unit tests in `src/web_server/tests.rs` cover GET/HEAD/index/slash redirect, MIME/length, validators, valid/invalid range handling, If-Range, escaped/repeated directory listings, per-directory index-before-listing fallback, generic 403/HEAD behavior, MIME overrides/default MIME, cache rules, source headers, `try_files`, rewrite redirect/internal behavior, `.htaccess` WordPress-style front-controller routing, nested DirectoryIndex/MIME/Header/Expires behavior, strict-mode rejection and PHP-source protection, hardening probe/method blocking and fingerprint-header stripping, precompressed representations/ranges, download headers, PHP FastCGI fixture execution/failure-source-disclosure behavior, request-body 413 enforcement, custom FastCGI/PHP_VALUE validation and reserved-parameter rejection, direct traversal rejection and Unix/Linux symlink policy. Upstream tests cover request-budget client generation recycling and disabled recycling; balancing tests cover the new keepalive defaults and validation bounds. `src/traffic/tests.rs` includes bounded-table, effective-client-IP view and fixed Prometheus User-Agent/method family coverage. Configuration tests cover pure static vhosts without a pool, mixed Web Server/Reverse Proxy routing, reuse of one Web Service by multiple vhosts, unknown service references and backward-compatible default source behavior. Vhost tests cover method-policy normalization/allow/block behavior, database tests cover retained configuration revision documents, actor metadata and previous-revision lookup, and admin tests cover restoring revision 1 as revision 3 while preserving the previous document and emitting `config.restore`. ACL tests include encoded traversal/separator/malformed-encoding cases.
Rust-independent release checks additionally validate UI/JSON/language/source contracts and reproducible packaging.
During the prior 0.25.4 compiler gate, an external build exposed one module-privacy error in the history split (`CONFIG_HISTORY_CAPACITY` was private to `db::history`) and one unused `country_resolved` assignment in the location GeoIP branch. Both were corrected before the 0.25.5 baseline: the capacity is exposed to its parent module with `pub(super)`, referenced through `history::CONFIG_HISTORY_CAPACITY`, and the dead assignment is removed. This workspace still has no Rust/Cargo toolchain, so compiler confirmation of the 0.25.5 tree remains a release-host gate.
During the prior 0.25.4 compiler gate, an external build exposed one module-privacy error in the history split (`CONFIG_HISTORY_CAPACITY` was private to `db::history`) and one unused `country_resolved` assignment in the location GeoIP branch. Both were corrected before the 0.25.5 baseline: the capacity is exposed to its parent module with `pub(super)`, referenced through `history::CONFIG_HISTORY_CAPACITY`, and the dead assignment is removed. This workspace still has no Rust/Cargo toolchain, so compiler confirmation of the 0.25.6 tree remains a release-host gate.
### Modular source-tree verification
- `python3 scripts/rust_module_contracts.py`: PASS. Every repository `.rs` file is at most 500 lines.
- `python3 scripts/source_module_contracts.py`: PASS. Dashboard JavaScript, Docker Agent Go implementation and operational deploy/NPM-migration Python modules are at most 500 lines; Docker tests remain in `_test.go`, and browser split boundaries/public bundles parse with Node when available.
- Oversized `app.js`/`vhosts.js` source is split while the public `/assets/app.js` and `/assets/vhosts.js` URLs remain stable; the Docker Agent is split into model/Docker/network/certificate/server/entry-point files.
- Rust unit-test bodies are isolated under `tests.rs` / `tests/`; implementation files contain only guarded test includes/declarations.
- All `include!("*.rs")` targets resolve, split fragments have balanced delimiters, and no fragment ends in a dangling Rust attribute or doc comment.
- Regression guards cover the Web Server split errors previously observed around `#[derive]`, `htaccess`, `server_protocol` and `anyhow::Context`.
- This is a compiler-independent structural check; Cargo parsing/type checking remains a release-host requirement when the Rust toolchain is unavailable in the packaging environment.
- The new upstream client implementation is split into `src/upstream/client.rs` with test bodies in `src/upstream/client_tests.rs`; both stay below the 500-line module limit, and `src/upstream.rs` remains below that limit after moving client construction out.
- `python3 scripts/source_module_contracts.py`: PASS. Dashboard JavaScript, Docker Agent Go implementation and operational deploy/NPM-migration Python modules remain within their source-size contracts.
- `python3 scripts/rust_module_contracts.py` is BLOCKED by a pre-existing source-tree issue in the supplied 0.25.5 baseline archive: `src/admin/documentation.rs` is 554 lines while the contract limit is 500. The same failure reproduces on the unmodified 0.25.5 baseline archive.
- `python3 scripts/docs_check.py` is also BLOCKED by a pre-existing supplied-archive issue: the Reader does not embed `docs/ACME_PROVIDERS.md`. This also reproduces on the unmodified input archive.
- Rust unit-test bodies added for this change are isolated in a test include file; the implementation module contains only the guarded include declaration.
- Cargo parsing/type checking remains a release-host requirement because this workspace has no Rust toolchain.
## Verification status in this workspace
| Check | Status |
|---|---|
| `python3 scripts/langs_check.py` | PASS |
| `python3 scripts/docs_check.py` | PASS |
| `python3 scripts/memory_contracts.py` | PASS |
| `node --check src/admin/web/vhosts/render.js` | PASS |
| Backend live-table source contract | PASS: four client-generation diagnostics are rendered from backend member status fields |
| `python3 scripts/memory_contracts.py` | PASS, including upstream generation-recycle/buffer-cap source contracts |
| `python3 scripts/web_server_contracts.py` | PASS |
| Browser JavaScript `node --check` | PASS for split app source, vhost split boundaries/public bundle, Settings, Docs Reader, Web Server and Traffic |
| `python3 scripts/security_contracts.py` | PASS |
| `python3 scripts/security_dependency_contracts.py` | PASS |
| `python3 scripts/source_module_contracts.py` | PASS |
| `node --check src/admin/web/vhosts/editor.js` | PASS |
| `node scripts/web_service_ui_test.mjs` | PASS |
| OpenAPI/language JSON parse | PASS |
| Shell `bash -n` | PASS in source/release checks |
| `go test ./...` in `docker-agent` | PASS |
| Python source parsing/contract checks | PASS |
| `python3 scripts/check_source.py` | PASS |
| `python3 scripts/release_smoke.py` | PASS |
| `./dev.sh test` Rust-independent phases | PASS; Rust phase BLOCKED because Cargo is unavailable |
| SQL migration/backup smoke (`release_smoke.sql_and_backup_smoke`) | PASS |
| Stopped-source upgrade smoke (`release_smoke.upgrade_smoke`) | PASS |
| `python3 scripts/rust_module_contracts.py` | BLOCKED by pre-existing `src/admin/documentation.rs` 554-line baseline failure |
| `python3 scripts/docs_check.py` | BLOCKED by pre-existing missing `docs/ACME_PROVIDERS.md` Reader embed |
| `python3 scripts/check_source.py` | BLOCKED because it invokes the same pre-existing Rust module-size gate |
| `python3 scripts/release_smoke.py` | BLOCKED because it invokes the same pre-existing Rust module-size gate; its SQL/upgrade smoke functions pass when run directly |
| `./dev.sh test` | BLOCKED at the same pre-existing Rust module-size gate before reaching Cargo |
| `cargo fmt --all` / `./dev.sh fmt` | BLOCKED: Rust/Cargo toolchain unavailable in this workspace |
| `cargo check --locked --all-targets` / `./dev.sh check` | BLOCKED: Rust/Cargo toolchain unavailable in this workspace |
| `cargo test --locked --all-targets` / Rust phase of `./dev.sh test` | BLOCKED: Rust/Cargo toolchain unavailable in this workspace |
| Live H1/H2/H3 Web Server integration and large-file acceptance | release-host acceptance gate |
| `cargo test --locked --all-targets` | BLOCKED: Rust/Cargo toolchain unavailable in this workspace |
| Live sustained HTTPS/HTTP/1.1 RSS acceptance | release-host acceptance gate; verify RSS plateau and `upstream_client_rotations_total` under representative traffic |
The source package therefore has static/source-contract verification here, but compiler-enabled and live transport acceptance must still be run on the release host before production release.
The new source-level memory bounds and UI/config contracts pass the available targeted checks. Compiler-enabled and sustained-traffic runtime acceptance remain required before production release; two unrelated static gates are already failing in the supplied 0.25.5 baseline as described above.
+9 -5
View File
@@ -204,8 +204,12 @@ requires `HTTP2-Settings`, and becomes HTTP/2 stream 1 after `101`. Optional dow
remains a separate QUIC listener capability (see HTTP3.md).
Pool keepalive settings configure idle connection reuse, idle timeout/limit, TCP
keepalive and HTTP/2 active-connection pings. Health probes are separate no-reuse
HTTP/1.1 or raw TCP operations; their concurrency cap is 16. Existing active exchanges
are not killed by disabling reuse or draining a member. Config publication rebuilds
client pools while preserving compatible observations and existing exchange leases.
The full contract and bounds are in VHOSTS.md. Keepalived/VRRP is not involved.
keepalive and HTTP/2 active-connection pings. They also bound each backend Hyper client
generation by request count/age and cap the upstream HTTP/1 adaptive read buffer. Client
generation recycling is local to the backend pool and does not publish a new vhost
snapshot or purge cache. Health probes are separate no-reuse HTTP/1.1 or raw TCP
operations; their concurrency cap is 16. Existing active exchanges are not intentionally
killed by disabling reuse, recycling a generation or draining a member. Config
publication still rebuilds client pools while preserving compatible observations and
existing exchange leases. The full contract and bounds are in VHOSTS.md. Keepalived/VRRP
is not involved.
+12 -4
View File
@@ -194,13 +194,21 @@ origin do not expose a selected backend or stale origin timing.
| `idle_timeout_seconds` | 30 | 1..3600 |
| `max_idle_per_host` | 4 | 1..64 |
| `tcp_keepalive_seconds` | 30 | 1..3600 |
| `pool_recycle_requests` | 25000 | 0 (off) or 1000..10000000 |
| `pool_recycle_seconds` | 3600 | 0 (off) or 60..604800 |
| `http1_max_buffer_bytes` | 131072 | 8192..1048576 |
These configure HTTP idle reuse and TCP keepalive; HTTP/2 also uses pings for active
connections, with a 10-second ping timeout and no pinging solely to keep idle
connections alive. Disabling reuse sets the idle pool limit to zero and disables
those keepalive probes. It does not abort already active requests/tunnels. The idle
limit is not a separate per-backend active-request cap; global connection/exchange
bounds still apply. HTTP health probes use separate no-reuse HTTP/1.1 connections.
connections alive. `pool_recycle_requests` and `pool_recycle_seconds` bound the lifetime
of each backend Hyper client generation. Recycling swaps only that backend client/pool;
it does not reload the vhost, purge cache or intentionally abort active exchanges. The
request counter is per backend client generation, not per TCP connection. Setting either
recycle value to `0` disables that trigger independently. `http1_max_buffer_bytes` bounds
Hyper's adaptive upstream HTTP/1 read buffer per connection; it does not apply to HTTP/2.
Disabling reuse sets the idle pool limit to zero and disables keepalive probes. The idle
limit is not a separate per-backend active-request cap; global connection/exchange bounds
still apply. HTTP health probes use separate no-reuse HTTP/1.1 connections.
`http2` over HTTPS prefers h2 but keeps HTTP/1.1 ALPN compatibility. Over plaintext,
`http2` remains explicit prior-knowledge h2c with no retry-based downgrade. `auto`