From c207e8a03f53ab44f80930a705948d49b3958b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Sun, 18 Oct 2026 12:00:59 +0200 Subject: [PATCH] v0.34.0_fina; --- AGENTS.md | 96 +- CHANGELOG.md | 1800 +++++--------------------- README.md | 1122 +--------------- ROADMAP.md | 734 +---------- SHA256SUMS | 99 +- docs/ACCESS.md | 34 + docs/ACME_PROVIDERS.md | 157 +-- docs/ACME_RFC2136.md | 160 --- docs/API.md | 716 +--------- docs/API_ACCESS.md | 244 +--- docs/ARCHITECTURE.md | 233 ---- docs/BACKUP.md | 39 + docs/CACHE.md | 406 +----- docs/CACHE_DIAGNOSTICS.md | 223 ---- docs/CACHE_HOTPATH_0.34.0.md | 78 -- docs/CHANGELOG_TECHNICAL_ARCHIVE.md | 876 ------------- docs/CONFIGURATION.md | 57 + docs/CONTAINER.md | 56 +- docs/CUSTOM_LOCATIONS.md | 139 +- docs/DASHBOARD.md | 32 + docs/DASHBOARD_PRESET.md | 74 -- docs/DOCKER_DISCOVERY.md | 169 +-- docs/ERROR_LOGGING.md | 47 - docs/FILE_LOGS.md | 109 -- docs/HTTP3.md | 188 --- docs/INSTALL.md | 376 +----- docs/INTEGRATIONS.md | 21 + docs/INTEGRATIONS_040.md | 165 --- docs/LIVE_LOGS.md | 55 - docs/LOGS.md | 50 + docs/MANAGEMENT.md | 139 -- docs/MEMORY_STRESS.md | 91 -- docs/MIGRATION.md | 550 +------- docs/MOCK_AUTH.md | 37 - docs/MOCK_SERVER.md | 116 -- docs/MONITORING.md | 46 + docs/NGINX_GAP_0.34.0.json | 216 ---- docs/NOT_FOUND_HOSTS.md | 24 - docs/NPM_MIGRATION.md | 120 -- docs/PERFORMANCE_0.33.8.md | 353 ----- docs/PHP_FPM.md | 443 +------ docs/PHP_FPM_ISOLATION.md | 31 - docs/PROMETHEUS.md | 241 ---- docs/README.md | 97 +- docs/REDIRECTS.md | 66 - docs/REFERENCES.md | 65 - docs/RENEWAL.md | 165 --- docs/RESPONSE_DELIVERY.md | 47 + docs/ROADMAP_HISTORY.md | 714 ---------- docs/SECRET_STORE_BACKUP.md | 59 - docs/SECURITY.md | 241 +--- docs/SECURITY_AUDIT.md | 78 -- docs/SETTINGS.md | 674 ---------- docs/STREAMS.md | 135 +- docs/TEST_REPORT.md | 115 -- docs/TRANSFORM.md | 55 - docs/TRANSPORT.md | 235 +--- docs/TROUBLESHOOTING.md | 61 + docs/VHOSTS.md | 514 +------- docs/VHOST_ACCESS.md | 136 -- docs/WEB_SERVER.md | 491 +------ docs/screenshots/README.md | 8 - scripts/preflight.py | 8 +- src/admin/documentation/documents.rs | 291 +---- src/admin/tests.rs | 4 +- src/admin/web/index.html | 7 +- src/forwarding/peer_formatting.rs | 31 +- src/proxy/tests/authority_tests.rs | 25 +- src/proxy/upstream_helpers.rs | 50 +- 69 files changed, 1496 insertions(+), 13838 deletions(-) create mode 100644 docs/ACCESS.md delete mode 100644 docs/ACME_RFC2136.md delete mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/BACKUP.md delete mode 100644 docs/CACHE_DIAGNOSTICS.md delete mode 100644 docs/CACHE_HOTPATH_0.34.0.md delete mode 100644 docs/CHANGELOG_TECHNICAL_ARCHIVE.md create mode 100644 docs/CONFIGURATION.md create mode 100644 docs/DASHBOARD.md delete mode 100644 docs/DASHBOARD_PRESET.md delete mode 100644 docs/ERROR_LOGGING.md delete mode 100644 docs/FILE_LOGS.md delete mode 100644 docs/HTTP3.md create mode 100644 docs/INTEGRATIONS.md delete mode 100644 docs/INTEGRATIONS_040.md delete mode 100644 docs/LIVE_LOGS.md create mode 100644 docs/LOGS.md delete mode 100644 docs/MANAGEMENT.md delete mode 100644 docs/MEMORY_STRESS.md delete mode 100644 docs/MOCK_AUTH.md delete mode 100644 docs/MOCK_SERVER.md create mode 100644 docs/MONITORING.md delete mode 100644 docs/NGINX_GAP_0.34.0.json delete mode 100644 docs/NOT_FOUND_HOSTS.md delete mode 100644 docs/NPM_MIGRATION.md delete mode 100644 docs/PERFORMANCE_0.33.8.md delete mode 100644 docs/PHP_FPM_ISOLATION.md delete mode 100644 docs/PROMETHEUS.md delete mode 100644 docs/REDIRECTS.md delete mode 100644 docs/REFERENCES.md delete mode 100644 docs/RENEWAL.md create mode 100644 docs/RESPONSE_DELIVERY.md delete mode 100644 docs/ROADMAP_HISTORY.md delete mode 100644 docs/SECRET_STORE_BACKUP.md delete mode 100644 docs/SECURITY_AUDIT.md delete mode 100644 docs/SETTINGS.md delete mode 100644 docs/TEST_REPORT.md delete mode 100644 docs/TRANSFORM.md create mode 100644 docs/TROUBLESHOOTING.md delete mode 100644 docs/VHOST_ACCESS.md delete mode 100644 docs/screenshots/README.md diff --git a/AGENTS.md b/AGENTS.md index b86e307..44acd45 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,83 +1,29 @@ -# RustProxy implementation rules +# RustProxy source and documentation maintenance -Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes. -The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for -compatibility. Current source version: 0.34.0. +Current source version: **0.34.0**. The Cargo package and binary are named `rust-proxy`. -## Delivery +Before editing, read [README.md](README.md), [ROADMAP.md](ROADMAP.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior. -Maintain English documentation and Markdown files. Keep translations in discovered langs/*.json packs, with en_US as fallback. Never hardcode a two-language switch. Preserve the existing logo. -CHANGELOG.md uses version headings with no release dates. Patch increments may be -bundled; substantial optional integrations raise the minor version. 0.7.0 adds managed renewal, server deployment helpers, Basic Auth and visual locations. The reported compression threshold type mismatch is removed without narrowing. Cloudflare Tunnel and Docker discovery are deferred. +## Keep documentation in sync -Deliver a complete archive with dev.sh and the existing tests. Keep verification results outside the repository; do not add iteration reports, standalone benchmark scripts, or micro-optimization notes to CHANGELOG.md. -Do not include databases, credentials, real certificates/keys, target directories, -node_modules or fabricated Cargo.lock content. Preserve an actually resolved -lockfile when a compiler-enabled environment can produce it. +- Write product documentation in English. Prefer focused instructions: purpose, configuration, defaults, restrictions, verification and common failures. Keep change history technical but user/operator-facing, not a series of profiling notes. +- Update the guide, [docs index](docs/README.md), [changelog](CHANGELOG.md), embedded Reader registry (`src/admin/documentation/documents.rs`) and API schema/tests together when functionality changes. +- Reader Markdown is compiled into the binary using `include_str!`. Renaming/deleting a guide without updating the registry breaks compilation. Avoid broken relative links. +- Keep per-component optional defaults accurate (for example COLD off, Prometheus off, backend `tls_verify` off unless explicitly set, and optional Web Server `sendfile` off). Mark source-only or untested changes accordingly. +- Maintain existing `langs/*.json` packs; `build.rs` discovers available locales. Do not add credentials, databases, real certificates, environment secrets or build artifacts to source archives. -## Engineering +## Engineering safeguards -Keep proxy traffic independent from control-plane database queries and providers. -Use async I/O, streaming bodies, bounded DNS work, bounded queues and explicit -cancellation. Hold request and connection permits through full body/tunnel lifetimes. -Run ACLs before opening OR reusing an upstream connection. Resolve only configured -backend origins; validate every DNS answer and use the validated addresses directly. -Backend TLS certificate verification defaults OFF when `tls_verify` is omitted for vhost -backends. Administrators may explicitly enable chain/identity verification per HTTPS backend. -This compatibility default is limited to application backends: never widen it to Tinyauth or -other verifier clients. Disabling verification must not weaken backend address/DNS/loop/control- -plane protections. +- Keep proxy traffic independent of control-plane database/provider calls. Prefer asynchronous streaming, bounded queues, resource limits and explicit cancellation. Hold backend permits for the entire body or tunnel lifetime. +- Run access rules before connecting to or reusing an upstream. Resolve only configured origin hosts; validate resolved addresses before use. Preserve controls against loopback/private/control-plane destinations unless explicitly authorized. +- Backend TLS verification's compatibility default must not weaken certificate verification for external authentication/integration clients. Preserve validation of Host, listener, authority, forwarded identity, IP/ACL and control-plane access. +- Vhosts bind to named frontends via listener IDs; never derive listener identity from spoofable forwarding headers. Listener and transport changes can require a restart. Health status is observation, not desired configuration. +- Keep HOT/COLD cache privacy, freshness, validators, scoped invalidation and persistent format compatibility. Do not expose cached private content across hosts, credentials, cookies, or `Vary` dimensions. +- Preserve administrator session, CSRF, Host/Origin, certificate-scoped credentials and the authenticated offline documentation/OpenAPI assets. Do not expose private API routes without updating access tests. +- Preserve safe deployment/upgrade behavior: stop or reject incompatible legacy layouts, validate changes before activation, respect data/secrets outside the source tree, and keep full encrypted recovery usable. -Preserve the explicit auth module path and upgrade regression coverage. Replace -managed source directories instead of leaving obsolete modules during upgrades. -Keep administrator intent, provider observations and live connection state separate. -Hot changes must publish validated, immutable snapshots after a successful revisioned -SQLite write. Invalid updates leave the old snapshot active. +## Verification and delivery -## Verification - -Keep production Rust source files within 500 lines. Rust unit and integration -**test files are exempt** from this limit: do not create numbered `part_XX.rs` -fragments to satisfy source-size checks. Consolidate coherent test suites in -`tests.rs` and preserve the existing test cases. -Run `./dev.sh check` and `./dev.sh test` when Rust is available. Run -existing SQL and upgrade checks when available; do not restore removed auxiliary scripts. UI tests use a simulated API; the Python protocol-peer -self-test is not a test of RustProxy. Record these distinctions accurately. -Do not mark a stage accepted or production-ready on the basis of static inspection. -Review real protocol behavior, restored data and resource bounds before expanding -scope. Do not expand provider work ahead of the core real-runtime acceptance gate. Keep that gate explicitly open until Rust runs. - -## Vhost invariants - -Use the accepted socket's actual local address and TLS state for frontend selection; -never infer a private binding or sticky identity from forwarding headers. Vhosts receive -frontend sockets only through `frontend.listener_ids` referencing `listeners.endpoints`. -Keep hostname ownership unambiguous. Socket/ALPN changes require restart, with actual -and desired plans visible. - -Keep health observations separate from desired configuration. Preserve observations -only for compatible members; re-enabled/changed origins require new health checks. -Keep backend leases through request/response/tunnel completion. Do not replay failed -writes to another backend. Cookies are routing affinity, never authentication. Health -probes must use the same origin/DNS policy, bounded concurrency and cancellation. -Retain HTTP/1.1 in TLS Auto negotiation; require H2 in explicit HTTPS H2 mode, -and do not implement fallback by replaying traffic. - -## Vhost configuration invariants - -The pre-production route/inline-listener compatibility importer has been removed. -Persist only the current vhost document and named listener references; old documents -must fail validation instead of being silently converted. Keep path-pinned origins -distinct from balanced pools. Do not silently widen listener bindings. -ACME secrets belong in private credentials files, not SQLite, stdout, argv or API. -HTTP-01 only serves bounded tokens for domains in the active job. No CA call on boot. -The exporter checks actual peer CIDRs and is disabled by default; secret configuration -stores a variable name, never a token. Keep metric labels bounded by configuration. - -## Administration access regression gate (0.8.1) - -No new public admin routes without updating the exact method/path allowlist and -access tests. Operational assets and the OpenAPI schema are private. Keep the -public login document/bundle separate from the dashboard. Documentation uses -the existing cookie session/CSRF, not a second auth mechanism. Preserve the pinned -Swagger build configuration and keep external validators/CDNs disabled. +- Run `python3 scripts/preflight.py` and documentation/link checks after restructuring documentation. When Rust is available, run `./dev.sh check` and `./dev.sh test`; run platform/protocol and restore tests before claiming production readiness. +- Python mock origins or static source inspection do not constitute testing of the real RustProxy binary. Record verification limits; do not claim measured performance gains from source changes. +- Ship complete reproducible source archives with `make_zip.py`, `SHA256SUMS`, the application scripts, current tests and docs. Never ship live state, private keys or generated build directories. Keep the technical deep-history backup outside the authenticated documentation Reader. diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a87a8..37f18a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1532 +1,438 @@ # Changelog -> Operator-facing release history. Historical engineering notes are kept in -> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.34.0 - -- DEV 1 KiB OPT5 multi-site follow-up: use direct CSPRNG 256-bit request ID - bytes mixed with the existing per-process sequence rather than SHA-256 per - request, retaining 64 lowercase hex digits and protected `X-Request-ID`. - Construct the absolute original URL only when the frontend enables Tinyauth, - without changing trusted/untrusted forwarding or IPv6 validation. Borrow the - healthy LB response detail instead of allocating the string `ok`. -- Keep traffic analytics non-blocking and bounded at 2,048 queued samples; - process up to 64 samples per dashboard mutex acquisition in arrival order, - allocate vhost keys only on first observation, classify User-Agents without - a lowercase string, reuse static common status-family labels and render - one-vhost views without cloning every top table. Skip response compression - policy resolution when no codec is accepted by the global ceiling (e.g. - `identity`), retaining all enabled codec, scope and exclusion behavior. -- Source-only until OPT5 compilation and Rust tests. The compiled OPT4 input - passed 151 live HTTP checks (0 failed, 1 optional Zstandard skipped). - Brief shared-host OPT4/OPT3/nginx tests are mixed at 16/64 connections; - No version increment. - -- DEV 1 KiB follow-up (OPT4): keep ordinary unmodified public request targets - borrowed through URI rewriting, avoid creating a metering body adapter for - request bodies with no frames/trailers, and consume already-validated upstream - URI ownership rather than cloning on the ordinary HTTP/1.1 path. Reuse the - formatted IPv4 peer address in RFC 7239 forwarding metadata, fast-path - short ASCII access-log fields, and skip nominated-hop parsing for absent, - plain `close` or single `keep-alive` Connection headers. Preserve cache - refresh owned targets, streamed upload metering, full hop-by-hop filtering - for complex values, TLS/H2/H3 authority handling and request limits. -- OPT3 compiled binary passed 151 live HTTP checks, 0 failed, 1 skipped - (optional Zstandard). OPT4 was subsequently compiled and tested similarly; - Results should be checked against matched configurations. - - -- DEV 1 KiB throughput follow-up (OPT3): keep normalized safe routing - paths borrowed from an independently retained URI instead of allocating - during observation and forwarding; use the original strict percent/decode - validator for noncanonical paths. Build untrusted-peer Forwarded headers - without intermediate per-hop vectors, avoid rewriting an unchanged client - address into per-request log state, fast-path plain `Connection: keep-alive`, - encode request-ID hex in a single buffer and update the cache-disabled - reason counter directly. ACL, trusted-proxy validation, IPv6 formatting, - body streaming, analytics, metrics and defaults remain unchanged. -- Add regressions for path validator equivalence, multi-valued hop-by-hop - fields and untrusted IPv6 forwarding. User-supplied OPT2-FIX1 binary passed - 89 targeted live checks. A compiled OPT3 executable was subsequently tested - (151 passing HTTP cases; one skipped), but short shared-host A/B did not - establish a stable RPS gain. - -- DEV 1 KiB accounting follow-up: move per-domain HIT/MISS/BYPASS, reason, - stored-entry and saved-body statistics outside the HOT cache's shared mutex. - Publish bounded domain/reason indexes and update counters atomically while - preserving the 4,096-domain cap, scoped/global reset behavior and dashboard - labels. Avoid creating analytics samples when the existing bounded queue is - full, preserving its drop counter and downstream processing. Change the - per-vhost latency histogram from up to 11 cumulative atomic increments per - exchange to 1 exclusive bucket increment; reconstruct unchanged cumulative - Prometheus and Docker-vhost histograms when scraped. -- Extend Rust regressions for parallel BYPASS/HIT counting, reset semantics and - saturated analytics. The supplied **previous** DEV executable passes 151 - actual HTTP checks; these newest source changes are uncompiled and their RPS - uplift unmeasured. - - -- DEV 1 KiB reverse-proxy throughput follow-up: identify HTTP/1.1 upstream - idle-pool capacity as a bottleneck using 4-vs-64 live tuning trials on the - *supplied, unmodified executable*. Change the new-vhost default from 4 to 64 - (still bounded to 1..64), preserving explicit saved values and all recycle - settings. Keep resource/FD trade-offs documented. -- Avoid uncontended `Mutex::lock` operations in final `Exchange::drop` via - exclusive `get_mut`, move final owned log strings, and skip reserved-cookie - sanitization when the corresponding header is absent. Preserve analytics, - history, counters, all security checks and cookie handling when present. -- Add source guards, Rust regressions and a measured-configuration report in - this source update. This **source** is not compiled; - newly introduced code performance and full protocol parity remain unverified. - No version increment and no claim of reaching 100k RPS / 80% nginx. - - -- Follow-up HOT cache path: clone response headers outside the shared store - mutex using immutable `Arc` metadata; preserve exact hit/LRU - accounting and per-response Age/transform flags. Fast-path direct no-Vary - keys and avoid per-HIT domain string allocations, inactive BAN locks and - repeated full retention sweeps (stale objects remain for revalidation). -- Add dedicated concurrent HOT hit, transformed header isolation, exact LRU, - BAN, stale-age tests and a manually ignored microbenchmark. No policy, - cache-file format or panel changes. New code remains uncompiled/unmeasured; - see docs/CACHE_HOTPATH_0.34.0.md. - -- Follow-up reverse-proxy RPS revision: reuse typed upstream HTTP origin URIs, - use thread-local cryptographic nonce generation for per-request IDs, - reduce smooth-WRR member-ID clones, and share compiled health policies with probes. -- Cache backend lease once for streaming uploads instead of locking it on each - DATA frame; move access-log events directly into history when there are no - external consumers; remove repeated route attribution in forwarding. -- Retain exact per-request pool-recycle count and age limits plus independent - supervisor rotation, and keep H1/H2 protocol telemetry. No altered upstream keepalive defaults or cache query policies. -- Add source-only invariants, Rust regression cases and reproducible live - before/after/nginx benchmarking instructions. This revision has not yet been - compiled or measured for RPS. - - -- Cache regression fix: interpret local max TTL as a cap on remaining freshness at receipt, not on the upstream Age clock. Preserve RFC 9111 Age and transformed response validators; prohibit private/no-store caching. -- HOT hit optimization: update LRU and hit counts in place, without removing/re-indexing the complete cache entry and its Vary variants on every hit. -- Expose both total HTTP Age and time resident in RustProxy HOT/COLD in the cache dashboard, with Polish/English labels. -- Add 100,000-case deterministic freshness simulation, executable loopback HTTP regression (including Brotli), and Rust HOT/COLD age/transform/persistence tests. Production compilation and post-change RPS measurement are still required. - - -- Optimize reverse-proxy hot path with shared immutable compiled route handles, - reusing the observer's host/location selection in forwarding without changing - ACL, rate, canonical authority or protocol enforcement. -- Avoid wildcard allocations for exact-host hits; preserve host precedence, - listener bindings, regex and internal named-location fallback behavior. -- Add allocation-free, scheduler-lock-free single-backend LB selection for - non-cookie pools. Keep health/drain/capacity admission, retry and lease - lifetime; preserve multi-backend algorithms and cookie-affinity semantics. -- Make healthy-backend selection lock-free using a synchronized atomic status - tag; share compiled health/passive policies between leases rather than - deep-cloning their strings per request. Preserve recovery state machine. -- Prepare error-fallback and retry request headers only when the operation is - configured/eligible. Keep error-response interception and safe-method replay. -- Replace exchange source, cache and transform telemetry mutexes with atomic - tags without changing log/event field names or tri-state flags. Do not clone - live-stream log entries when there are no subscribers and move terminal - exchange fields into the final log record; history and sinks stay. -- Add routing/health/LB/telemetry regression cases and source-only guardrails. - No new configuration fields, migrations, LB switches, or changed keepalive - defaults. Actual Rust compilation and benchmark are required before rollout. - -- Separate global cache representation compatibility from per-vhost scopes. - Keep HOT and COLD entries across vhost keepalive/recycle/LB diagnostics saves; - invalidate only modified content-bearing scopes and cancel their in-flight fills. -- Persist a content fingerprint alongside COLD entries so interrupted scoped - purges cannot resurrect old responses after restart; retain COLD format v3. -- Remove per-frame `Exchange.backend` mutex acquisition from response body - metering by retaining the final backend lease with the body; request metering, - lease lifetime, counters and error handling retain their semantics. -- Guard cache-ticket creation against in-flight requests using an obsolete - configuration snapshot after a successful save, without evicting unchanged - cache entries. -- Add regression tests for operational saves, scoped content changes and COLD - restart compatibility. No LB UI changes or new database migration. -- Add `sendfile: off | auto` to Web Service configuration and Delivery editor. - For eligible Linux cleartext HTTP/1.1 GET >=64 KiB, transfer already-authorized - static file descriptors using native `sendfile(2)` behind a bounded connection - bridge. Keep normal streaming for TLS/H2/H3, range, transforms, compressed - bodies, active object cache and other operating systems. Disabled by default. -- Keep the kernel transport switch outside global cache identity; toggling it - must not invalidate HOT or COLD. Include native transport/fallback regression - test cases and panel save/reload tests (Rust tests still need execution). -- Include an explicitly hypothetical Amdahl model of nginx parity using the - existing 0.33.8 benchmark, with no claim of measured 0.34.0 RPS improvement. -- Actual Rust compilation, protocol/regression execution and comparative RPS - benchmarking are required before deployment; do not infer throughput gains - from the source-level changes alone. - -## 0.33.8 - -- Skip cache-only policy maps, request snapshots and ticket work when caching - is ineligible. Preserve independently enabled transformations, header policy, - security decisions, telemetry and runtime configuration reloads. -- Lazily allocate idle timers while preserving the original construction - deadline, progress resets, trailers, cancellation and permit lifetime. -- Retain only request headers consumed by compression, and use direct hex - formatting for request IDs while preserving per-request entropy and hashing. -- Shorten live-history mutex holds by cloning entries and releasing evicted - entries outside the lock; preserve retention, drop counters, live streaming - and external sink behavior. -- Reduce filesystem job context copies and root-descriptor duplication. Use - adaptive file reads up to 64 KiB, preserving pinned-root and symlink checks. -- Report validated local file end-of-stream on the final frame. Fix successful - HTTP/1 file responses reported as cancelled and full/empty file cache fills - that were never committed. Reject short or oversized local streams; keep - upstream end-of-stream and trailer handling unchanged. -- Correct inherited Host-port validation, long-form Expires parsing, retained - cache inventory, diagnostic-header spoof protection, escaped-JSON log - redaction and certificate-token authorization ordering. -- Repair obsolete test fixtures/assertions without relaxing existing behavior; - add actual process/transport regression, benchmark, profiling and soak runners - with exact configurations, checksums, raw samples and resource observations. -- Keep the existing LB panel and persistence schema. Performance outcomes are - documented separately in `docs/PERFORMANCE_0.33.8.md` and the evidence archive. - -## 0.33.7 - -- Apply the same theme-aware subdued `--muted` color used on small metric - tiles to every Overview heading and the chart/metric headings in Cache, - Transform, Traffic and Vhosts. Keep numerical readings, legends, font sizes - and established sentence/uppercase title rules unchanged. -- Make the shared Vhosts Backend/H2 popover (including rows and its close - control) follow the selected interface corner style: default/square, - subtle, rounded and extra-rounded, updating without dismissing the popup. - -- Unify all 78 Overview card headings at 12px in sentence case, with the - same proportional font across metrics, Process, RAM cache, backend tiles - and wide charts. Use one monospaced font for compact descriptions and - numeric readings; preserve HTTP, H1/H2 and RAM acronym capitalization. - Keep the 9px chart legends and other panels' typography unchanged. -- Add 10px separation between the Cache "Domains and effectiveness" and - "Stored objects" cards, leaving other Cache section spacing unchanged. -- Replace the native Vhosts H1/H2 upstream badge tooltip with a click-to-open - panel sharing the backend quick-view's anchored popover, dismissal and - positioning. Keep the panel and its content nodes stable across status - refreshes; show current observations and H1/H2 response counters without - a blinking browser tooltip. Ensure clickable Backend count and Health tiles - keep the same square geometry and typography as adjacent Vhost detail and - live-metric tiles, regardless of the selected interface corner style. -- Align every chart legend and its value labels with the 9px monospace styling - used by **Requests per second**, including compact/ultra dashboard densities. -- Add a clickable Backend count / Health tile to Vhosts: a compact, - keyboard-dismissible backend list shows name, configured IP/hostname and - port, upstream scheme/HTTP mode, health and the last real HTTP response - code. The live status feed refreshes the open list without extra API calls. - -- Replace the legacy Arrange Overview modal with authenticated SQLite-backed, - administrator-scoped named layout presets (16 maximum). Support save, - overwrite, load, rename, delete and current-view factory reset. Include - Overview history sample limits, density, card widths, order and visibility. -- Add SQLite migration `0010_dashboard_presets.sql` (schema 10), protected - preset endpoints and English/Polish UI translations. Existing preferences - and proxy configuration documents remain unchanged. -- Extract only Overview/WWW metric and chart elements into compile-time HTML - partials. Preserve card IDs, DOM order, dashboard controls and API polling. -- Harmonize chart headings at 12px, using the existing proportional heading - font (sentence case on Overview, uppercase in other panels). Use the existing - monospaced chart font for **all** legends and data labels, not only their - numeric values. Covers Overview, Compression activity, Web Server, Cache, - Transform, Traffic, Vhosts and Streams, including dynamically rendered charts - and compact/ultra dashboard densities. - -## 0.33.6 - -- Normalize the height of all 14 large Overview/WWW plots to 125px, avoiding - width-dependent vertical stretching; fix quarter-grid alignment. -- Keep CPU at 0-100% and correct compact percentage sparklines to the same - absolute bounds. Never hide actual CPU readings in labels. -- Give transformation and compression activity, plus other unbounded rate, - count, latency and memory series, minimum zero-based ranges instead of - scaling near-zero readings to the plot ceiling. Extend only as necessary - using rounded ticks, and hold the ceiling until chart reset. -- Fix the full Cache/Transform rate plots at 180px across screen sizes. - Preserve actual /s values, updates and history resets. -- Add Chromium and helper-scale regressions for chart geometry and ranges. - No changes to status APIs, polling, database schema or account settings. - -## 0.33.5 - -- Fix the Overview **System and process CPU** axis at 0–100% so low - readings no longer expand to fill the chart. Clip plotted values above - 100% without changing the actual percentages shown in the legend. -- Fix the CPU plot height at 125px across viewport widths and densities, - independently of CPU readings. Other charts, their dimensions, data - sources, dashboard layout and settings are unchanged. -- Check fixed percent mapping, clipping, legend visibility and plot geometry - with simulated Chromium at desktop and mobile viewport sizes. -- Overview heading cleanup: move the existing request-rate maximum (`max XX.X - req/s`) from the chart footer to the upper-right Requests per second header; - remove its LIVE TRAFFIC eyebrow and LIVE badge. Remove the LIVE badge from - Connection Activity. No change to collected values, scaling or APIs. -- Add simulated Chromium checks for chart headings, live peak placement, - dashboard densities and resized widget widths. - -## 0.33.4 - -- Unify per-Web-Service live metrics with Vhost cards: six matching live metric - tiles in a responsive, always-visible full-width grid instead of a collapsible - section, plus a full-width Vhost-style rate-history chart. -- Refresh visible WWW readings on service-list redraw and continue live status - updates without a disclosure toggle. Keep existing rate sampling, history - limits, PHP capacity semantics, language packs, authentication and API shapes. -- Add simulated browser regression coverage for layout parity at desktop and - phone widths, immediate metrics, refreshed counters and rate-chart width. - -## 0.33.3 - -- Avoid one-point holes in full-width Overview counter-rate histories and - WWW rate charts after tab suspension, BFCache resume or overdue refresh. - Preserve already plotted history, skip unavailable rate samples and display - "-" until the next valid interval; genuine measured 0/s remains visible. - The Web Server service-rate sparkline no longer draws a false zero dip. - Add deterministic JavaScript and simulated Chromium regression coverage. - -- Make main sidebar navigation highlight depend on the Appearance **interface - theme** rather than Chart style. Selected/hover backgrounds reuse theme nav - tokens; active text and the selection marker use readable theme colors. - Preserve corner rounding, mobile navigation and RTL selection indicators. - Browser-test palette independence across four themes, eight chart palettes - and five corner settings. - -- Repair **Arrange Overview cards** modal after introducing variable card widths: - use a readable three/two/one-column editor preview rather than squeezing six - 2/12-width cards and overflowing controls into a 12-column mini-dashboard. - Width buttons still edit true Overview spans, while grouped toolbar actions, - hidden-card controls and modal footer wrap without horizontal scrolling. - Add a Chromium regression for Polish labels, card controls and narrow screens. -- Fix Overview chart content not filling resized cards: preserveAspectRatio=none - on time-series SVGs (including dynamically created network interface charts), - with non-scaling strokes so data fills the width without distorting donuts. - No sample frequency, persistence or API changes. -- Add 2/3/4/6/12-column width controls to both Overview layout editors. - Small number/sparkline tiles can grow to double width and full-size charts - can shrink to 3/12, allowing four charts per row on wide screens. -- Remove old cross-type drag restrictions after entering free layout: cards - can move between the top numeric area, compact charts and diagnostic panels. - Preserve legacy grouped layouts for existing saved preferences until edited. -- Save card widths in account preferences with strict widget/type validation, - preserve the existing factory preset and hide/show state, and restore widths - on cancel or when resetting to defaults. No DB schema migration. -- Use responsive dense CSS Grid layout and add real-browser resize, ordering, - modal editing, save/cancel and compatibility regression coverage. - -## 0.33.2 - -- Synchronize Cache occupancy progressbar colors with the selected Appearance - chart palette, matching the existing Overview fill color. Native WebKit and - Firefox progressbar tracks and fills honor the theme automatically. -- Synchronize track and fill corner rounding across Cache, Overview and WWW - with the Appearance corner setting (square, subtle, rounded and extra). - Also align Web Server ranking bars and traffic ranking bars. -- No changes to telemetry, polling, persisted preferences or cache behavior. -- Add a Chromium browser regression for corner styles, all chart palettes, - theme switching and visible native Cache progress fill colors. -- Align the System Load RAM utilization bar with all other Overview meters: - replace the browser-rendered native `` element with the shared - chart-palette/corner-radius track and fill. Keep 5px height and status-based - percentage updates, including appropriate absent-data handling and ARIA - values; add a bundled-JavaScript browser regression for these states. - -## 0.33.1 - -- Fix intermittent inline Overview hide/show reversions: backend and Stream - telemetry renders now honor the unlocked layout draft until Save/Cancel. - Unrelated preference refreshes no longer overwrite the draft. -- Add a single bundled `dashboard-defaults.json` defining the factory ordering, - visibility and compact density provided by the operator, with 600 initial - Overview history samples. Apply only to new accounts and explicit resets; - preserve saved user layouts and browser-stored sample preferences. -- Add live UI regressions for draft add/hide, backend/Stream status updates, - cancel/save and default preservation. -- Preserve counter baselines and chart history while Overview widgets are hidden, avoiding discontinuities when shown again. -- Clarify the distinction between lifetime Cache HIT ratio and recent interval HIT ratio. -- Document safe dashboard preference export in the browser Console without `copy()`. - -## 0.33.0 - -- Move the Overview history sample selector to the connection-status header row - (right-aligned, wrapping on narrow screens); hide it on other pages without - changing temporary/persistent history settings or polling. Narrow the - Web Server-only `PhpMetrics::record_web` visibility to match `ResponseKind` - and remove Rust's `private_interfaces` warning. -- Fix fatal authenticated JS bundle startup error (`rustProxyOverviewSamples.onChange` - accessed before sample selector initialization). Ensure delayed DOM setup and - preserve temporary/persistent 60/120/300/600 history choices. -- Keep donuts compact with totals next to rings and legends. -- Add twelve **hidden-by-default** Web Server Overview metric blocks and - expandable per-Web-Service live metrics, using the existing status SSE path, - shared cadence and user-controlled widget ordering/visibility. -- Instrument Web Server request kinds, exact status subset, HTTP methods, - pre-compression response body bytes, active header work, configured PHP - concurrency, classified FastCGI failures, and per-service compression. -- Expand fixed-cardinality Prometheus families and Grafana operations dashboard - with Web Server, PHP-FPM, per-service filtering and cross-metric correlation. -- Preserve API/configuration schema, previous user preferences and existing - cache/transform sample controls; add targeted test regressions and document - the outstanding Rust/live-runtime acceptance gate. - -## 0.32.8 - -### Dashboard metrics visual follow-up - -- Render bounded HOT cache RAM, COLD queue RAM and COLD disk budgets as progress bars with used/limit detail; use a meter for file descriptors, HTTP protocol shares and backend capacity as well. Missing or disabled limits never generate a fabricated percentage. -- Show cache HIT/MISS distribution as compact, labeled donut charts, preserving the percentage and raw HIT/MISS counts. -- Introduce full-size, four-series transformation and compression rate charts with per-series legends (transform applied/cache HIT/skipped/errors and gzip/Brotli/Zstd/deflate). Keep existing refresh cadence, authenticated status APIs, appearance presets and user card preferences. -- Ship JS simulated-status regression checks and responsive Chromium layout checks; Rust build and live-server acceptance remain pending. - -- Keep the Overview title on the left with actions aligned to the right, wrapping naturally on narrow screens instead of forcing three equally sized button columns. -- Use existing quiet, outline and primary button styles in the Overview actions and visual editor; remove redundant Overview-specific button classes and obsolete mobile button overrides without changing the shared presets. -- Remove the complete `tools/checks/` directory from the distributed source tree. Keep the source/upgrade preflight in `scripts/preflight.py` and run Rust tests directly from `dev.sh`. -- No changes to saved layout preferences, Overview JavaScript event handlers, proxy routing or monitoring behavior. - -## 0.32.7 development follow-up (UI density and H2 diagnostics) - -- Remove per-location button size overrides for Overview refresh/arrangement/unlock and Appearance visual editor/show-all; defer to shared Normal/Compact button sizing. -- Make normal/compact/ultra Dashboard density distinguish metric values, charts and non-table panels on desktop and mobile without changing saved settings or grid gaps. -- Lower only confirmed graceful H2-close terminal diagnostic duplication to TRACE; preserve 502/access-log details and full error handling for uncorrelated `Canceled` failures. -- Add static and optional Chromium regression checks; Rust build and live H2 reproduction remain pending. - -## 0.32.7 - -- Handle peer-initiated graceful upstream HTTP/2 GOAWAY(NO_ERROR) by retiring a - stale connection-pool generation and retrying one empty, safe request on the - same backend. Do not replay requests with bodies or unsafe methods. -- Keep expected H2 GOAWAY and co-timed cancellations out of passive I/O error - quarantine. Count unrelated I/O errors, but de-duplicate failures inside a - brief connection-closure burst. -- Make passive ejections recover through healthy active probes or, when active - health checks are disabled, a bounded half-open cooldown. -- Emit one WARN per transition out of traffic with the first failure detail in - the counted streak and the error triggering the threshold, subject to the - existing Error log sink configuration. -- Add unit and static regression coverage. Rust compilation and live H2 - acceptance testing remain pending. - -## 0.32.6 - -- **Development-branch Appearance layout:** replace the long - half-width Overview cards settings list with a full-width, compact horizontal - ribbon. Keep every visibility switch and reorder control operational, add - left/right browsing, and provide an optional expanded grid. Preserve stored - layout preferences, theme shapes, and the visual layout editor. - -- **Development-branch dashboard polish:** keep the Cache - hits/misses/bypasses per-second chart visually continuous across browser-tab - minimize/restore by reusing the last valid rate sample until a fresh interval - is available again, rather than leaving a visible gap. Preserve the 3 s - Overview cadence and do not change server polling. -- **Development-branch mobile/layout polish:** make the - Overview page-intro action buttons equal-height and symmetric on mobile, - tighten the Appearance → Dashboard layout → Overview cards editor into a more - horizontal wrapped control strip, increase the gap between the `p` and the right border of the - RustProxy logo mark, and normalize the Overview canvas gutter so small-small, - small-large and large-large dashboard block transitions use the same spacing. - - -- **Development-branch diagnostics charts:** publish cached - CPU, process CPU/RSS and per-interface RX/TX samples in compact live status; - charts follow the Overview update cadence (default 3 s, accepting saved 2 s) - while all Process details still follow the selected System-info refresh. The - OS collector cadence remains unchanged, with no extra probe per SSE event. - Remove nested mobile scrolling in the interface chart list as a mitigation - for reported WebKit jumps near Process/Network; physical iOS 27 validation is - still required. Match the Origin protocol badge's radius to neighboring - policy pills for both default and configured corner styles, preserving colors. -- **Development-branch rate continuity:** preserve cache - HIT/MISS and other counter-derived chart histories when a browser tab is - suspended, minimized or restored from BFCache. Treat resumed/overdue - observations as new counter baselines, not artificial 0/s samples. Show - a missing-rate marker until the next fresh interval; retain real 0/s - values. Apply one shared rule to Cache, Transform, Overview, backend, - vhost and Stream charts without adding polling or changing System-info - scheduling. Add isolated JavaScript pause/resume and reset tests. -- **Development-branch follow-up:** update existing network-interface - diagnostic chart nodes in place during live refresh, preserving DOM identity - and avoiding scroll-anchor loss around the Process and Network panels on touch - browsers. Keep network-interface history advancing on every live Overview update - using the most recent system measurement; collect fresh system data only at the - separately configured System information interval. Real iOS 27 validation - remains pending; this does not prove all scroll jumps have been eliminated. -- Fix CSS transformation of descendant pseudo-class selectors (including - rounded-corner rules), and mark administration responses `no-transform`. - -- Restore two-column small metric cards on narrow mobile Overview grids, ensure - the first render uses the same responsive layout and avoid DOM operations - that can disrupt scrolling when preferences refresh. -- Extend account-selected corner presets to vhost cards, backend badges, - entity cards and additional panel surfaces while preserving the square dock. -- Report observed HTTP/1.1 or HTTP/2 per backend in authenticated status and - show fresh observations next to configured modes in the Vhost policy-badge - group, without changing green/neutral state colors. Stale or unobserved modes - show `?`, never a guessed Auto selection. No active probes. -- Add browser, JavaScript and Rust regression coverage. Rust compilation, - physical iOS Safari and live HTTP/2 ALPN remain acceptance gates. - -## 0.32.5 - -- Add bounded DNS/DDNS hostnames as **allow-only** client ACL rules in reusable - profiles, host policies, vhosts, locations and Docker ACL assignments. -- Refresh A/AAAA answers asynchronously every 60 seconds and after config - publication; cache immutable, expiring address sets for lock-free request - checks. Fail closed on lookup failure and initial unresolved state. -- Add IP/CIDR vs DNS/DDNS selection in the ACL profile editor and localized - operator guidance; preserve the JSON ACL rule format and previous policies. -- Add DNS ACL validation/regression tests. Cargo and real rotation testing - remain required on a Rust-enabled staging host. - -## 0.32.4 - -- Allow `preserve_host` for HTTP/1.1, HTTPS Auto H2/H1 ALPN, legacy compatible - HTTP/2 and strict H2/H2C. Forward the public Host consistently as HTTP/2 - `:authority` while keeping DNS/TCP pinned to the configured origin URL and - TLS SNI/certificate validation independent. Do not replay unsafe requests. -- Drop the UI restriction that unchecked Preserve Host on changing protocols; - make the HTTP/1.1 debug origin preset explicitly optional and only warn for - HTTPS-by-IP verification without a certificate name. Retain previous saved - vhost settings, TLS defaults, health-probe semantics and WebSocket behavior. -- Make the Overview gap between the last small-card row and first large-card - row identical to the other density-dependent horizontal and vertical gutters. -- Extend source, UI and local-peer regression tests. Cargo/Rust runtime - validation and real Angie end-to-end testing remain outstanding. - -## 0.32.3 - -- Fix unequal heights and ragged Overview rows after customization. Replace absolute-positioned masonry/snapped bands with the original CSS Grid geometry for every layout, grouping numeric tiles, normal charts, large charts, extended diagnostics and the backend table into uniform rows. Each row has equal-height blocks and the same density-aware gutters; order can be customized within a size group without mixing short cards with tall panels. Preserve all 62 widgets, existing persisted settings, Hide/Restore, Default/Reset, theme and version **0.32.3**. - -- Snap customized Overview tiles to shared pixel-aligned rows and **balanced horizontal bands**. Short cards can stack alongside tall diagnostics and, when their combined height is close enough, share the same bottom baseline with consistent density-sensitive gutters. Keep the factory six/three symmetrical rows, prevent stretching a single short tile to a tall panel, preserve drag/save/reset settings and all 62 widgets; version stays 0.32.3. - -- Keep the RustProxy **logo shape fixed** across all Corner rounding presets. The unlocked Dashboard’s × Hide button inherits the same rounding as the adjacent arrow controls (including Square mode). -- Preserve the **original symmetric Overview by default** (six metric cards / three large panels, density-aware gutters), while packing **only user-reordered layouts** on wider canvases: shorter graphs keep their natural height and can stack next to tall diagnostics. At narrow widths, retain the original row layout. Measure grid spans in CSS layout pixels so UI zoom 90–130% cannot cause collisions; no preference schema or version changes. -- Restore a clean **Settings → Appearance** Overview selector: no block-type badges among the visibility/reorder controls. The modal visual editor and the unlocked Dashboard retain their type labels. Move/Hide/Show controls stay right-aligned. -- Open **Appearance** on a first visit to Settings, reopen the last selected Settings tab on later visits (remembered locally), and always honor explicit `/settings?tab=...`, `/configuration` and `/logging` navigation. -- Add an account-scoped **Corner rounding** selector with Original, Square, Subtle, Rounded and Extra rounded options. Original retains the established corners in all four themes; other presets apply to cards, fields, dialogs, buttons and panels consistently, including login. Preserve older preference documents with the default preset and reject unknown values. -- Restore the RustProxy logo's original coral/soft-green mark accents in Ultra Dark without changing the near-black surfaces or global chart/button palette. Preserve version 0.32.3 and existing dashboard layouts. - -- Restore the original strictly aligned **default** Overview row layout in response to uneven masonry spacing: six equal numeric cards and three equal diagnostic panels per desktop row, with consistent original `--ui-gap` gutters. Customized orders can now opt into independently sized stacking without changing the default geometry. All 62 blocks retain drag-to-order, hide/restore, reset and saved preferences; version stays 0.32.3. -- Add six opt-in compact charts (HTTP requests/s, active exchanges, H1/H3 shares, cache MISS/s and in-flight backend requests) and seven opt-in full-size multi-series charts (HTTP protocol rates, system/process CPU, process/cache memory, cache HIT/MISS, connections/WebSockets, backend availability and response/probe latency). All read existing status snapshots with bounded browser histories; unknown counters remain unavailable and resets do not create negative rates. The shared registry/API now offers **62 blocks**, retaining the preceding 49 IDs and their saved settings. -- In the visual block selector and unlocked inline editor, place the block-type badge before the action buttons; keep Move/Hide/Show controls aligned to the right even when names vary in length. Keep the release version **0.32.3**. - -- Add optional **Ultra dark** (AMOLED-style black) and **Warm cream** (subdued ivory) admin themes. Persist both via existing per-user Appearance preferences and preserve the original Dark/Light choices, login preview, and default `dark` value. -- Add **× Hide from Dashboard** to each block while editing the unlocked real Overview. Restore any numeric card, chart, panel or backend table from the collapsible **Hidden blocks** list; Cancel discards the draft, Save persists visibility and order with one preference update. No registry records or backend metrics are deleted. -- Add an **Unlock layout** button on the real Overview canvas. Only the drag handles start movement; keyboard-friendly arrow buttons also reorder number tiles, charts and whole panels. Changes are previewed locally, committed in one existing preferences PUT by **Save and lock**, or discarded by **Cancel** or navigation. The original live widget DOM and callbacks are preserved. -- Classify all 49 blocks in the shared registry as numeric, numeric+chart, chart, diagnostic panel or table. Show localized block-type labels in the visual editor while keeping Appearance compact, with a filter for hidden widgets. Retain default visibility, account preferences, all existing metrics and version 0.32.3. - -- Extend the visual Overview editor to all dashboard content, including existing live charts, three diagnostic panels and the backend table. Restore the original ordering by default; allow any registered block to be moved or hidden without losing its live DOM handlers. -- Add twelve opt-in, bounded-history metric charts based on existing process, cache, frontend, TLS, HTTP/2 and backend-health counters; unknown data stays neutral. All 49 cards/panels share one registry, the previous settings arrays and the same account-scoped endpoint. -- Add **Default view** draft preview and a confirmed **Reset layout** action; both preserve non-dashboard preferences. Keep the 0.32.3 version and upstream protocol configuration unchanged. -- Restore `http2` backward compatibility: HTTPS offers H2/H1 via ALPN as it did in 0.32.2, while cleartext uses H2C prior knowledge. The new `h2` value is strict on HTTPS, with no H1 downgrade. `http1` and `auto` retain their prior semantics; no request replay or extra retry loop. -- Extend the Origin selector to four clearly described choices (H1, Auto, HTTP/2 compatible, H2 only), keeping existing saved JSON untouched. Preserve separate WebSocket HTTP/1.1 Upgrade and H2 Extended CONNECT paths. -- Share a validated Overview Cards registry between Rust and JavaScript. Retain the original 16 widgets and saved order/hidden choices. Unknown identifiers do not invalidate a whole saved profile. No SQLite migration. -- Expand to fourteen opt-in HTTP origin cards: keep health, activity, errors/s, probe RTT, traffic, configured modes, requests/s and capacity, and add 5xx, timeouts, DNS/TCP/TLS/I/O errors, retries, queue pressure and client-pool rotations. No extra probes/polls; all new widgets default hidden. Protocol counts describe configuration rather than actual ALPN results. -- Synchronize the authenticated Dashboard OpenAPI preference enums, maximum array sizes and visibility defaults with all 30 registry entries. -- Add a visual Overview layout dialog with draggable metric previews, keyboard reorder and hide/show list; settings remain account scoped and Save uses one existing preferences PUT. Cancel makes no changes. -- Document that HTTPS ALPN runs once per new TLS connection; Hyper pools and reuses negotiated connections. Keep configured per-backend recycling rather than adding unsafe protocol locks, timers or probing traffic. -- Add real-peer Rust tests for compatible HTTPS H1/H2 negotiation, strict H2, H2C, request-version safety and WebSocket isolation, plus UI/source preference regression checks. Cargo/runtime acceptance and benchmarks remain pending. - -## 0.32.2 - -- Fix Web Server shared-cache revalidation: browser `no-cache`, `max-age=0` and freshness-constrained requests now supply stored origin validators. Honor local origin `304 Not Modified` by refreshing the same cached body and preserving/incrementing `X-Cache-Hits`, matching the existing Reverse Proxy behavior. -- Keep client-facing conditional/HEAD/range semantics distinct from origin validation; do not re-use a stale body if the file changed. Remove old stored copies on changed/uncacheable local responses, and prevent an uncacheable `304` from re-enabling shared storage via vhost response rules. -- Preserve original representation headers when revalidating transformed assets, and guard validation across cache PURGE/config revision changes. Add focused regression tests using the local Web Server's actual static file handler. -- No changes to the database schema, cache format, API, Web Server configuration or Reverse Proxy source. **Rust compilation and real-browser verification are outstanding** until run on a Rust 1.99+ host. - -## 0.32.1 - -- Restore Overview proportions from 0.31.9 by removing 0.32.0's desktop minimum heights. Increase compact metric typography so labels, values and captions remain readable at 90% UI scaling without large empty bottoms. -- Unify Overview inter-card gaps with the same `--ui-gap` density spacing used by the rest of the Dashboard, instead of distinct gaps for dashboard density options. -- Retain the six desktop columns, responsive layouts, permanent chart legends, optional Stream cards and saved Appearance preferences. Add focused layout regression coverage. No changes to proxy transport, persisted data, API or saved settings schema. -- Replace oversized Appearance Stream card help bubbles with localized native `title` tooltips, retain assistive descriptions on the buttons, remove tooltip-specific CSS and prevent widget-grid overflow on narrow screens. No preference or API changes. - -## 0.32.0 - -- Extend Stream TCP/UDP listeners with explicit per-host backend pools (up to 32), legacy single-upstream fallback, stable backend IDs, weight, enabled, drain, session caps and weighted Round Robin / Least Connections or Sessions selection. Keep TCP/UDP affinity for each session and release backend gauges via RAII. -- Add optional TCP Connect and conservative UDP response-matched probes with configurable interval, timeout and rise/fall, globally bounded concurrency and cancellation. Never label an unverified UDP connect as Healthy. -- Add bounded per-backend observations to existing status/SSE and opt-in detailed Prometheus metrics; preserve global `include_streams`, stream-level basic counters and traffic direction meanings. -- Extend existing Appearance dashboard card visibility/order with four opt-in Stream cards. Reuse account preferences, existing status updates, translations, style/responsive layout and in-memory browser charts. Migrate older 12-widget layouts to hidden Stream cards. +Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in 0.34.0. -### 0.32.0 Overview layout and Vhosts hint correction +## 0.34.0 — cache consistency, static delivery and proxy optimization -- Restore permanent inline secondary labels and legends on the main Overview tiles, without hover/focus tooltip behavior or extra keyboard stops. The follow-up restores the **original six desktop columns** (not four), with taller tiles for their descriptions; retain responsive grid and dashboard density options. Stream traffic uses the existing RX/TX chart with an inline colored legend. -- Keep hover/focus help in **Settings -> Appearance** unchanged; dashboard card visibility, preferences and polling are not modified. -- Remove the redundant *Controls whether this vhost accepts traffic* helper text in the Vhost add/edit form. Keep the enabled checkbox, state badge and enabled behavior unchanged. +### Cache +- Improved HOT/COLD cache retention when saving operational Vhost settings such as backend keepalive, connection recycling and load-balancer diagnostics. Unrelated changes should no longer unnecessarily clear compatible cached content. +- Changes affecting served content invalidate the relevant Vhost cache scope and pending fills. Global changes to response representation may still invalidate content more broadly. +- Added protection against restoring obsolete persistent COLD responses after an interrupted scoped purge or restart. Previously stored objects may need to be populated again during compatibility checks. +- Corrected freshness and expiry handling: a local maximum TTL limits **remaining freshness**, not the upstream response's existing `Age`. The dashboard now distinguishes total HTTP `Age` from time spent in HOT/COLD cache. +- Improved cache HIT/LRU accounting, `Vary` handling and response-header isolation. Existing `private`/`no-store` rules, conditional requests, PURGE/BAN controls and authenticated-request protections remain in effect. -### 0.32.0 Appearance / Stream editor hotfix - -- Make the four Stream Overview card toggles authoritative even without active or configured listeners; render a neutral no-data state instead of silently hiding a user-enabled card. Preserve opt-in defaults, cached status rendering, existing polling and all legacy widgets. -- Move Appearance Stream card descriptions into hover/focus tooltips (with `aria-describedby`) without changing ordering, control behavior or keyboard accessibility. -- Remove only the redundant enable-control helper hint from the Stream editor. Label detailed metrics precisely: Stream null means default disabled; backend null uses the parent Stream setting; global Prometheus `include_streams` remains the export gate. -- Bundle `scripts/start_stream_backends.sh`, `scripts/test_stream_tcp.py` and `scripts/test_stream_udp.py` directly under `scripts/` and update test instructions. +### Static content and backend traffic +- Added **`sendfile: off | auto`** in **Web Server → Delivery**, disabled by default. `auto` supports eligible full static GET responses of **64 KiB or more** on Linux over unencrypted HTTP/1.1; TLS, HTTP/2, HTTP/3, ranges, compression, transformations and other excluded requests use standard delivery. +- Increased the default HTTP/1.1 idle backend connection pool size for **new** Vhost/origin configurations from **4 to 64**. Existing explicitly saved values remain unchanged; a larger pool can use more file descriptors and connections. +- Reduced overhead in common reverse-proxy routing, upstream connection reuse, backend selection, request forwarding and streamed responses. Existing ACLs, trusted-proxy checks, request limits, load-balancer choices and retry rules are preserved. +- Optimized request forwarding for common HTTP/1.1 paths and forwarding headers without changing public path validation, IPv6 handling or the HTTP/2 and HTTP/3 security model. -### 0.32.0 follow-up fixes +### Metrics, logging and compatibility +- Improved traffic analytics, per-Vhost cache statistics, latency reporting and log handling under load. Analytics collection remains bounded and non-blocking; dropped samples are still counted when limits are reached. +- Reduced monitoring overhead without removing existing Prometheus, dashboard, cache or protocol metrics. Dashboard labels and reported cumulative latency values remain compatible. +- Preserved protected `X-Request-ID` formatting as **64 lowercase hexadecimal characters**; request IDs now use cryptographically secure 256-bit randomness combined with the process sequence. +- Preserved response-compression settings, cache exclusions, existing configuration formats and database compatibility. No new mandatory migration or load-balancer control was introduced for these optimizations. -- Correct the Appearance/Overview visibility synchronization: immediately apply the newly saved (rather than stale) hidden-widget list, repaint cached Stream totals after an opt-in and on return to Overview, without an extra request, rate sample or timer. A subsequent hotfix removes the no-Stream visibility restriction in favor of an explicit neutral state. -- Show secondary Overview-card details as focus/hover tips on pointer devices while retaining on-screen details on touch devices; keyboard focus is supported. -- Rename the atomic reserve call from deprecated `fetch_update` to `try_update` without changing the capacity logic or memory ordering. -- Include independent mock-backed TCP and UDP black-box test scripts, executable against configured RustProxy listeners, plus harness-only selftests. +**Validation:** Several later 0.34.0 changes were provided as source changes and require final Rust compilation, regression tests and comparable performance measurements. Earlier candidates passed **151 live HTTP checks** with one optional Zstandard check skipped, but these results do not validate the latest source. No measured RPS advantage over 0.33.8 or nginx is claimed. -## 0.31.9 +## 0.33.8 — safer file responses and request handling -- Add per-credential **View settings**, **Edit settings** and **Regenerate token** actions to Settings → Certificate API. Read and edit return non-secret metadata only; Key ID remains stable, and editing comment/certificate allowlist/deploy permission does not rotate the existing bearer token. -- Add administrator-only `GET /api/certificate-api/{key_id}`, `PATCH /api/certificate-api/{key_id}` and `POST /api/certificate-api/{key_id}/regenerate`. Regeneration atomically updates only the stored hash and rotation/usage timestamps, preserving the grant and showing the new random secret only in the regeneration response. The old secret becomes invalid immediately. -- Keep existing login/session/CSRF protections and external Certificate API authorization strictly limited to targeted reload and explicit deploy permissions. Include OpenAPI, English/Polish UI strings and regression tests for secret non-disclosure and token invalidation. -- No database schema or proxy transport change. Cargo/runtime acceptance remains required on a Rust 1.99+ build host. +- Reduced work for requests where caching cannot apply, while retaining independently configured security checks, response transformations and monitoring. +- Improved Web Server file streaming and file-read behavior, preserving filesystem root and symlink protections. +- Fixed successful HTTP/1.1 file responses sometimes being reported as cancelled; corrected cases where complete or empty eligible files were not committed to cache. +- Improved handling of short or unexpectedly oversized local streams without changing upstream streaming and trailer behavior. +- Fixed Host/port validation, extended `Expires` parsing, cache inventory reporting, diagnostic-header spoof protection and redaction of sensitive values in escaped JSON logs. +- Corrected certificate API token authorization checks; added HTTP compatibility and performance test scenarios for future releases. -## 0.31.8 +## 0.33.7 — saved dashboards and Vhost backend details -- Fix Docker VHosts startup status: a missing first observation is WAITING, not a connection failure. Read cached agent status when opening the page (including with Live view off) and briefly retry while the first background check is pending. Display an error only after a failed agent check; omit the configured certificate SHA-256 from generic failure details because it is not a diagnosed failure cause. A configured pin alone does not mark the agent connected. Keep agent polling, certificate validation, manual refresh and persisted settings unchanged. -- Add server-side Stored Logs sorting by visible columns, including numeric and discovered nested fields; preserve sorting over the complete bounded scan before pagination. Column and direction persist with account preferences, with no SQLite schema change. -- Add matching dark HTML built-in pages for HTTP 200, 400, 408, 410, 413, 500 and 502. Align the remaining generic HTML status template with the established RustProxy appearance. Preserve administrator-configured responses, status codes, protocol headers and bodyless responses. -- Reduce long-lived GeoIP CSV allocations by storing exactly sized boxed range slices and 34-byte network-order IPv6 records instead of 48-byte u128-aligned records; retain equivalent ordered range search. -- Avoid duplicating a single uniquely-owned transformed response frame when filling HOT cache, provided its allocation capacity is known and tightly bounded; preserve the copying fallback for stream/fragmented or shared bodies, HOT accounting and HTTP content unchanged. -- Keep existing cache/transport limits and persisted configuration format. Build, Rust tests, live deployment and memory comparisons still require verification with the supported toolchain. +- Added **Dashboard Presets**: up to **16 named Overview layouts per administrator**, with save, load, overwrite, rename, delete and factory reset. +- Presets retain card order, visibility, widths, density and chart history length. Existing account preferences and proxy settings remain compatible. +- Added an interactive Vhost backend summary showing the configured destination, HTTP mode, health state and latest observed HTTP response without generating extra probes. +- Changed HTTP/1.1 and HTTP/2 backend badges from basic tooltips into interactive details panels with current observations and response counts. +- Standardized Overview card titles, chart captions, legend typography and popover appearance; styles follow the selected dashboard theme and corner-rounding preset. -## 0.31.7 +## 0.33.6 — consistent chart scales -- Allow intentionally pruned historical release audits and implementation notes in the 0.31.7 source archive. Update preflight, documentation/source contract checks, and the embedded Reader registry to require actual runtime/compiled documents instead. Remove stale documentation links, update Reader integration assertions, and add a regression check that still rejects missing embedded documents or runtime modules. No runtime configuration, storage format or UI behavior changed. +- Fixed Overview and Web Server chart heights to prevent stretched or uneven plots at different screen sizes. +- Kept CPU and other percentage-based charts on consistent **0–100%** scales, with actual values still shown in legends. +- Made rate, count, latency and memory charts start from zero instead of exaggerating low traffic. +- Unified Cache and Transform rate-chart dimensions, preserving the real values, history and refresh intervals. -- Refine revision history dialogs: place **Full JSON comparison** directly below **Restore revision**, allow long change lists to scroll without overflowing the modal, and use the standard dialog border for JSON comparison. No revision data, restore logic or API changes. +## 0.33.5 — CPU monitoring and chart headings -- Group Global User-Agent blocklist, Global custom headers and Client response under a new **Settings → Rules & headers** tab with three sub-tabs, using the existing persistent Settings forms. Security continues to own session and hostname access controls. -- Introduce validated per-vhost User-Agent request limits: up to 64 case-insensitive substring/`*` patterns, each with its own request count, window and burst. First matching UA rule overrides country/default limits and is scoped per vhost and client IP; user agents without a match retain the existing limiter. Settings remain optional and older saved configs load unchanged. -- Show only changed configuration fields in the revision preview, with ID-stable array paths. Move full before/after JSON into a separate on-demand modal. Revision restore and retention remain unchanged. -- Add spacing between the HTTP/2 and HTTP/3 listener tuning links. Collapse ACL profile **Attached to** by default without hiding its contents or changing profile attachment rules. -- Correct `update.sh` retention cleanup (mtime-sort key was previously included in the removal path) and retain **two** installed releases, including the active release. Preserve **three** private stopped-service backups for rollback. Preserve Cargo fingerprints, compiled deps and executables; only expired incremental build state may be removed, after a successful update and only while Cargo's profile lock is available. -- Confirm managed-update marker is created before `systemctl stop` and read on shutdown, triggering best-effort HOT→COLD persistence even when `cold_on_shutdown=false` if cold storage is enabled. Add completion diagnostics counting successful persisted objects (including zero), not merely attempted writes; no claim of a successful runtime flush without testing. +- Corrected System and process CPU charts so low utilization no longer appears as a full-height spike; legends continue to show actual percentages. +- Stabilized CPU chart dimensions across layout sizes and dashboard densities. +- Moved the maximum requests-per-second reading next to the Requests per second heading and simplified live chart labels. -## 0.31.6 +## 0.33.4 — Web Service live metrics -- Preserve logo click as the original `href="/"` full-page navigation. For H3 to HTTP/1.1 backend forwarding, complete empty GET/HEAD receive streams through a bounded first-frame probe before forwarding. Hyper already special-cases GET/HEAD H1 encoding; this change targets H3 receive-half lifecycle, not assumed chunked GET framing. Also bound H3 GET/HEAD cache and 404 preflight so a missing FIN cannot indefinitely prevent navigation; rewind/replay any nonempty body unchanged. -- Do not abort all in-flight H3 responses when the H3 accept loop fails; stop accepting and drain the active request tasks within the normal grace bound. -- Persist H3 pre-header peer cancels as status-0 diagnostic events rather than HTTP 400. For interrupted responses record phase, QUIC stream ID, method, query-free path, response status, sent byte count and redacted failure reason; preserve non-cancellation per-stream error reset behavior. Log start/completion/interruption of root-document `GET /` with a stream ID and response status in service INFO/WARN logs. -- Add H3 navigation regression tests and iPhone reproduction checklist. Retain the 0.31.5 Stored Logs mobile layout, HOT/COLD cache preservation and administrator Origin protections. Rust build and real iOS testing are still required. +- Made Web Service live metrics always visible in the service view, with **six responsive metric tiles** matching the Vhost dashboard. +- Added a full-width Web Service request-rate history chart and improved metric refresh during service-list updates. +- Preserved existing PHP concurrency metrics, request sampling and monitoring APIs. -## 0.31.5 +## 0.33.3 — flexible Overview layout -- Make the Stored Logs explorer responsive: compact, keyboard-expandable record cards and readable parsed fields on narrow screens; keep user-selected columns accessible without widening the whole page. Desktop table behavior remains available. -- Do not persist a synthetic HTTP 400 for a QUIC peer cancelling an incomplete H3 request with `H3_REQUEST_CANCELLED`/`H3_NO_ERROR` on refresh. Preserve real HTTP/3 protocol-error logging and avoid a second internal reset after identified peer cancellation. The related white-page symptom still needs live browser/QUIC validation. -- Preserve HOT/COLD cache compatibility identity when per-vhost HTTP/2 or HTTP/3 enablement changes; these are frontend protocol switches, not cached-response representation changes. No cache schema migration. -- Clarify that the optional public panel HTTPS origin pins the external scheme/authority for Origin/CSRF validation with a trusted reverse proxy, while additional allowed DNS hostnames only expand the strict Host allowlist. Add source/Rust regression cases and a browser-width layout smoke test; compiler and live wire acceptance are still required. +- Added card-width choices of **2, 3, 4, 6 or 12 columns** and improved drag-and-drop ordering between different widget groups. +- Updated the layout editor to remain usable on small screens; saved widths, hidden widgets and factory layout settings are preserved. +- Fixed charts that did not fill resized widgets and false gaps or zero readings after returning to a suspended browser tab. +- Made the sidebar's selected/hover colors follow the interface theme rather than the independent chart palette. -- Give repeated-value analysis its own Stored Logs tab and move the rotation-consolidation option to a compact top-right control. Preserve the browser-local consolidation choice. +## 0.33.2 — theme consistency -- Fingerprint panel, login and documentation JS/CSS asset filenames with a random 16-hex build identifier independent of the application version. Reject legacy JS/CSS URLs (404), retaining unversioned JSON/SVG; enforce exact fingerprints and preserve the existing HTML cache-header behavior. No SQLite/API changes. +- Made Cache, Web Server, Traffic and Overview progress indicators follow the selected chart colors and corner-rounding style. +- Unified RAM utilization meters and matching progress bars across dashboard panels, including light/dark theme switching. +- Monitoring values, polling frequency and saved preferences remain unchanged. -- Remember the Stored Logs consolidation toggle per browser with localStorage, preserving the existing default and avoiding API/database changes. +## 0.33.1 — reliable layout editing -- Add global and per-vhost User-Agent blocklists with bounded, case-insensitive substring/wildcard matching before upstream connection. Existing configurations remain valid and the feature is disabled by default. -- Make Traffic values copyable for faster investigation and blocklist preparation. -- Add repeated-value aggregation to Stored Logs for User-Agent, client IP and arbitrary parsed fields, with bounded result/cardinality limits. -- Let Stored Logs consolidate an active per-vhost JSONL file and its gzip rotations into one logical source. Preserve individual file metadata/source IDs and existing combined access/error behavior. -- Update English/Polish UI, OpenAPI and release documentation. SQLite schema 9 is unchanged; Rust/Cargo verification remains outstanding in this environment. +- Fixed hidden/shown Overview widgets unexpectedly reverting while editing the layout; Save and Cancel now respect the unfinished changes. +- Added a consistent factory Overview layout and an explicit reset/preview workflow for card order, visibility, compact density and initial chart history. +- Preserved history and rate baselines while widgets are temporarily hidden, and clarified lifetime versus recent cache HIT ratios. -## 0.31.4 +## 0.33.0 — Web Server monitoring -- Correct the administration panel's handling of intermittent `auth.invalid_origin` with optional explicit HTTPS `administration.public_origin` when behind a trusted reverse proxy. Allow a bounded, HTTPS-first multi-hop forwarded-proto chain without trusting arbitrary forwarding headers. -- Use a single public panel URL: automatically include its hostname in the panel's effective Host allowlist. Keep `allowed_hosts` as optional **additional** DNS entries in an advanced section and deduplicate legacy repeated values on edit/save without an automatic database migration. -- Preserve strict Host, forwarded-Host, Origin, Fetch Metadata, CSRF and trusted-proxy checks; keep public proxy routes and SQLite schema unchanged. -- Add configuration and HTTP guard simulations, Rust source regression tests, English/Polish form labels and operator documentation. Rust/Cargo build and live mobile acceptance are still outstanding. +- Added richer Web Server and PHP-FPM statistics, including request types, HTTP status counts, methods, response-header timing, compression and PHP concurrency. +- Expanded Prometheus/Grafana reporting with per-service Web Server and PHP activity, errors and performance metrics. +- Added **12 optional Web Server Overview cards**, hidden by default, with the existing layout and refresh controls. +- Improved Overview history selection and corrected a dashboard initialization issue that could stop the panel loading. +- Kept the existing configuration format, API compatibility and saved dashboard preferences. -## 0.31.3 +## 0.32.8 — resource and compression charts -- Prevent duplicate session requests after BFCache/tab restoration; suspend SSE/polling until session validation finishes, then resume once. Replace automatic resume reload loops with a visible, localized retry screen and preserve the startup recovery limit until initialization is complete. -- Return a readable 403 document for invalid-Origin dashboard navigations; continue rejecting cross-origin API requests and cross-site fetches. Add redacted, bounded server diagnostics for rejected Origin checks and security regressions. -- Keep proxy routing, cookies/CSRF, local administration policy, SQLite schema 9 and user configuration unchanged. Rust/Cargo and real mobile reverse-proxy acceptance remain required. +- Added capacity indicators for HOT RAM cache, COLD queue/disk, file descriptors, HTTP protocol share and backend usage. +- Added compact cache HIT/MISS donut charts with counts and percentages. +- Added detailed Transform and Compression activity graphs with separate series for transformation results and gzip/Brotli/Zstd/deflate. +- Improved Overview action-button layout on narrow screens without changing stored settings or metrics. -## 0.31.2 +## 0.32.7 — HTTP/2 lifecycle and dashboard fixes -- Refresh core runtime, security, TLS certificate generation, database, Swagger UI and monitoring libraries for current stable versions. Keep persisted data, cache format, configuration, panel behavior, HTTP/3 and proxy routing compatible. -- Move the build toolchain baseline to Rust 1.99. This dependency refresh must pass compilation, runtime and integration checks before production deployment. +- Improved handling of an upstream HTTP/2 connection shutting down gracefully, including a limited retry for eligible empty, safe requests on the same backend. +- Prevented normal HTTP/2 shutdown events from incorrectly counting as backend health failures; unrelated connection errors still count. +- Improved backend recovery after passive health ejection, including bounded recovery when active health probes are disabled. +- Reduced duplicate shutdown diagnostics and improved Overview button sizing and density presentation. -## 0.31.1 +## 0.32.6 — appearance and backend protocol visibility -- Make mobile and reverse-proxied panel reloads reliable by loading the dashboard JavaScript as one ordered resource, waiting for its initialization before fetching settings, and showing a recoverable error if essential resources are unavailable. Preserve the loading indicator and preferences. -- Version panel JavaScript and CSS URLs to prevent mixing assets from different releases in intermediate caches. No configuration, API, or proxy traffic changes. +- Improved Appearance layout controls, dashboard card density, responsive mobile metrics and corner-rounding consistency. +- Expanded live Process and Network charts using available CPU, memory and network-interface measurements. +- Prevented false zero-rate readings and interrupted chart histories when returning to inactive browser tabs. +- Added the **observed** backend HTTP/1.1/HTTP/2 mode next to the configured mode; unknown or stale observations display `?` instead of a guessed result. +- Preserved existing refresh settings, polling behavior and administrator preferences. -## 0.31.0 +## 0.32.5 — DNS/DDNS access rules -- Harden BAN/PURGE with scheme/host/vhost isolation, unambiguous headers and bounded background cache-maintenance workers (shared with authenticated admin purge/invalidation); audit BAN-rule removals before mutation. Add a localized curl-examples tooltip to Settings → Cache. -- Add browser-negotiated Zstd/Brotli/gzip compression for panel HTML and static assets, configurable under Settings → Administration without an extra environment variable; cached representations and `Vary: Accept-Encoding` work with or without a reverse proxy (subject to forwarded headers). -- Add strictly opt-in, IP/CIDR-gated HTTP `PURGE` (exact URL) and `BAN` (lazy URL prefix) on public vhost listeners, using the existing invalidation engine, ACL/rate limits, audit and CIDR validation. Empty allowlist keeps the feature disabled; no DB or cache-format migration. -- Align the System information manual Refresh control directly next to the API badge. -- Make the authenticated panel visible during initialization: replace the previous fully hidden body with an accessible loading indicator, a delayed reload action and an explicit startup-failure state. Paint the Overview shell before slow preferences/config/status requests complete. -- Place **Refresh now** beside the Overview title at the far right, including on narrow mobile screens. Serve panel HTML/assets using the new configurable negotiated codec policy, with an uncompressed fallback. No new endpoint, API change or proxy traffic change. -- Add per-administrator Refresh preferences under Settings → Appearance, including Live enable/disable and supported intervals for status/SSE (1/2/5/10 s), Traffic/Cache (0/3/5/10/30 s), and system information (0/15/30/60/300 s). Reset and manual refresh remain available, even when automatic updates are off. -- Replace duplicated dashboard timers with one coordinator for SSE, fallback polling, Traffic, Cache, Transform, Docker, ACME, tunnel diagnostics and editor-draft presentation. Poll only active views, prevent overlapping requests, abort stale views, pause when hidden and stop on logout. -- Keep the full authenticated status API for compatibility; add a compact live-only status view and a separate authenticated `/api/system-info`. SSE snapshots exclude full configuration and expensive system diagnostics. SSE interval is per client and does not affect other sessions. -- Update English and Polish UI labels, OpenAPI, documentation and release checks. No database schema, cache format or proxy data-plane changes. +- Added DNS/DDNS hostnames as **allow-only** entries in reusable client ACLs, Vhosts, Custom locations and Docker ACL assignments. +- Hostname A/AAAA records are periodically refreshed (**60-second** interval); unresolved or failed lookups deny access rather than allowing unknown clients. +- Added DNS/IP selection and guidance to the ACL editor. Existing IP/CIDR rules and stored policy formats remain compatible. -## 0.30.1 +## 0.32.4 — Preserve Host with HTTP/2 -- 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. +- Expanded **Preserve Host** support across HTTP/1.1, automatic HTTPS H2/H1, compatible HTTP/2 and strict H2/H2C upstream modes. +- Forwarded the public hostname as HTTP/2 authority while continuing to use the configured origin for destination routing and TLS identity checks. +- Removed unnecessary UI restrictions when using Preserve Host with different backend protocols, while retaining warnings for potentially invalid HTTPS certificate names. -## 0.30.0 +## 0.32.3 — protocol selection and customizable Overview -- 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. -- Improve long-running memory behavior further: reclaim expired cache PURGE-generation metadata, avoid per-request allocations for repeated analytics labels, reuse JSON access-event serialization with Loki, and remove redundant buffers from local directory listings. Keep all outputs/configuration semantics unchanged. See [memory stress verification](docs/MEMORY_STRESS.md); Rust runtime/RSS acceptance is still required. -- Share one JSONL serialization between file and Logstash sinks. Limit in-memory directory inventories while scanning historical per-vhost logs and gzip rotations, rather than collecting all matching files before truncation. -- Keep SQLite schema 9 and Cold Cache format 3 unchanged. Rust build, long-duration RSS/heap measurements and real traffic tests remain mandatory before production acceptance. +- Added strict **`h2`** upstream mode; existing `http2` remains compatible with HTTPS H2/H1 negotiation and plaintext H2C. `http1` and `auto` keep their previous behavior. +- Expanded the Vhost Origin protocol selector with clearer choices for HTTP/1.1, Auto, compatible HTTP/2 and H2-only. +- Expanded Overview customization: unlock editing directly on the dashboard, reorder or hide registered cards, preview changes, Save/Cancel and reset to the default layout. +- Added optional protocol, cache, backend health, connection and system/process charts; the Overview catalog expanded to **62 blocks**. New diagnostic cards remain hidden unless enabled. +- Added **Ultra dark** and **Warm cream** panel themes and account-level corner-rounding presets (Original, Square, Subtle, Rounded and Extra rounded). +- Improved the default Overview grid and customized card arrangement across desktop, mobile and browser zoom. Existing saved layouts remain compatible. -## 0.29.7 +## 0.32.2 — correct local cache revalidation -- Introduce per-backend `health_check_enabled` (default true) and optional per-backend `health_check` override (default null): both active probes and passive health ejection can be switched off without disabling a path-pinned backend. Add UI, status and validation. -- Add cache diagnostic policy `expose_only_hits` (global default false; vhost null inherits) with global and vhost controls, keeping Via separate and cache hits visible while MISS/BYPASS remain unadvertised when opted in. -- Warn when active Web Services use one PHP-FPM socket with different dynamic PHP_VALUE/PHP_ADMIN_VALUE directives. Provide instructions for dedicated PHP-FPM pools; RustProxy cannot reset worker-scoped INI state inside a reused shared FPM worker. -- Preserve schema 9, Cold format 3 and existing configuration defaults. +- Fixed Web Server cache revalidation with `ETag` and `Last-Modified`, including requests using `no-cache` or `max-age=0`. +- A valid origin `304 Not Modified` can refresh the existing cached body; changed or no-longer-cacheable content is not incorrectly reused. +- Preserved client-side HEAD, conditional and Range behavior as well as transformed-response headers. +- No database, cache-format, API or Web Server setting changes. -- Extend PHP-FPM help in both Web Service settings and reusable PHP profiles with concrete parameter/value syntax, limits, privilege distinctions, WordPress/Roundcube examples and dedicated-pool guidance. Align PHP/PATH_INFO switches and attach consistent info tooltips. -- Add opt-in vhost `pages.intercept_statuses` so selected application-origin HTTP error bodies are replaced by vhost/global/built-in RustProxy error pages, with preserved application status, no-store, and higher-priority named internal fallbacks. Keep old configs disabled and add regression coverage, localization and OpenAPI documentation. -- Move the shared PHP profile syntax/safety help button below the profile list and Add profile action, separated by a horizontal rule. Remove an unnecessary mutable Web Server response binding. Ensure the outer observer never silently replaces reverse-proxy or Web Server/PHP origin errors with global pages when interception is disabled; origin codes remain unchanged even when the replacement page has its own status override. +## 0.32.1 — dashboard readability -## 0.29.6 +- Restored the earlier Overview layout proportions and consistent spacing between dashboard cards. +- Improved compact font sizes and responsive layout at reduced UI zoom, without forcing large empty chart areas. +- Simplified Stream card help text and retained the existing visibility and saved Appearance settings. -- Fix PHP-FPM response truncation with gzip, Brotli, Zstd and deflate in native Web Server mode: fuse the FastCGI response stream so compression can safely poll after EOF without a Tokio worker panic. -- Route `WARN` and `ERROR` diagnostics (including bounded PHP-FPM stderr and Rust panic-hook events) to the configured error file when enabled; otherwise use console/systemd. Remove duplicate stderr entries while file logging works; fall back to stderr if the file cannot be written or the bounded queue is full. -- Add FastCGI EOF/compression and dynamic diagnostic-routing regression tests. No schema, cache-format or saved-configuration changes. +## 0.32.0 — multi-backend TCP/UDP Streams -## 0.29.5 +- Added Stream backend pools of up to **32 members**, with weights, enable/disable, draining, session caps and compatible single-upstream fallback. +- Added weighted Round Robin and Least Connections/Sessions balancing for Stream backends, with connections remaining associated with their selected backend. +- Added optional TCP connect and response-based UDP health checks with configurable intervals, timeouts and recovery/failure thresholds. +- Expanded per-backend Stream status and optional Prometheus metrics; the existing global Stream metrics switch remains in control. +- Added four optional Stream cards to Overview and improved their no-data and enabled/disabled presentation. +- Preserved the six-column desktop Overview and previous Stream configurations. -- Fix Roundcube 1.7 static asset delivery: a valid `static.php/...` PHP `PATH_INFO` target executes directly, before the Web Service's `try_files` fallback can mistakenly route it to `index.php` and return HTML instead of CSS/JavaScript. -- Apply Web Service hardening and `.htaccess` restrictions to the resolved PHP script, not just the full virtual URL. -- Test the actual FastCGI params for Roundcube static assets and verify opt-in PATH_INFO behavior with the front-controller fallback preserved when disabled. -- Retain the 0.29.4 shared PHP-FPM profile manager, Web Server cache/transform fixes, SQLite schema 9 and existing saved configuration. +## 0.31.9 — Certificate API credentials -## 0.29.4 +- Added **View settings**, **Edit settings** and **Regenerate token** for individual Certificate API credentials. +- Administrators can update certificate grants, comments and deployment permissions without rotating an existing token. +- Token regeneration invalidates the old secret immediately; the new token is shown only when generated, not returned by later settings reads. +- Added matching authenticated API operations while retaining certificate-scoped authorization and existing session security. -- Expose the existing shared PHP-FPM profile manager directly beside **New Web Service** in the Web Server toolbar, without removing the shortcut in the service editor. -- Add named, shared PHP-FPM configuration profiles that Web Services can attach or detach without modifying their local PHP settings, with a profile-management modal and revisioned API support. -- Apply the vhost cache and transformation pipeline to Web Server local-file/PHP routes, with fresh-hit reuse, vhost response rules, response-side privacy protection and bounded GET/HEAD request-body probing. -- Supply the validated HTTP authority in FastCGI `HTTP_HOST`, including authority-only HTTP/2 and HTTP/3 requests used by WordPress. -- Retain SQLite schema 9, Cold Cache format 3 and saved-configuration compatibility with 0.29.3. +## 0.31.8 — Docker status and log explorer -## 0.29.3 +- Changed an unobserved Docker agent startup state to **WAITING** rather than incorrectly reporting a failed connection; actual failures are shown after a check. +- Added server-side sorting in Stored Logs, including numeric and nested fields, before paginating matching results. +- Added consistent built-in HTML error/status pages for more response codes; custom error pages keep priority. +- Improved cache and GeoIP resource handling without changing saved configuration formats. -- Fix internal error fallback on empty GET/HEAD requests over HTTP/2 and HTTP/3 with unknown body length; safely probe truly empty bodies without replaying nonempty or unbounded payloads. -- Preserve the final status and response body of the internal fallback target, including its own 404 HTML, without overwriting it with RustProxy's global error page; warn when a named fallback location is missing. -- Retain SQLite schema 9, Cold Cache format 3 and saved-configuration compatibility with 0.29.2. +## 0.31.7 — rate limits and configuration history -## 0.29.2 +- Added a **Settings → Rules & headers** workspace for the global User-Agent blocklist, custom headers and client-response rules. +- Added per-Vhost User-Agent request limits: up to **64 patterns**, each with its own rate, window and burst. Rules are optional; unmatched clients use existing limits. +- Improved configuration revision previews to show changed fields first, with full JSON comparison available separately. +- Fixed managed-update cleanup and documented retention of two installed releases plus three private stopped-service rollback backups. +- Improved preservation of eligible HOT cache entries to COLD storage during managed updates when COLD is enabled. -- Debian install/update now deploys managed logrotate policy (14 daily rotations, 100 MB max, compressed, copytruncate) and repairs the shell PATH profile on upgrade. +## 0.31.6 — HTTP/3 forwarding diagnostics -- Fix Web Server vhosts returning 503 because backend `transient_unavailable` was applied to the main filesystem/PHP route. The main Web Server source does not consult or compile unused backends; enabled reverse-proxy Custom Locations still use the configured backend pool. -- Remove the three error-fallback Prometheus metrics without changing internal fallback routing. +- Improved empty HTTP/3 GET/HEAD forwarding to HTTP/1.1 backends and handling of graceful HTTP/3 connection shutdown. +- Distinguished cancelled HTTP/3 requests from genuine HTTP 400 errors in stored diagnostics. +- Expanded interrupted-response diagnostics with phase, response status, transferred bytes and redacted failure context. -- Add validated, operator-managed `PHP_ADMIN_VALUE` support for shared trusted PHP-FPM endpoints. -- Expose service-scoped PHP response counts, response-header latency and concurrency gauges in Prometheus, alongside Web Server status and latency counters. -- Keep FastCGI reserved parameter protections and PHP `PATH_INFO` support. +## 0.31.5 — Stored Logs and request filtering -## 0.29.1 +- Improved Stored Logs on mobile with readable expandable records, configurable columns and repeated-value analysis for User-Agent, client IP and other parsed fields. +- Added consolidated viewing of a Vhost's active JSONL logs together with compressed rotations, while preserving each file's identity. +- Added optional global and per-Vhost **User-Agent blocklists**, disabled by default, using case-insensitive patterns. +- Improved HTTP/3 cancellation reporting and preserved HOT/COLD cache contents when only frontend HTTP/2 or HTTP/3 enablement changes. +- Improved panel asset-cache versioning and trusted-public-origin guidance for administrators using a reverse proxy. -- Add Nginx-style internal error fallback routing for vhost sources and Custom locations, including internal-only named locations, configurable 403/404/500/502/503/504 interception, original request target/context preservation, validation-time cycle rejection and a runtime depth guard. -- Intercept configured upstream/local errors before finalization or cache storage, then process fallback targets through normal ACL, header, compression, cache and response handling. -- Add streaming multipart `Range` support with at most 16 byte ranges per request while retaining existing single-range behavior. -- Add optional PHP-FPM `PATH_INFO` support that securely separates an existing script path from trailing path information. -- Keep SQLite schema 9, saved-configuration compatibility and Cold Cache format 3 unchanged. +## 0.31.4 — administration behind HTTPS proxies -## 0.29.0 -- Group vhost hostnames and WWW redirect controls in Frontends. Each exact domain shows both redirect directions as selectable source → destination cards, even while redirects are off. Implicit `www.` targets are supported without adding an extra hostname. Other aliases continue serving the same application without cross-domain redirects; wildcard targets are excluded. +- Added an explicit **administration public HTTPS origin** option for panels published behind a trusted reverse proxy. +- Corrected forwarded-protocol validation and automatically included the configured public hostname in the effective administration Host allowlist. +- Preserved strict Origin, Host, CSRF and trusted-proxy checks; the setting does not make untrusted forwarded headers acceptable. -- Add one-click per-vhost canonical www/non-www redirection. The first exact DNS hostname is canonical and its companion hostname redirects permanently while retaining the request target; Force SSL and canonicalization collapse into one redirect where possible. -- Add a dedicated Custom location Redirect action with configurable permanent/temporary status, absolute HTTP(S) target, optional capture rewrite regex and query preservation. -- Default HTTP health-check `Host` to the first exact vhost DNS hostname when no explicit health-check Host override is configured. -- Add an opt-in per-vhost forced-cache target list for narrowly scoped URLs that must be cacheable despite request query/Cookie/Authorization/client-cache directives. Response `Set-Cookie`, `private`, `no-store` and other response-side safety exclusions still block storage, and credential/cookie values remain representation variants. -- Read bounded logrotate `.gz` access/error/per-vhost log rotations from Stored Logs without accepting arbitrary filesystem paths or unbounded decompression. -- Cover static maintenance HTML returned with an explicit status such as `503 Service Unavailable`. -- Preserve valid nested-URL query strings end to end and retain rejection of literal/encoded backslashes and other ambiguous path encodings. -- Keep SQLite schema 9, saved-configuration compatibility and Cold Cache format 3 unchanged. - -## 0.28.2 - -- Harden external certificate-file loading for Certbot/system-managed PEMs without requiring an exact chmod. POSIX ACL read access and group-readable private keys are supported; private keys still reject world-read and all external certificate/key files reject group/world-write. -- Validate the configured external path and the resolved Certbot target through root/RustProxy-owned non-writable parent directories, while allowing root-owned sticky ancestors such as `/tmp`. -- Make Certificate API credentials least-privilege: new tokens are reload-only by default, require an explicit certificate-ID allowlist, cannot call global reload, and need an explicit opt-in for deploy permission. Existing credentials migrate to reload-only broad access until rotated. -- Add SQLite schema 9 for per-token Certificate API grants; the saved proxy configuration and COLD cache format remain unchanged. -- Add explicit read-only systemd access declarations for `/etc/letsencrypt` and `/etc/ssl` and document POSIX ACL setup, including directory traversal-only grants. -- Improve English/Polish certificate UI help so external file ownership, ACL, parent-directory and reload-token requirements are visible where the paths/tokens are configured. - -## 0.28.1 - -- Add manual certificate support for absolute external certificate/key paths, including `/etc/letsencrypt/live/...` pairs. Certbot-style symlinks are resolved again on every reload so an external renewal can be hot-published without copying files into RustProxy storage. -- Keep `POST /api/certificates/{certificate_id}/reload` revision-stable for Certbot/acme.sh wrapper hooks and reject use of the deploy endpoint against an ID configured with external file paths. -- Preserve the legacy/simple filename mode for `/certs`, reject mixed relative/absolute pairs, and leave upload/deploy request bodies path-free. -- Exclude externally referenced PEM material from RustProxy backups; only their configured absolute paths are retained, so the external files/mounts must be restored separately. -- Clarify certificate source/storage behavior in English and Polish across ACME, Import and Existing-files UI modes. -- Keep SQLite schema 8 and COLD format 3 unchanged. +## 0.31.3 — session and browser restoration -## 0.28.0 +- Fixed duplicate panel session checks and repeated reload attempts after tab suspension or browser back/forward restoration. +- Added a recoverable startup/retry state and clearer invalid-Origin navigation errors while keeping cross-origin API requests blocked. -- Add a manual GeoIP **Download now** action and replace the fragile release/checksum-base coupling with direct source URLs. CSV mode accepts separate IPv4/IPv6 files; MMDB mode accepts one native MaxMind DB for both families. Publisher SHA-256 URLs are optional per file; size/format validation, staging, the updater lock, atomic publication and hot reload remain mandatory. -- Improve active-listener visibility in Settings by rendering HTTP/HTTPS binds as distinct endpoint cards and showing the active `H1`, `H2`, `H2C` and `H3/UDP` protocol badges per listener. -- Remove the speculative Python/Certbot `checking/preparing` line from the startup banner; log the final detected ACME runtime state at INFO after the asynchronous check completes. -- Add a configurable cache `Via` response header, enabled by default with value `RustProxy`, while preserving any upstream `Via` chain. -- Avoid full cache invalidation when only cache bypass rules or response-only cache diagnostic/Via headers change; representation-affecting settings still invalidate incompatible cache entries. -- Fix the vhost Security header-rule editor layout so Delete no longer collides with the first Operation selector. -- Keep SQLite schema 8 and COLD format 3 unchanged; older configurations gain the default `cache.via_header` through serde defaults. - -## 0.27.3 - -- Preserve the existing data-directory UID/GID across backup restore, including restores executed as root. -- Make `/var/lib/rustproxy` the production CLI data-directory default while retaining `./data` in `dev.sh`. -- Add enabled-by-default automatic GeoIP updates with configurable HTTPS release/checksum sources, 256 MiB default file limit, 24-hour default interval, immediate first-start attempt, publisher SHA-256 verification, streaming staging, live reload and serialization with manual upgrades. -- Fix explicit startup listener overrides so `--proxy-listen`/`--tls-listen` replace the canonical saved HTTP/HTTPS bind instead of retaining the old `:80`/`:443` listener alongside it. -- Accept strict WebSocket `101` responses with absent `Content-Length` or `Content-Length: 0`; reject positive `Content-Length` and any `Transfer-Encoding` without requiring legacy mode. -- Make release-version checking strictly best-effort and start it only after listener binding succeeds; DNS/update-server failures no longer interfere with startup diagnostics. -- Keep the sidebar runtime/footer block at the viewport bottom when interface scale is reduced. - -- Fix managed Certbot validation and execution: Certbot 5.8.0 is a console-script application and does not provide `certbot.__main__`, so `python -m certbot` incorrectly failed validation. Managed ACME now executes the final `acme-python/bin/certbot` entry point directly; 0.27.3 creates/rebuilds the environment at its final path so the console-script shebang is valid. - -- Improve managed ACME bootstrap on systems with Python/pip but restricted `venv`/PEP 668 behavior: keep installation private, retry the application-local `virtualenv` bootstrap safely when required, and report whether failure happened during environment creation, pip preparation, Certbot installation or validation. - -- Separate application-coupled Python scripts into `scripts/runtime`, `scripts/build`, `scripts/deploy` and `scripts/migration`; move QA/dev/audit helpers to `tools/` and platform-specific OPNsense/RouterOS helpers to `integrations/`, with all entry points and release checks updated. - -- Fix HTTP/3 `HEAD` cache handling by running the same bounded empty-request-body probe used for `GET`; HTTP/2 and HTTP/3 now apply the same cache semantics instead of treating an unpolled H3 body stream as `request_body`. -- Reduce persistent COLD-cache index RAM by dropping per-entry `HeaderMap`/validator copies, removing the duplicate key, and storing the SHA-256 digest as 32 bytes. Existing `.json`/`.body` files remain format-compatible; headers and validators are validated and loaded only when a COLD object is actually read. -- Reduce rate-limit RAM and stale-IP retention by grouping client buckets under one vhost string, sweeping expired fixed-window buckets during requests and periodic maintenance, and shrinking sparse hash tables before allocator trimming. Active fixed-window behavior and the 65,536-client hard bound are unchanged. -- Exclude rebuildable `acme-python` and `acme_python` managed runtime directories from backups. -- Confirm response compression remains streaming, uses `CompressionLevel::Fastest`, and does not aggregate complete response bodies; memory use scales with concurrently compressed responses rather than retained cache objects. -- Promote `https://rustproxy.linuxiarz.pl/` (DEB/RPM) and `https://rustproxy-opn.linuxiarz.pl/` (OPNsense plugin) as the official documented installation/update paths; keep Debian source build/update as an optional path. -- Keep SQLite schema 8, saved configuration compatibility and the COLD on-disk format unchanged. - -## 0.27.2 - -- Fix managed ACME issuance/renewal after the 0.27.1 private-Python change: Certbot is now launched through the final managed interpreter (`acme-python/bin/python -m certbot`) instead of the pip console script whose absolute shebang still referenced the temporary `acme-python.installing` path after the atomic directory rename. -- Validate managed Certbot through the same module-based launch path, so already-created 0.27.1 environments recover immediately after upgrade without deleting or rebuilding `/var/lib/rustproxy/acme-python`. -- Keep the distribution Certbot fallback unchanged for systems where the managed environment cannot be used. -- Keep SQLite schema 8 and the saved configuration format unchanged. - -## 0.27.1 - -- Show ready-to-run first-administrator setup-token commands instead of requiring a `$DATA_DIR` placeholder substitution. -- Move Certbot automation to a RustProxy-managed Python environment under the application data directory, initialized automatically with `venv`/`pip` and an app-local `virtualenv` bootstrap when needed. -- Retain distribution Certbot as a fallback when available and expose managed/system/unavailable runtime state in the Certificates panel, including explicit missing-Python/pip diagnostics for minimal systems such as OPNsense. -- Add account-scoped column and direction sorting to vhosts, Docker vhosts, redirects, 404 hosts and streams with feature-specific columns; Redirects and 404 hosts use the same search/sort/action toolbar layout as Vhosts. -- Replace the development-only Certbot hook example with the installed production binary plus explicit data directory, and expose the official source/package repositories plus Python/pip/Certbot runtime state in the startup banner and About/runtime views. -- Keep SQLite schema 8; administrator sort preferences are additive/defaulted and require no migration. - -## 0.27.0 - -- Enable downstream response compression by default for zstd, Brotli and gzip; keep deflate opt-in. Client q-values remain authoritative and equal-quality encodings use `zstd -> br -> gzip -> deflate` priority. Existing `Content-Encoding` is passed through unchanged. -- Compress eligible authenticated/cookie/private/no-store responses instead of broadly excluding them. Explicit `no-transform`, ranges, SSE, upgrades and already encoded responses remain uncompressed by RustProxy. -- Add inherit/enable/disable dynamic-compression controls plus per-scope zstd/Brotli/gzip/deflate filters to ordinary vhosts and Web Server services. Docker vhosts use a deliberately simpler single Compression checkbox and always use the codec set/priorities from global Response settings. Global Response settings remain the hard codec ceiling and own the minimum size. -- Keep every explicitly published Docker hostname routed until it is explicitly unpublished. Missing services return `503 Service Unavailable`; temporary agent failures reuse the last successful inventory when present, and returning services are reattached automatically. -- Fix HOT/COLD cache progress meters so their configured-limit fill is visible in Chromium/WebKit and Firefox. -- Make fresh installations emit `Server: RustProxy` by default while preserving existing saved/legacy behavior. -- Remove Debian package-build metadata from the main source tree; ready Debian packages are maintained separately at `https://rustproxy.linuxiarz.pl`. -- Keep SQLite schema 8; the new compression override fields are defaulted/additive and require no database migration. - -## 0.26.7 - -- Add HOT and COLD cache occupancy progress bars on `/cache` for configured object and byte limits, with explicit percentage/limit details. -- Verify cache `Age` progression on stored HITs and retain 0.26.6 compatibility for existing custom `Age` rules. Response-side Age rules are deferred to the final wire stage so they cannot alter internal freshness/storage. Successful validator revalidation still legitimately restarts HTTP freshness age while `X-Cache-Resident-Age` continues to grow. -- Restore downstream compression for cookie-bearing responses only when RustProxy is serving a shared-cache HIT already accepted by cache policy or a local static Web Server response; privacy-sensitive dynamic/cookie responses remain excluded. -- Keep `Content-Encoding`/`Vary: Accept-Encoding` negotiation for gzip, Brotli, Zstandard and deflate, and make the bundled compression tester stop forcing `Cache-Control: no-cache` unless explicitly requested. -- Preserve compatible backend upstream clients across unrelated active-snapshot rebuilds so generation age, generation request count and rotation totals do not reset spuriously. -- Keep age-based and request-count upstream client recycling active while making long-running rotation diagnostics reliable. -- Update the OCI image to ship Certbot with the supported Cloudflare, OVH and RFC2136 DNS plugins in one pinned Python environment, and refresh container/install documentation. -- Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.6. - -## 0.26.6 - -- Persist and reapply backend diagnostics column and sorting preferences as account-scoped user preferences. -- Reset the certificate-creation form after an ACME issuance job succeeds; failed jobs keep non-secret values for correction. -- Fix ACL evaluation so explicit IP allow ranges and allow-only countries can independently satisfy the same `default=deny` policy while explicit denies continue to take precedence. -- Rework the ACL profile editor around two readable policy modes: **block by default + allow country/IP exceptions** and **allow by default + block country/IP exceptions**, with a live OR-policy preview and a retained custom mode for mixed rules. -- Refresh dynamic ACL editor translations after the language bundle loads or the user changes language, preventing raw keys such as `aclProfileNew` or `aclProfilePreviewBlocksAll` from appearing in the UI. -- Accept single IPv4/IPv6 host addresses in ACL rules without requiring users to type `/32` or `/128`; CIDR input remains supported. -- Document `migrate_npm.py` sibling-module requirements and correct the container migration example. -- Detect Linux runtime type in Process diagnostics and make LXC uptime/load container-aware while leaving VM/host and other environments on the existing statistics path. -- Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.5. - -## 0.26.5 - -- Keep compact toolbar selects and neighboring inputs on one interface-scaled control box so filters such as Stored Logs no longer render mismatched heights. -- Make **Streams -> Add TCP/UDP listener** navigate to **Settings -> Listeners** instead of opening the Stream editor. -- Keep SQLite schema 8 and the saved proxy configuration format unchanged; no migration is required from 0.26.4. - -## 0.26.4 - -- Add 404-host editors to the session draft dock so unfinished 404 forms can be minimized, switched away from and restored like Web Server, Vhost, Redirect and Stream drafts. -- Complete Polish Button style translations and align compact toolbar search/filter inputs with the selected button height. -- Standardize modal Enabled controls for 404 hosts, Redirects and Streams on the vhost state-control presentation. -- Replace the Overview-card checkbox list in Appearance with compact responsive visibility/reorder tiles. -- Make age-based upstream client recycling run from backend maintenance even without traffic, fixing idle backends that could remain at rotation count 0 for hours. -- Keep SQLite schema 8 and the saved proxy configuration format unchanged; no migration is required from 0.26.3. - -## 0.26.3 - -- Make the **Settings -> Appearance** interface scale global across the authenticated administration UI, including dialogs, overlays and other top-level surfaces, and extend the selectable range from 80-125% to 70-125%. Form fields also receive a bounded proportional size adjustment so they track the selected scale more visibly. -- Reduce padding and typography in the Overview telemetry cards so the dashboard stays compact at wide resolutions while retaining the existing responsive layout. -- Expand chart presets from four to eight: Default, Soft, Technical, High contrast, Neon, Monochrome, Warm and Cool. -- Make chart presets alter the full presentation palette: line colors, chart surfaces, grid treatment, line weight/caps and contrast now change together across overview diagnostics, vhost/stream charts and Traffic donut/bar charts. -- Restyle the session draft dock as a square-corner RustProxy panel with ordinary panel surfaces, thin separators and compact rows instead of a rounded blurred floating card. -- Let the draft dock collapse to a single title bar and show the age of each suspended editor draft, while keeping drafts session-only and independently restorable/discardable. -- Add a Settings search field that filters settings categories by their translated label and page content without rebuilding forms or discarding unsaved values. -- Add color-palette selectors beside chart presets so the visual character of each chart theme is visible before/while selecting it. -- Add independent Normal/Compact/Ultra compact density controls for tables/logs and for Overview telemetry cards; these are separate from the global 70-125% interface scale. -- Make the Overview telemetry row configurable per administrator: cards can be hidden, restored and reordered without changing runtime telemetry collection. -- Add per-section Appearance reset actions for Interface, Charts, Tables/Logs and Dashboard, while retaining the full Appearance reset and preserving unrelated account-scoped preferences. -- Add an account-scoped **Button style** preference: Normal keeps the refreshed control sizing, while Compact restores the smaller source-style button dimensions without changing global interface scale. Apply it consistently to generated vhost actions and tabs inside dialogs. -- Keep appearance data in the existing account-scoped administrator preference JSON; SQLite schema 8 and the saved proxy configuration format are unchanged, so no data migration is required from 0.26.2. - -## 0.26.2 - -- Add a bottom-right draft dock for unfinished Web Server, Vhost, Redirect and Stream editors. Closing a dirty editor minimizes its exact in-session state instead of leaving stale modal state or blocking a new editor; drafts can be restored or discarded independently. -- Add **Settings -> Appearance** with account-scoped theme, automatic/explicit language, 80-125% interface scale, 12/24-hour time display and chart-style presets. These preferences are stored in the existing administrator preferences row. -- Apply the selected time format across dashboard timestamps and make expanded Stored Logs records more compact for long entries. - -- Combine Stored Logs and Live Logs under one top-bar **Logs** menu. -- Replace the Stored Logs filter matrix with an optional source selector plus a single field-query search supporting `field:value`, explicit `AND`, `!` negation, quoted values, wildcards and numeric comparisons. -- Build selectable Stored Logs columns dynamically from JSON fields instead of fixed `matched_by`/SNI protocol columns; saved account preferences can retain discovered fields. -- Parse RAW error-log lines into timestamp, level, target, message and detectable `key=value` fields, while preserving the original raw line. -- Show parsed field/value details for a selected record with raw JSON available on demand, and move the column picker to the upper-right table toolbar. -- Remove the configured-file inventory while retaining an optional source selector; bounded searches scan configured sources together by default or one selected source on demand. -- Replace backend sort-direction text with `↑`/`↓` symbols. -- Keep SQLite schema 8 and the existing saved proxy configuration format; no data migration is required from 0.26.1. - -## 0.26.1 - -- Route low-level protocol errors to per-vhost persistent logs when a vhost can be safely resolved from HTTP authority/Host or TLS SNI; unknown, malformed and non-attributable traffic remains in `_unmatched.jsonl`. -- Add `sni`, `authority`, `matched_by`, `reason` and `error` diagnostics to stored protocol-error records while keeping the 0.26.0 compatibility fields. -- Keep protocol-error persistence independent from the Live Logs memory buffer. -- Improve Logs Explorer with selectable saved columns, a sticky table header, highlighted selection and inline JSON record details. -- Collapse the configured-file inventory by default, repair mapped `client_ip` display and extend keyword filtering to protocol diagnostics. -- Keep schema 8 and the existing saved proxy configuration format. - -## 0.26.0 - -- Add persistent protocol-error traffic records for malformed HTTP, TLS ClientHello/handshake failures and timeouts, HTTP/2/HTTP/3 protocol failures, invalid h2c bootstraps and rejected connection/stream limits. These records use the existing bounded external logging queue and do not enter the Live Logs memory ring. -- Add a built-in read-only **Logs** explorer at `/logs`, reachable from the top bar and Settings -> Logging, with source, text, event, status-class, client-IP and vhost filters plus bounded pagination and JSON record inspection. -- Keep the stored-log explorer independent from Live Logs: it reads only configured access/error files, including existing files when file logging is currently disabled. -- Harden local log-file access with regular-file checks and `O_NOFOLLOW` on Unix; the browser API never accepts an arbitrary filesystem path and enforces bounded bytes, lines, sources, offsets and page sizes. -- Extend structured access-log fields with `event`, `protocol_error`, `error_detail`, `local_addr` and `tls_sni` so protocol failures can be distinguished from ordinary request records. -- Keep SQLite schema 8 and the existing saved configuration format; no data migration is required from 0.25.7. - -## 0.25.7 - -- Add one-click vhost enable/disable actions and make the vhost editor Enabled control clearly stand out with an explicit state indicator. -- Add search, sorting and selectable columns to the main Backends diagnostics table; visible columns and sort order are saved in the existing account-scoped administration preferences. -- Theme dashboard scrollbars for dark and light modes. -- Add ordinary/Docker Prometheus metrics for backend retry attempts/successes and upstream-client generation, generation request count, generation age and rotations. -- Add Grafana panels for ordinary and Docker backend client lifecycle, retries and rotations. -- Keep SQLite schema 8 and the existing configuration format; no migration is required from 0.25.6. - -## 0.25.6 - -- Show upstream Hyper client generation, request budget progress, generation age and rotation count in the live **Backends** diagnostics table, sourced from the same `/api/events` status snapshots as the existing backend metrics. -- Retain the configurable backend client generation recycling and HTTP/1 buffer ceiling introduced in 0.25.5. -- Keep SQLite schema 8 and the existing configuration format; no migration is required from 0.25.5. - -## 0.25.5 - -- Bound long-lived backend Hyper client generations with configurable request-count and age recycling, add a configurable upstream HTTP/1 read-buffer ceiling, and expose per-backend rotation diagnostics without reloading vhosts or purging cache. -- Expand Prometheus/Grafana coverage with bounded Traffic analytics for countries, fixed User-Agent families, methods, status classes, cache outcomes, routes and serving source; add explicit built-in Web Server inventory/source metrics while keeping raw IPs, paths and full User-Agent values out of Prometheus labels. -- Add an About dialog in the dashboard top bar with project author, open-source notice and source repository link; rename the top-bar Docs action to Documentation. -- Replace the always-expanded mobile dashboard navigation with a compact on-demand Menu control that closes after section selection, Escape or an outside click; desktop navigation is unchanged. -- Make the Revision History modal use the same square-corner dialog styling as the rest of the dashboard and remove its duplicated modal/backdrop CSS. -- For vhosts using verified Cloudflare client-IP mode, use trusted `CF-IPCountry` for country analytics/logging and country rules, falling back to local GeoIP when the header is absent or invalid. -- Keep SQLite schema 8 and the existing configuration format; no database migration is required from 0.25.4. - -## 0.25.4 - -- Add restore from retained configuration history. An older retained document is validated and saved as a new revision instead of rewinding or overwriting history. -- Protect restore with the current-revision compare-and-swap, administrator session and CSRF controls, and record the source/new revision in administration audit. -- Keep SQLite schema 8 and the 512-revision retention limit; no database migration is required from 0.25.3. - -## 0.25.3 - -- Add vhost and Custom-location HTTP method allow/block policies, with location-level reusable ACL profiles, inline IP ACLs and country access rules enforced before origin work. -- Forward a per-request SHA-256 `X-Request-ID` to backends and keep the same identifier in diagnostics and access logs for end-to-end correlation. -- Retain the newest 512 manual-configuration revisions and add a calendar-style dashboard history modal with revision metadata and path-level change previews. -- Migrate the embedded SQLite schema from 7 to 8 for configuration-history retention while preserving existing configuration automatically. +## 0.31.2 — updated runtime baseline -## 0.25.2 - -- Allow password-encrypted full backups while RustProxy is running by using a consistent SQLite snapshot and a private staged copy of durable filesystem state; restore remains offline and atomic. -- Preserve validated internal relative symlinks in backup/restore, including Certbot `live/` links into `archive/`, while rejecting absolute, escaping and dangling links. Version-1 backup archives remain restorable. -- Recover a restored/resumed authenticated dashboard tab from a transient `auth.invalid_origin` response with one bounded same-origin reload; server-side Host, Origin, request-marker and CSRF validation remain unchanged. - -## 0.25.1 - -- Reorganize **Traffic** into compact Overview, Clients, Requests and Delivery sub-tabs and visualize all bounded analytics dimensions with donut or horizontal bar charts instead of requiring a long page scroll. - -## 0.25.0 - -- Add reusable Web Services with document root, ordered index files and optional directory listing in the new **Web Server** dashboard section. -- Allow vhosts and custom locations to choose **Reverse Proxy** or **Web Server**, reuse one Web Service across multiple vhosts and mix static/proxy routing in one vhost. -- Add static `GET`/`HEAD`, streaming responses, MIME/length headers, index/redirect/listing behavior, HTTP validators and single-range requests (`206`/`416`). -- Enforce document-root confinement, reject ambiguous/encoded traversal and prevent symlink escape on Unix by refusing to follow symlinks. -- Reuse the existing vhost ACL/authentication/header/error/TLS/H1/H2/H3 pipeline without adding a second router, error-page subsystem or object cache. -- Add an authenticated **Docs Reader** alongside the API/OpenAPI view, with a canonical documentation index, 0.25 audit and separate TODO. -- Align Web Server directory listings and Web Service cards with the RustProxy panel UI, make index files optional for listing-only services, clarify Web Server vhosts in backend summaries and show a dedicated validation error when Reverse Proxy has no backend. -- Fix Web Service card rendering and authenticated-panel refresh behavior so a UI startup error cannot be mistaken for an expired login session; also render an already-loaded configuration when the Web Server UI module registers, removing intermittent blank Web Service lists after fast page refreshes. -- Fix Web Server vhost dashboards so status, request/active/error counters, EWMA latency and traffic are populated from vhost-wide metrics even when no reverse-proxy backend pool exists. -- Fix repeated directory-listing requests so refreshing a directory does not reuse an exhausted directory offset; keep per-directory index lookup ahead of listing fallback. -- Add Web Service MIME overrides, a configurable fallback MIME type and an optional default `Cache-Control` policy while retaining vhost/location response-header overrides and the OS page cache instead of a second static object cache. -- Expand Web Service configuration into General, Routing, MIME, Cache & headers, Delivery, PHP-FPM and Security tabs; add `try_files`, internal/redirect rewrites, ordered extension/MIME cache rules, fixed response headers, precompressed sidecars and forced-download extensions. -- Add bounded FastCGI/PHP-FPM execution over local Unix sockets or loopback TCP with configurable executable extensions, timeouts and concurrency; support `index.php`/front-controller routing and never expose PHP source when PHP-FPM is unavailable. Extend PHP-FPM configuration with explicit TCP/Unix syntax help, Authorization forwarding control, a streaming request-body limit, bounded response headers/stderr, trusted custom FastCGI parameters and validated `PHP_VALUE` overrides. -- Add bounded `.htaccess` migration compatibility for common RewriteRule/RewriteCond front controllers, DirectoryIndex/Indexes, AddType, response Header, Expires and PHP AddHandler directives, with recursive compile-time scanning, strict mode, fail-closed handling of unsupported access/handler directives and protected `.ht*` files. -- Add one-shot Web Service presets for Static, SPA, PHP, WordPress, Drupal, Laravel, Symfony, Joomla, PrestaShop and Downloads plus optional per-service hardening that blocks high-confidence exploit probes/sensitive files and strips common implementation fingerprint headers. -- Add an explicit Web Service symlink policy: deny by default, with a Linux-only static-files mode that follows links only when the opened target remains inside the configured document root. -- Split **Settings -> Logging** into Outputs, JSON format and Error log subsections inside the same card/sub-tab layout as Cache, and allow `route`, `cache_effective_enabled` and `transform_effective_enabled` in custom JSON log mappings. -- Add `user_agent` and optional `country` structured access-log fields. Country capture is disabled by default so GeoIP lookup is not added to the normal request path unless explicitly enabled or already required by country access/rate-limit policy. -- Add a bounded per-vhost **Traffic** dashboard with top countries, User-Agents, effective client IPs, paths, routes, methods, status classes and cache outcomes plus aggregate request/error/byte/latency counters; add donut charts for countries, User-Agents, client IPs, methods, status classes and cache outcomes. -- Use the standard generic/customizable `403` error-page pipeline when a Web Server directory has no matching index and directory listing is disabled instead of returning an implementation-specific diagnostic string. -- Reduce sustained-traffic RSS retention without configuration reloads by periodically releasing already-free glibc pages and automatically trimming only sufficiently idle HOT cache entries above a configurable target waterline. -- Harden production defaults and FastCGI boundaries: keep the one-line/bootstrap reference admin listener on loopback unless plain remote HTTP is explicitly opted into, never pass client `Proxy` as CGI `HTTP_PROXY`, reject informational FastCGI statuses, discard application-supplied response framing/hop-by-hop headers, reject symlinked `.htaccess` control files, use owner-only Debian service umask defaults, and prevent local `.env` files from entering source release archives. -- Fix CMS preset selection so choosing a non-Custom Web Service preset immediately fills and saves its generated index, `try_files`, PHP, delivery and cache settings without requiring a separate Apply click. +- Updated runtime, TLS, database, Swagger and monitoring dependencies. +- Raised the minimum Rust toolchain requirement to **1.99** for source builds, while preserving configuration and data-format compatibility. -## 0.24.10 +## 0.31.1 — panel loading reliability -- Persist Error File enabled/path/format through the existing revisioned Logging configuration while retaining compatibility with older logging API payloads. -- Keep response-time history permanently visible and add an accessible explanation of the cumulative mean metric. +- Improved dashboard startup after mobile reloads and navigation through reverse proxies. +- Added versioned dashboard JavaScript/CSS assets to reduce problems caused by mixed browser/intermediary caches. -## 0.24.9 +## 0.31.0 — selective cache purge and live monitoring -- Add a central encrypted Secret Store and migrate ACME DNS-provider credentials away from persistent plaintext storage. -- Add password-encrypted full backup/restore with authenticated encryption, integrity validation, staging and rollback-safe activation. -- Add an optional ERROR-only JSON/RAW file sink with bounded asynchronous writes, secret redaction and logrotate integration. -- Harden the built-in release checker on FreeBSD/OPNsense and other Unix platforms with system CA bundle support and bounded redirects. +- Added optional, IP/CIDR-restricted public HTTP **`PURGE`** (exact URL) and **`BAN`** (URL prefix) controls. An empty allowlist keeps the feature off; requests remain subject to relevant validation and audit rules. +- Tightened Vhost/scheme isolation for cache invalidation and bounded background cache-maintenance work. +- Added configurable compression for administration HTML and assets, with gzip, Brotli and Zstd negotiation where supported. +- Added per-administrator live-refresh controls for Overview, Traffic/Cache and System information, including the option to stop certain automatic refreshes. +- Improved panel startup and separated compact live status updates from more expensive system information requests, preserving full status API compatibility. -## 0.24.8 +## 0.30.1 — dashboard diagnostics -- Expand Cache RAM diagnostics with HOT usage/limit, transient cache RAM breakdown and COLD storage occupancy. -- Move the long RSS/cache accounting explanation behind an accessible information control while keeping COLD disk usage separate from process RAM. +- Corrected HTTP/WebSocket protocol legend colors so charts follow the selected palette. +- Moved Python/Certbot and system diagnostics into **Settings → System information** with live OS/runtime details. -## 0.24.7 +## 0.30.0 — backend health configuration -- Make transform temporary-memory and concurrency ceilings configurable, preserving safe defaults for existing installations. -- Add persisted RAM saver, Recommended, Performance and Uber performance transform resource presets without changing transformation semantics. -- Add safety warnings and live resource accounting for transform RAM/slots, cache RAM and COLD disk occupancy. -- Include transformation representation/eligibility settings in cache compatibility identity while excluding resource-only capacity settings. +- Moved per-backend health-check editing into the Vhost **Health** tab with a selector for monitored backends. +- Kept independent backend health overrides when switching between members or temporarily disabling checks. +- Improved historical log processing and resource usage without changing saved Vhost, database or COLD cache formats. -## 0.24.6 +## 0.29.7 — PHP-FPM safety, health checks and custom errors -- Persist selected HOT/COLD cache preset identities and restore them in Settings. -- Add a Recommended COLD policy preset while preserving operator-selected disk path and capacity. -- Bound transformation buffers/decompression and background cache work to prevent unbounded memory/task growth. -- Bound stale revalidation, proactive refresh, COLD cleanup and delete fallback work. +- Added independent per-backend health-check enable switches and optional probe overrides, including the ability to disable active/passive checks for a selected backend. +- Added optional **HIT-only cache diagnostic headers** (`expose_only_hits`) at global or Vhost scope; non-HIT outcomes can remain unadvertised. +- Added warnings and practical guidance for PHP-FPM services sharing a pool with conflicting per-request PHP settings. Separate pools are recommended where settings differ. +- Expanded PHP-FPM profile help, `PATH_INFO` guidance and examples for applications such as WordPress and Roundcube. +- Added configurable application-error interception (`pages.intercept_statuses`) to serve internal, Vhost or global error pages while retaining the appropriate status code. Disabled for existing configurations unless enabled. -## 0.24.5 +## 0.29.6 — compressed PHP responses -- Fix empty, length-unknown HTTP/3 request forwarding to HTTP/1.1 origins while keeping non-empty bodies streaming. -- Harden HOT/COLD cache memory admission with allocation-aware HOT limits and separate transient/COLD-queue RAM budgets. -- Expose HOT logical/allocated/transient memory and pressure counters. -- Make global cache disable authoritative over per-vhost settings and remove the artificial 1 GiB HOT/object ceiling. -- Add host/cgroup-aware cache memory presets and safety/headroom guidance in Settings. +- Fixed truncated PHP-FPM responses when serving gzip, Brotli, Zstd or deflate content in Web Server mode. +- Improved error-file routing for PHP-FPM diagnostic output and service warnings, with fallback to console logs when writing fails. -## 0.24.4 +## 0.29.5 — PHP PATH_INFO and Roundcube -- Reduce Linux RSS amplification by disabling Transparent Huge Pages only for the RustProxy process, without changing the host-wide THP policy. - -## 0.24.3 - -- Add per-certificate reload for manual/file-backed certificate IDs and scoped Certificate API authorization. -- Allow multiple Certificate API keys with comments, individual revocation and revoke-all while storing only token hashes. -- Add Linux allocator tuning and reduce avoidable transformation allocations to improve long-running RSS behavior. -- Move HTTP/2 lifecycle tuning to listeners, align HTTP/3 tuning, and keep compatibility fallback for older configuration. -- Improve dashboard process/platform diagnostics and remove stale platform-specific source from release output. +- Fixed Roundcube `static.php/...` routes being incorrectly sent through the front-controller fallback, which could return HTML instead of CSS or JavaScript. +- Applied Web Server security and `.htaccess` restrictions to the resolved PHP script path, including when optional `PATH_INFO` is used. -## 0.24.2 - -- Version-only release bump from 0.24.1; no functional, configuration or protocol changes. - -## 0.24.1 - -- Restore portable network-interface discovery/RX/TX counters and add broader portable system/process diagnostics. -- Expose authenticated runtime metadata for platform, statistics source and resolved data/database paths. -- Make local CLI data-directory discovery support Linux and BSD/OPNsense conventions. -- Move Logging into **Settings -> Logging** while retaining legacy `/logging` navigation compatibility. - -## 0.24.0 - -- Replace abrupt downstream H2 idle termination with a GOAWAY-based graceful shutdown that rechecks current activity and waits for accepted streams. -- Add independent H2 idle timeout, maximum connection lifetime and drain timeout settings; lifetime may be disabled with `0`. -- Bound H2 draining so stalled streams cannot keep a retiring connection open indefinitely. -- Keep H2 Extended CONNECT/WebSocket streams under the same active-stream accounting and graceful drain path. -- Extract system statistics into platform backends while preserving Linux metric names, units and behavior; add native FreeBSD/OpenBSD/NetBSD sampling where available and explicit nulls otherwise. -- Add runtime platform to Proxy Process and Prometheus platform info. -- Expose H2 lifecycle controls through Settings and the administration API. -- Reorganize **Settings -> HTTP connections** into HTTP/2 and HTTP/3 sub-tabs; move listener-scoped H3/QUIC enablement and limits out of the Listeners editor while preserving restart semantics and the existing listener configuration model. - -## 0.23.3 - -- Make external certificate deployment convergent: create missing manual certificate ids, rotate existing manual ids, no-op identical RustProxy-owned generations, and keep managed ACME ids protected from external ownership. -- Make the OPNsense os-acme-client hook reconcile on every run so RustProxy remains the source of truth and deleted/drifted external certificate entries self-heal. -- Add per-vhost **Enable WebSocket legacy support** for older appliance/embedded WebSocket implementations. -- Force legacy-mode WebSocket upstream handshakes to HTTP/1.1 and force the WebSocket `Host` to the configured backend authority and align `Origin` to the matching backend scheme + authority. -- Tolerate legacy `101 Switching Protocols` response framing while keeping `Sec-WebSocket-Accept` and subprotocol validation strict. - -## 0.23.2 - -- Add selectable 60/120/300/600-sample history windows to the Cache and Transformation live charts, with bounded per-workspace browser persistence. -- Fix `rust-proxy --livelogs` on HTTPS administration listeners by detecting the HTTP 308 redirect and reconnecting over TLS instead of looping. -- Pin local CLI HTTPS to leaf certificates present in the private `data-dir/certs` directory and reuse the HTTP/HTTPS-aware client for config and certificate CLI API calls. +## 0.29.4 — reusable PHP-FPM profiles -## 0.23.1 +- Added named shared PHP-FPM profiles, manageable from the Web Server toolbar or a Web Service editor; services can attach/detach a profile without losing their local configuration. +- Extended Vhost cache, transformation and response policies to appropriate local Web Server file/PHP responses. +- Corrected FastCGI `HTTP_HOST` forwarding for valid HTTP/2 and HTTP/3 requests, supporting applications such as WordPress. -- Add default-collapsed, full-width live activity charts to the Cache and Transform workspaces. -- Plot Cache HIT/MISS/BYPASS rates for the selected scope using deltas from the existing cache runtime counters. -- Plot Transform applied/cache-HIT/skipped/error rates for the selected scope using the existing transform metrics endpoint. -- Reuse the administration dashboard SVG/grid/legend visual language, keep the existing 3-second live refresh, and add English/Polish language-pack labels. +## 0.29.3 — internal error fallback -## 0.23.0 +- Corrected internal error fallback for empty HTTP/2 and HTTP/3 GET/HEAD requests. +- Preserved the final status and response body of an internal fallback location, even when it returns its own error page. +- Improved warnings for missing internal fallback destinations. -- Bound inactive inbound HTTP/2 connections to five minutes so idle TLS/H2 sessions cannot accumulate indefinitely toward the process connection limit; export `rust_proxy_http2_active_connections` for direct diagnosis. -- Account hot-cache response-body backing allocation capacity rather than only visible body length, preventing `BytesMut` growth slack from bypassing the configured RAM budget and under-reporting resident cache memory. -- Add a scoped external Certificate API credential managed from a new **Settings → Certificate API** tab. RustProxy stores only the token hash plus a non-secret Key ID; the cleartext token is returned only on rotation. -- Allow the scoped Key ID + Bearer Token only on `POST /api/certificates/deploy` and `POST /api/certificates/reload`, require HTTPS for non-loopback token use, track last use, support immediate revoke, and retain normal administrator-session/CSRF access as an alternative. -- Keep production certificate operations available directly from the installed binary (`rust-proxy tls deploy` / `rust-proxy tls reload`) without requiring `dev.sh`. -- Add `rust-proxy --livelogs`: an interactive varnishlog-style all-vhost live access stream with structured filters (`host`, `vhost`, `backend`, `route`, `client`, `method`, `transport`, `cache`, status classes, path prefix, errors and slow requests), pause/resume, per-request JSON inspection and detailed error/WebSocket/H3/cache fields. -- Back live logs with a bounded non-blocking broadcast stream and authenticated SSE endpoint; slow consumers receive an explicit `gap` event with the dropped-entry count instead of silent loss. -- Additional historical details for this release are retained in the technical archive. +## 0.29.2 — managed logs and PHP monitoring -## 0.22.3 +- Added managed log rotation for Debian installation/update, including daily rotation and compressed history. +- Fixed Web Server routes incorrectly returning 503 when backend availability flags were unrelated to local file/PHP serving. +- Added validated `PHP_ADMIN_VALUE` support for trusted PHP-FPM use, and expanded Web Service Prometheus status, latency and concurrency measurements. -- Add downstream HTTP/1.1 `Upgrade: h2c` on listeners that explicitly enable cleartext HTTP/2, preserving the existing prior-knowledge path and rejecting malformed/body-bearing upgrade attempts before protocol switch. -- Add authenticated `POST /api/certificates/reload` and `tls reload` to re-read configured public certificate/key files and hot-publish a fresh TLS/H2/H3 snapshot without changing the desired configuration revision. -- Add `tls deploy --id ... --cert ... --key ...` and authenticated `POST /api/certificates/deploy` for externally issued local certificates such as Certbot. The CLI performs bounded local reads while the running service owns pair/SAN validation, immutable generation staging, revision-CAS publication and rollback on failure, avoiding root-owned staged PEMs when Certbot hooks run as root. -- Keep all vhosts assigned to one manual certificate id synchronized during external rotation; reject RustProxy-managed ACME ids to avoid dual renewal ownership and report the administration-TLS restart caveat explicitly. -- Add Certificates-panel reload controls and certificate audit events. +## 0.29.1 — internal locations, range delivery and PHP scripts -## 0.22.2 +- Added internal-only named Custom locations for controlled error fallback, with selectable interception of common 4xx/5xx statuses and validation against routing loops. +- Added multipart HTTP `Range` delivery for up to **16 byte ranges** per request. +- Added optional PHP-FPM `PATH_INFO`, securely separating an existing script from its trailing request path. +- Preserved existing configuration and persistent cache compatibility. -- Add bounded replay-safe backend failover retry for body-free GET/HEAD/OPTIONS requests, including retry policy controls, passive-health integration, logs and per-backend retry counters. -- Correct explicit HTTP/2 upstream requests and h2c prior-knowledge forwarding. -- Add per-backend request-concurrency limits with bounded capacity waiting, `503` rejection and capacity/queue-timeout metrics. This limits full proxy exchanges, not TCP socket count. -- Add detailed upstream failure classes, last failure detail/timestamp, and backend health ejection/recovery timestamps to runtime diagnostics and Prometheus. -- Expand the backend administration table with concurrency capacity, last failure and last recovery columns. -- Parse public X.509 leaf metadata for issuer, SANs, validity, remaining days and expiry warning state, and surface ACME renewal status/last attempt/error in certificate inventory. +## 0.29.0 — canonical hostnames and redirect rules -## 0.22.1 +- Added easier management of domain aliases and **www/non-www canonical redirects**, including one-click permanent canonicalization and retention of the original request target. +- Added a Custom location **Redirect** action supporting permanent/temporary responses, full HTTP(S) destinations, optional capture-based rewriting and query preservation. +- Improved default HTTP health-check Host selection using the Vhost's first exact DNS name when no explicit override is set. +- Added narrowly scoped **force-cache targets** for configured URLs while continuing to respect response-side privacy exclusions such as `private`, `no-store` and `Set-Cookie`. +- Expanded Stored Logs to search permitted compressed log rotations, and improved maintenance/error-page and nested URL handling. -- Vhost UX: replaced separate WebSocket/H2 RFC 8441/H3 RFC 9220 checkboxes with one transport selector; H2/H3 modes keep HTTP/1.1 Upgrade as an explicit fallback and unavailable transports are disabled from listener capabilities. -- UI polish: replaced the overview TLS listener tile with compact HTTP protocol and WebSocket protocol mini charts; Target and Cache key/tags cells remain copyable with full values. -- Keep the overview metrics in one shared stats grid so the WebSocket/H2/H3/TLS row uses exactly the same card geometry as the primary metrics, without a transport-specific dashboard layout. Make Cache **Target** and **Cache key / tags** cells copy their full untruncated value on click/keyboard activation. -- Polish the 0.22.1 administration UI: move **Active tunnels** into the WebSocket dashboard card, flatten the H1/H2/H3 tunnel breakdown to match the standard transport-card styling, and align Cache modal close actions with the vhost modal header pattern. -- Move selective cache PURGE/BAN and the active BAN list into dedicated Cache modals, keep their launch actions beside **Purge selected scope**, and use null-safe inline operation feedback consistent with login errors. -- Add opt-in RFC 8441 WebSocket Extended CONNECT for HTTP/2, with per-vhost controls beside RFC 9220, listener-scoped SETTINGS advertisement, H2-to-H1 bridging, explicit H2 upstream forwarding, cancellation/idle timeout and transport-specific metrics. -- Add opt-in cleartext HTTP/2 prior-knowledge (`h2c`) listeners. Legacy HTTP/1.1 `Upgrade: h2c` remains deliberately unsupported because the current Hyper integration cannot safely reconstruct the required HTTP/2 stream 1 bootstrap. -- Add stable active-tunnel IDs, authenticated tunnel inventory/kill API, operator-supplied close reasons, audit records, RX/TX/duration metadata and an administration modal. -- Additional historical details for this release are retained in the technical archive. +## 0.28.x — external certificates and GeoIP + +| Version | Technical changes | +|---|---| +| **0.28.2** | Safer external PEM/key access, including POSIX ACLs and Certbot symlinks; certificate API keys now default to limited reload-only permissions and explicit certificate grants. | +| **0.28.1** | Support externally managed absolute certificate/key paths and hot reload after Certbot renewal; keep external key files outside RustProxy backups. | +| **0.28.0** | Add manual GeoIP downloads, flexible CSV/MMDB source URLs and checksum validation; improve active listener/protocol display and cache Via controls. | -## 0.22.0 - -- Add per-cache-key request collapsing for foreground MISS/fill traffic with bounded wait, configurable timeout fallback, cancellation-safe leader release and collapse metrics. -- Add single `Range: bytes=...` handling from complete cached objects, including `206`, `416`, `Content-Range`, `Accept-Ranges` and `If-Range`; multipart/unsupported ranges remain origin pass-through and are not cached as separate objects. -- Extend stale-if-error with configurable transport failure classes and an explicit 5xx status allowlist while preserving the previous default behavior. -- Add bounded proactive cache refresh with pre-expiry scheduling, deterministic sampling, concurrency limits and reuse of the existing validator revalidation path. -- Add selective cache PURGE by exact URL, prefix, tag, regex or cache key, including query/Vary handling, RAM + Cold Cache invalidation and a publication barrier preventing pre-PURGE fills from republishing stale data. -- Add lazy BAN rules with lookup-time enforcement, bounded rule count/TTL, incremental garbage collection, rule deletion and metrics. -- Add authenticated administration API and Cache panel controls for PURGE/BAN, broad-operation confirmation, per-object Purge URL actions, BAN listing/removal and audit entries. - -## 0.21.0 - -- Fix browser refresh on the dedicated **Transform** route by serving the dashboard document at `/transform`. -- Add a dedicated Transform administration workspace with global/per-vhost runtime counters for applied/skipped/error transformations, transformed cache HITs, byte savings, reduction, transform time, kind breakdown and bounded skip reasons. -- Add a transformed-response series to **Live traffic** (fresh transformations plus transformed cache HITs) and render **Core state** uptime in compact human-readable units. -- Rename the proxy-owned backend timing diagnostic to `X-RustProxy-Backend-Time`; the legacy lookalike `X-Rust-Proxy-Backend-Time` is stripped but is no longer emitted. -- Persist transformed-representation metadata through hot/Cold Cache, mark transformed objects in the Cache dashboard, and append `transformed` to standardized RFC 9211 `Cache-Status` diagnostics; origin `X-Cache-Status` lookalikes are stripped rather than emitted. -- Shard Cold Cache storage and add bounded asynchronous garbage collection plus selective manual cleanup. -- Add RFC 5861 stale windows, request `max-stale`, age-aware request `max-age=N`, and correct `must-revalidate` / `proxy-revalidate` behavior while preserving `s-maxage` priority. -- Harden transformed cache fills so transformed Content-Length is authoritative, incomplete streams cannot publish partial objects, and canonical transformed identity representations do not fragment on origin `Vary: Accept-Encoding`. -- Additional historical details for this release are retained in the technical archive. - -## 0.20.0 - -- Rebuild **Certificates** into Installed / Renewal / Generate tabs using the same accessible keyboard tab pattern as Settings. -- Keep certificate issuance, PEM import and existing-file registration inline in the Generate tab; successful imports and edits return to Installed. -- Add explicit ACME operation state cards and include the exact private Certbot log directory in failed issuance/renewal messages. -- Expose per-managed-certificate Certbot log directories from the authenticated ACME status endpoint so persisted renewal failures remain actionable after refresh/restart. -- Rework renewal entries into readable cards with provider/environment, last check, next check, expiry and grouped actions. - -## 0.19.4 - -- Preserve backend traffic display semantics: idle renders as `0.0 KiB/s`, and positive rates below 0.1 KiB/s render as `<0.1 KiB/s` in both backend rows and aggregate vhost traffic. -- Add a presentation filter to **Cache -> Domains and effectiveness** with All / Cache enabled / Cache disabled choices, using each vhost's resolved cache settings while leaving aggregate statistics unchanged. - -## 0.19.3 - -- Simplify the dashboard footer to API latency and dropped-log count, and add a fail-soft built-in release checker using embedded `UPDATE_CHECK.toml` defaults. The checker reads the official Cargo manifest immediately and then hourly, exposes a newer-version link in the footer, and never performs automatic upgrades. -- Allow runtime release-check overrides through `--update-repository-url`, `--update-version-url` and `--update-check-interval-secs` (plus matching `RUST_PROXY_*` environment variables); interval `0` disables checks. -- Add an account-scoped light/dark administration theme stored in the existing SQLite `admin_preferences` document; dark remains the default and the last authenticated choice is cached locally only to avoid login/dashboard first-paint theme flicker. -- Additional historical details for this release are retained in the technical archive. - -## 0.19.2 - -- Restore the vhost local-listener risk control for administration backends when the administration socket is startup-locked or otherwise reported only by live status; treat `localhost`, `127.0.0.0/8` and `::1` as equivalent loopback forms for the same administration port. -- Make **Discard changes** reload the persisted vhost being edited instead of replacing the editor with a new blank/default vhost. -- Add descriptive HTTP reason phrases to the default-response status selectors and headings. -- Add a temporary, continuously updated 4096-entry Live-log buffer that resets when the log window closes. -- Stop backend traffic EWMA tails on an idle sampling window, show idle consistently as `0.0 KiB/s`, and render positive sub-0.1 KiB/s traffic as `<0.1 KiB/s` instead of a rounded zero. -- Additional historical details for this release are retained in the technical archive. - -## 0.19.1 - -- Add frontend scheme to the primary cache hash so otherwise identical HTTP and HTTPS requests cannot reuse one object. -- Retain 4096 completed access-log entries while keeping the 256-entry live window; add one-shot on-demand history loading and document the API limit. -- Render backend traffic with KiB/s as the minimum unit, simplify the Live Logs column picker, align generic checkboxes and move Docker-vhost Advanced switches beside Backend protocol on desktop; remove the redundant alias help line. - -## 0.19.0 - -- Persist per-administrator Live Logs column visibility in SQLite schema v4 without changing the revisioned proxy configuration; JSON export remains complete regardless of visible columns. -- Add authenticated `GET/PUT /api/preferences` endpoints for dashboard-only account preferences, protected by the existing session/CSRF control-plane guards. -- Retain the full disabled-verification legacy X.509 fix and expanded upstream connector diagnostics introduced during the 0.18.8 follow-up. - -## 0.18.8 - -- Make backend `tls_verify=false` a complete certificate-authentication bypass instead of delegating TLS CertificateVerify to webpki. This restores compatibility with legacy appliance certificates such as X.509 v1 while keeping TLS encryption and all backend address/DNS/loop protections in force; `tls_verify=true` is unchanged. -- Expand upstream connection diagnostics with the full nested Hyper/rustls/io error chain, backend/TLS context, 2 KiB UTF-8-safe detail storage and warning-level journal entries for connect failures. -- Replace Docker-vhost alias comma input with hostname-style chips for add/remove/paste workflows. -- Add per-Docker-vhost HTTP-to-HTTPS redirect, WebSocket and cache controls; new compatible assignments default HTTP-to-HTTPS redirect on while legacy assignments retain their prior redirect-off behavior until explicitly saved. -- Add a Docker-agent default ACL-profile selector. Generated Docker vhosts inherit it unless the application selects its own ACL profile; existing per-app inline ACL configuration is unchanged. -- Additional historical details for this release are retained in the technical archive. - -## 0.18.7 - -- Collapse **Response time history** by default on the Overview dashboard while keeping live sampling active. - -## 0.18.6 - -- Add bounded per-interface RX/TX diagnostics from `/proc/net/dev` to the administration status payload and render separate live interface histories without adding per-interface Prometheus labels. -- Clarify compression negotiation and make dashboard counters track the codec actually selected per response. - -## 0.18.5 - -- Add an explicit HTTP/1.1 request counter and render total + HTTP/1.1 + HTTP/2 + HTTP/3 request-rate series on the Overview chart. -- Add an Open app action to published Docker vhosts, using the assigned frontend listener and expanded proxied hostname. -- Label Docker-generated pools in the Backends view with their primary proxied domain. -- Remove the global Open proxy shortcut from the page header. -- Make Compact density materially tighter than Dense across cards, controls, tables, Docker rows and charts. -- Default vhost/Docker proxy timeout controls to explicit 10/120/60/60-second connect/header/read/send limits instead of blank inherited values. -- Additional historical details for this release are retained in the technical archive. - -## 0.18.4 - -- Default backend health checks to TCP for newly created or otherwise defaulted ordinary and Docker vhosts; explicit existing HTTP/off health modes are preserved. -- Replace the dense Docker-vhost service editor with summary-first rows. Publish state, service identity, backend runtime health and Hide/Show stay visible; routing, ACL, timeouts and health tuning are expanded only for the selected service. -- Collapse secondary Docker-agent inventory metadata behind Agent details while keeping host, service count, last observation and base domain immediately visible. -- Rework Settings -> Security into focused account/session and host-policy cards with on-demand password and policy editors. -- Move Administration audit into a dedicated modal and stop fetching audit entries merely by opening the Security settings tab. - -## 0.18.3 - -- Add Docker-vhost search plus persistent per-browser Hide/Show Hidden view controls, and allow each Docker agent to select a configured frontend certificate inherited by its generated vhosts. -- Keep Docker inventory polling driven by each agent's configured 5..3600 second interval; the 5-second dashboard refresh reads cached observations only. -- Move redirect HTTP/2 and HTTP/3 toggles below listener selection and keep Cache `Updated` + `Live` together at the right side of the toolbar. -- Add an Open-in-browser action beside each vhost Enabled state; wildcard-only hosts open the wildcard base domain, while mixed host lists prefer the first non-wildcard name. -- Restore the documented HTTP/1.1 backend default for newly created ordinary and Docker vhosts: preserve the public `Host`; existing explicit backend settings are not rewritten. -- Write HTTP/1 upstream header names in conventional title case for compatibility with legacy/custom origins that incorrectly treat header names as case-sensitive; HTTP/2 is unaffected. -- Additional historical details for this release are retained in the technical archive. - -## 0.18.2 - -- Secure Docker-agent transport by default with a persistent self-signed ECDSA certificate and SHA-256 certificate pinning in RustProxy; public-CA HTTPS remains available without a pin and plain HTTP is compatibility-only. -- Add certificate lifecycle handling, hardened systemd state storage and installer fingerprint output for the standalone Docker agent. -- Enable per-Docker-vhost HTTP/TCP backend health checks using the same bounded backend-pool health machinery as ordinary vhosts, with live checking/up/down, latency, probe, request, active and error diagnostics in Docker vhosts. -- Rework Docker-agent and Docker-vhost UX around connection security, last observation, detected addresses, route controls and collapsible health-check tuning while retaining per-agent domains, aliases and per-service IP selection. - -## 0.18.1 - -- Extend the Docker agent inventory with all usable host addresses (interfaces, routed address, public lookup and optional advertised address) and preserve every published binding per service. -- Add per-service Docker backend-IP selection, per-agent default backend IP, arbitrary FQDNs and multiple aliases while retaining short-name expansion below each agent's own base domain. -- Support many Docker agents with independent domains such as `docker1.lab.local` and `docker2.lab.local`, plus operator-controlled custom domains. -- Remove all special `X-Rust-Proxy-Revision` response-header behavior; the name is no longer inserted, stripped, protected or accepted as a legacy diagnostic setting. -- Keep cache-Live placement, production password recovery and Settings navigation improvements from 0.18.0. - -## 0.18.0 - -- Add a standalone Go Docker discovery agent with a hardened systemd unit, token authentication, published TCP-port inventory and host/public-IP detection. -- Add per-agent Docker configuration, in-memory observations and dynamic Docker vhosts with operator-controlled service/subdomain assignments. -- Add Docker vhost/admin UI and OpenAPI endpoints for cached observations and explicit refresh. -- Move the cache Live toggle to the action-toolbar edge. -- Remove configurable `X-Rust-Proxy-Revision` response exposure and always strip the header. -- Use the installed `rust-proxy admin reset-password` command in production recovery guidance. - -## 0.17.0 - -- Add Debian packaging assets, a hardened systemd unit, `rustproxy` service user/state/log directories, three-day maximum-gzip log rotation, local `.deb` build tooling and signed APT-repository generation suitable for `rustproxy.linuxiarz.pl`. -- Expand Prometheus coverage across all current bounded process/OS diagnostics plus GeoIP, certificate renewal, backend health-probe detail, stream, logging and frontend inventory metrics; add explicit token-free scraping for trusted actual-peer CIDRs. -- Add a Grafana operations dashboard under `grafana/`. -- Add dedicated listener/certificate-aware local-response / 404 hosts with built-in placeholder, empty/status-only and custom-HTML modes. -- Keep the default `RustProxy is running` placeholder uncacheable and intentionally free of optional proxy/cache/backend/diagnostic/security/compression headers. -- Reuse vhost-style hostname chips for Redirect source domains. - -## 0.16.1 - -- Remove internal configuration revision numbers from successful save feedback in dashboard editors/manual configuration while retaining revision-based CAS and stale-write protection internally. - -## 0.16.0 - -- Decouple Custom locations from the main vhost backend fallback. Unmatched paths now use the main pool even when custom rules exist; untouched legacy synthetic root routes are normalized automatically. -- Fix same-process public administration login by allowing `rustproxy_session` across only a guarded HTTPS self-admin backend that directly targets the configured administration listener and whose public host is explicitly allowed. -- Make RFC 9220 bootstrap wait for the backend HTTP/1.1 upgrade before downstream H3 success, and extend access logging with transport/WebSocket phase/error/reset/upstream status diagnostics. -- Add cache transient-memory gauges and procfs RSS/data/swap breakdowns to the dashboard and Prometheus. Existing allocator trimming/release behavior is unchanged; no new transient-memory throttle is introduced. -- Reorganize the Vhost editor around clearer backend cards, Authentication subcards and compact protocol controls. New vhosts select standard HTTP :80 plus HTTPS :443 together when available; Redirects reuse the same listener selector; disabled vhost cache hides its subordinate controls and new vhost drafts default cache to off. -- Show the local-listener risk acceptance only when the entered backend URL directly targets the configured administration listener. Add protected opt-in `X-RustProxy-Backend` and `X-RustProxy-Backend-Time` response diagnostics for selected member identity and origin response-header latency. - -## 0.15.1 - -- Fix Custom locations English/Polish translation loading in the administration UI. -- Make visual header rules easier to edit with numbered cards, clear field grouping, empty-state guidance and localized help. -- Add a live per-location behavior preview for matching, action, rewrite, cache, ACL and header rules, and reorganize the inline documentation with common examples. -- Add one live three-series HIT/MISS/BYPASS activity chart to the RAM cache card and CPU/RSS history charts to the Proxy process card. - -## 0.15.0 - -- Expand vhost Custom locations with Nginx-style exact/prefix/`^~`/regex/regex-insensitive matching, upstream path preserve/strip/replace/regex rewrites and backend-free custom responses. -- Replace the location header JSON-only workflow with a visual request/response header builder, keep Advanced JSON as an escape hatch, add in-panel documentation and reorganize the location editor into compact functional sections. -- Add `nagios/` API checks for administration response time, unavailable/checking backends, backend latency and dropped logging/cold-cache work, with reusable protected session cookies and Nagios performance data. -- Move per-vhost `Server-Timing` and verified Cloudflare client-IP diagnostics into the main frontend/details configuration. -- Add compact dashboard history charts for RAM-cache residency, network RX/TX and mean response latency, plus administration API RTT and low-priority queue/drop information in the footer. -- Add independent cold-cache age policy controls: demote by time since last access, by total hot-tier residency, by LRU pressure and on graceful shutdown. -- Additional historical details for this release are retained in the technical archive. - -## 0.14.0 - -- Fix administration reverse-proxy Host/Origin handling: explicit DNS allowlist entries describe the external authority instead of the private admin socket port; direct IP/localhost access remains bound to the actual admin port. -- Trust forwarded administration host/scheme only from loopback or configured trusted proxy peers, use the external scheme for Origin/CSRF checks, and set/clear Secure session cookies consistently behind HTTPS termination. -- Implement RFC 9220 WebSocket over HTTP/3 Extended CONNECT, including `SETTINGS_ENABLE_CONNECT_PROTOCOL`, `:protocol=websocket` validation, HTTP/1.1 backend Upgrade translation, downstream 200 bootstrap, bidirectional H3 DATA transport and FIN/reset cancellation handling. -- Replace the separate `h3`/`h3-quinn` pair with `hpx-h3 2.5.19` and its integrated Quinn feature to expose the RFC 9220 protocol extension through the HTTP/3 stack. - -## 0.13.2 - -- Fix authenticated dashboard refresh/back-forward session restoration so valid cookies are honored on every panel document route instead of briefly rendering the login shell. -- Make managed software updates force a bounded hot-to-cold cache handoff whenever the cold tier is enabled, independent of the ordinary `cold_on_shutdown` preference, and extend the managed systemd stop budget. -- Persist non-secret automatic-certificate-renewal observations in SQLite schema v3, preload them before administration listeners are exposed, and import the older `certs/renewals.json` sidecar once for upgrade continuity. -- Move the raw full-document editor from primary navigation into **Settings -> Manual config** while retaining `/configuration` as a compatibility browser route. - -## 0.13.1 - -- Make administrator browser sessions configurable from 7 to 30 days (30 days by default), replacing the previous short browser lifetime/idle behavior while preserving explicit logout/password/revoke invalidation. -- Refresh authenticated cookies only to the remaining absolute server-side expiry so session persistence cannot become an unbounded sliding session. -- Add per-backend `accept_local_listener_risk` with a dashboard confirmation and validation path for deliberate local-listener targets such as `127.0.0.1:9090`; `allow_private` and special-address protections remain independent. - -## 0.13.0 - -- Add first-class redirect hosts with dedicated dashboard navigation, named listeners/certificates, exact/prefix and scheme matching, URL/host/path targets, 301/302/303/307/308, path/query policies and redirect cache controls. -- Resolve redirect traffic locally before proxy routes while preserving listener ownership and global host ACLs. -- Change omitted backend `tls_verify` to default false and update UI/API/documentation accordingly. -- Compact TLS certificate cards: show DNS SAN coverage as the same policy-style badges used on vhosts, collapse bindings/files/assigned-vhost details, and inspect older/manual public PEM files when persisted coverage metadata is missing. - -## 0.12.1 - -- Add revisioned per-section configuration PUT endpoints and move ordinary dashboard forms to them, retaining full-document updates only where multiple sections must publish atomically. -- Decouple cache compatibility from the SQLite configuration revision so logging, administration, TLS inventory, metrics and other unrelated saves do not purge hot/cold objects. -- Add explicit dashboard subpage routes plus History API navigation so browser refresh preserves the selected panel. -- Add independent cold-tier policies for idle objects, LRU evictions and graceful shutdown, reduce the cold-write body queue, flush remaining hot objects before process exit, and trim released allocator memory on glibc/Linux. -- Expose certificate DNS SubjectAltName coverage separately from active SNI bindings for uploaded and ACME-managed certificates. -- Preserve secure-by-default backend TLS verification when omitted; keep the explicit debug-origin verification bypass and improve corrective validation/help text. - -## 0.12.0 - -- Move raw TCP/UDP stream management from Settings to a dedicated **Streams** main-navigation page directly below Vhosts. -- Add vhost-style searchable stream cards and a modal editor with Details, Listener & upstream, and Limits tabs plus an explicit **Add TCP/UDP listener** action. -- Add fixed-cardinality runtime telemetry per active stream: connections/sessions, completed sessions, active sessions, rejects, errors, RX/TX bytes and UDP packet counters. -- Add per-stream live RX/TX rate charts in the dashboard while keeping desired stream changes visibly pending until the process restarts and the listener is actually active. -- Change named HTTP/HTTPS listener bind-address editing from a textarea to chip-style addresses matching the Vhost hostname interaction, preserving existing multi-address validation. -- Keep stream forwarding as raw Layer 4 with no HTTP vhost, ACL, cache, headers, compression or authentication envelope. -- Additional historical details for this release are retained in the technical archive. - -## 0.11.5 - -- Accept percent-encoded spaces (`%20`) in otherwise unambiguous request paths while preserving rejection of encoded separators, double-decoding hazards, controls and dot-segment ambiguity. -- Avoid the `h3-quinn 0.0.10` `stop_sending()` panic path when an HTTP/3 request body is dropped while a receive read is pending. -- Add raw layer-4 TCP/UDP stream hosts on dedicated listeners with bounded TCP connections / UDP client sessions and restart-bound configuration in the admin panel. -- Add a Debian 13+ source bootstrap that installs build dependencies and Rust, clones/builds the repository and installs the systemd service with the explicitly requested public admin profile. -- Match Varnish built-in request-method behavior for the shared cache: `GET`/`HEAD` participate in lookup while successful POST/PUT/PATCH/DELETE/OPTIONS/TRACE requests no longer trigger an implicit global purge. -- Replace the conservative per-request-header/backend-member cache identity with a Varnish-style Host + URL primary identity, isolated by RustProxy vhost/config scope, and use response `Vary` as the secondary representation selector. -- Additional historical details for this release are retained in the technical archive. - -## 0.11.4 - -- Fix Force SSL frontend validation and messaging: require an actual non-TLS listener for the incoming redirect, prefer HTTPS port 443 when multiple TLS listeners are selected, and reject only ambiguous non-standard HTTPS target ports. -- Add a visual reusable ACL-profile rule editor with add/remove/action/CIDR rows. Keep bulk paste/import as an advanced path, normalize invisible Unicode whitespace and report malformed rules with their source line. -- When a vhost disables cache, strip and suppress RustProxy `X-Cache`, `X-Cache-Hits`, `Cache-Status`, `X-Cache-Reason` and `X-Cache-Resident-Age` diagnostics for that response. -- Render backend pools belonging to disabled vhosts as muted `Disabled` rows in the main dashboard instead of classifying them as unavailable. - -## 0.11.3 - -- Add per-vhost control over forwarding the effective client IP to the application. Disabling it removes `Forwarded`, `X-Forwarded-For` and `X-Real-IP` only on the backend request; ACLs, GeoIP, rate limiting and Tinyauth continue to use the real client identity. -- Add a compact Vhost search/filter for vhost IDs, hostnames/wildcards and backend IDs/URLs. -- Fix JSONL access logging under the hardened systemd unit by provisioning `/var/log/rustproxy` as a writable service log directory while keeping `ProtectSystem=strict`. - -## 0.11.2 - -- Fix administration HTTPS access negotiated over HTTP/2. The admin guard now derives the effective authority from H2/H3 `:authority` (via the request URI) when no legacy `Host` header is present. -- Preserve strict HTTP/1.x Host handling and reject conflicting HTTP/2 `Host` versus `:authority` values to avoid ambiguous authority validation. -- Add per-vhost Force SSL beside frontend selection. Cleartext requests receive a non-cacheable HTTP 308 to the same hostname/path/query on the vhost's unique selected TLS port; ambiguous multi-port TLS selections are rejected. -- Map NPM `ssl_forced` (and migrator `--force-https`) to RustProxy `force_ssl`, retaining HTTP+HTTPS listener bindings when available instead of importing forced hosts as TLS-only. - -## 0.11.1 - -- Fix release update health checks when the administration panel uses persisted HTTPS or a persisted bind different from the install-time CLI address. The check now resolves the active admin listener/scheme from `config.sqlite3` and honors the admin Host allowlist. -- Add a standard-library-only Nginx Proxy Manager SQLite migrator. It reads `proxy_host`, creates RustProxy vhosts/backends, preserves wildcard names/WebSocket/enabled/H2 intent, uses the RustProxy default certificate for imported TLS hosts unless a named certificate is selected, and keeps unsupported NPM policy/custom-config items as explicit review warnings. -- Make NPM migration dry-run by default, emit `npm-migration-plan.json`, detect hostname conflicts, split source records above RustProxy's 16-host limit and use revisioned authenticated API writes only with explicit `--apply`. - -## 0.11.0 - -- Add reusable ACL Profiles with optional IP/CIDR ACL and country allow/block rules. Vhost profile, inline vhost ACL and route ACL enforcement remains cumulative. -- Add an ACL Profiles navigation page and per-vhost profile selection without removing individual vhost policy controls. -- Add chip-style hostname editing and validated leading `*.` wildcard domains. Exact hosts win before the longest matching wildcard on the same listener. -- Add compact browser-session request-rate history charts to vhost cards, with progressive compaction of older samples. -- Add the built-in HTTP 401 page and classify normal WebSocket tunnel EOF/reset/abort/broken-pipe termination after a successful 101 upgrade as `websocket_closed` rather than a body/proxy error. -- Redirect plaintext requests received on the HTTPS administration socket to HTTPS while keeping application content unavailable over plaintext. Permit valid DNS Host values when the HTTPS admin hostname allowlist is empty; explicit allowlists remain strict. -- Additional historical details for this release are retained in the technical archive. - -## 0.10.0 - -- Add an optional persistent cold cache tier under the private data directory. Hot LRU/idle objects demote through a bounded asynchronous queue; fresh cold hits are SHA-256 checked and promoted back into the bounded RAM tier. -- Add hot/cold object, byte, promotion, demotion, cold-hit, eviction/error and queue-drop observability to the Cache API, panel and Prometheus output. -- Add cold-tier path, byte/object budgets and idle demotion settings; purge/config publication invalidates both tiers and startup rebuilds the same-revision cold index. -- Add structured-log Standard, Nginx-style, ECS-style and Minimal presets plus a visual field picker/renamer with JSON preview; preserve advanced raw JSON mappings. -- Reuse the shared application card component for Logging sinks and remove release-specific fieldset border styling. -- Additional historical details for this release are retained in the technical archive. - -## 0.9.0 - -- Redesign the vhost list as readable cards with listener/protocol/TLS/cache identity and live pool health, request, active, error, latency and traffic metrics. -- Add bounded asynchronous structured access-log sinks for JSONL files, Logstash TCP and Loki HTTP(S), with configurable JSON field mapping/static fields and sink delivery/drop/error counters. -- Expand cache object inspection with body bytes, resident bytes and metadata overhead; add optional `X-Cache-Resident-Age`. -- Add administration HTTPS with either the application-managed default certificate or a selected inventory certificate. Persisted non-loopback administration binds are accepted when HTTPS is enabled. -- Keep loopback HTTP as the default and retain the explicit legacy risk gate for plain remote HTTP. Secure session cookies are marked `Secure` on HTTPS administration. -- Add global TLS 1.2/1.3 and rustls cipher-suite policy plus per-vhost TCP TLS overrides. HTTP/3 vhosts must use the global TLS 1.3 policy. -- Additional historical details for this release are retained in the technical archive. - -## 0.8.1 - -- Enable the shared RAM cache by default. -- Replace blanket conditional-request bypass with local conditional HITs for fresh `If-None-Match` / `If-Modified-Since` requests. -- Add validator-based backend revalidation: request `no-cache`, freshness constraints and legacy `Pragma: no-cache` can revalidate a stored representation; backend 304 refreshes metadata and reuses the cached body. -- Keep request `no-store` as a hard bypass. Allow response `Cache-Control: no-cache` with ETag/Last-Modified to be stored only for mandatory validation; `no-store` and `private` still forbid shared storage. -- Retain expired validator-bearing objects for a bounded one-hour revalidation keep window without stale delivery. -- Raise the configurable object ceiling from 8 MiB to 1 GiB, still bounded by the global 1 GiB resident budget. -- Additional historical details for this release are retained in the technical archive. - -## 0.8.0 - -- Global static-cache defaults now include common image, stylesheet/script, font, media and download extensions, with request Cookie ignored for matching assets by default. The global and per-vhost controls are visible in the cache UI; `Set-Cookie`, `private`/`no-store` and `Vary: Cookie` still prevent shared caching. -- HTTP/2 and HTTP/3 are per-vhost frontend opt-outs next to listener selection. Controls are enabled only when a selected named listener supports the protocol; TLS H2 ALPN is narrowed by SNI per vhost. -- Settings -> Listeners uses normal page scrolling instead of an extra nested scrollbar. -- Add per-vhost request throttling with graphical request/window/burst controls, country-specific limits and country block/allow-only policies. Return built-in HTML 429/403 responses while preserving custom error-page overrides. -- Download the PDDL `sapics/ip-location-db` user-country IPv4/IPv6 data during first installation over HTTPS and verify publisher SHA-256 checksums; expose load status without exposing client data. -- Verify the installed GeoIP database against current publisher SHA-256 files on every managed update and download only stale/missing assets before downtime. -- Additional historical details for this release are retained in the technical archive. - -## 0.7.3 - -- Generate the one-time administrator setup token from the release CLI even before the first server start; retain the rule that it is invalid after setup completes. -- Add explicit remote-panel opt-in for `0.0.0.0`/`::`, requiring both `--allow-remote-admin`; keep plain-HTTP risk visible. -- Make Linux/systemd install defaults listen on HTTP 80 and HTTPS 443; add `--no-https` for explicit HTTP-only installation. -- Hotfix backend HTTPS-by-IP with optional `tls_server_name`: TLS SNI and hostname verification may use a DNS name while the validated TCP target stays pinned to the configured backend URL. -- Add an explicit per-backend `tls_verify` switch, secure by default. Administrators may turn verification off for an HTTPS origin without weakening DNS/address/self-loop policy. Add matching 503/504 HTML gateway pages while preserving custom-page overrides. - -## 0.7.2 - -- Publish a distinct patch release; keep both previous 0.7.1 deliveries historical. -- Remove the independently downloading utoipa-swagger-ui adapter and embed the prepared JS/CSS directly, for debug and release. Existing admin guards stay in place. -- Prepare assets with the existing Python 3.10+ tool requirement, now also needed for direct Cargo builds. Support absolute local tag ZIPs, optional archive SHA-256, bounded exact-member reads, validated download caching and retained license notices. -- Resolve build input paths from CARGO_MANIFEST_DIR, track local sources and outputs, and report missing tools/assets explicitly instead of silently changing UI versions. -- Additional historical details for this release are retained in the technical archive. - -## 0.7.1 - -- Add visual request/response header editing to each custom vhost location, with set/append/remove, validation, isolation and synchronized optional JSON. -- Split anonymous sign-in from the operational document and module/language bundles. Remove the login tagline; preserve the original brand and compact square style. -- Reduce public health to `status` and auth discovery to `setup_required` only. Make the public allowlist exact by method and path; private/unknown requests are denied before body/query extraction, including method mismatches and aliases. -- Keep Origin/Host/CSRF/session guards; reject duplicate CSRF/marker headers and hide login/setup JSON parser details. Clear UI data across tabs and on revocation. -- Add authenticated `/docs` and OpenAPI with pinned, build-embedded Swagger UI, same-session authorization, read-only execution by default and CSRF-confirmed writes. -- Replace updater reliance on a public version with local MainPID/executable checks. -- Additional historical details for this release are retained in the technical archive. - -## 0.7.0 - -- Renew managed ACME profiles automatically with shared worker bounds, jitter, backoff, private observations and validated CAS hot deployment; expose opt-out, check-now and dry-run controls. Keep previous certificates active on failure. -- Add install.sh/update.sh for Linux/systemd, root-owned release switching, stopped full-data backups and code/data rollback on failed startup/version checks. -- Add per-vhost Basic Auth with Argon2id-only storage, optional verified upstream credentials and cumulative graphical IP ACLs. Keep protected content out of cache. -- Add WebSocket toggles and graphical custom path locations, including explicit exclusion of dedicated path origins from normal pool/sticky selections. -- Replace the sample welcome page with neutral information, make the protocol fixture opt-in, unify square modal and Settings-tab styling, preserve branding. - -## 0.6.0 - -- Replace separate legacy routes with vhosts and a one-way, validated compatibility importer; preserve path-pinned origins, ACLs and existing listener scope. -- Add HTTP-01 webroot domain validation, Cloudflare DNS-01 and OVH DNS-01 alongside RFC2136 using private Certbot workers. -- Unify certificate creation/import in a provider-picker modal; record optional issuer metadata in the library and keep secrets outside exported configuration. -- Add an independently configurable Prometheus endpoint with actual-peer CIDRs, optional environment-sourced bearer token, bounded scrapes and collector switches. -- Export application/admin/listener/vhost/backend/cache/system data and full-exchange vhost histograms with bounded configured labels. -- Split Settings into functional switching cards; preserve dirty values, keyboard access and translation discovery; remove Security and legacy routing sidebar entries. -- Additional historical details for this release are retained in the technical archive. - -## 0.5.0 - -- Add a compact dedicated cache panel with vhost/domain counters, object pagination, TTL/Age/hits, memory/fill diagnostics and authenticated selective purge. -- Fix reserved administrator-cookie ordering and empty terminating DATA frames; emit fixed cache decision reasons without exposing request secrets. -- Replace special X-Mock-Origin hiding with the explicit global/vhost revision switch; ordinary response rules still handle arbitrary removable headers. -- Default HTTP/3 on TLS bindings, retain explicit null opt-outs, and explain overlapping sockets with ownership and concrete protocol/limit differences. -- Return 503 for upstream connection/unavailability, 502 for invalid responses and 504 for header timeouts; generated errors use no-store. -- Add basic asynchronous Certbot DNS-01/RFC2136 issuance, staging-first consent, private TSIG/account profiles, output validation and explicit library import. No automatic renewal scheduler or real CA validation is claimed. - -## 0.4.0 - -- Rebuild standard Forwarded and X-Forwarded metadata at an explicit trusted-proxy boundary; use verified client IP for ACL/affinity, never for destination routing. -- Add per-vhost Tinyauth subrequests, identity header allowlists, safe same-login-origin redirects and fail-closed checks before origin/static/cache/WebSocket use. -- Move certificate upload into an independent inventory; assign certificates by ID per vhost and expose metadata-only inventory reads. ACME is not implemented. -- Add named multiple-IP listeners with descriptive scope, opt-in legacy routes, reusable vhost references and actual/desired plans. -- Add Quinn/h3-based HTTP/3 UDP transport, TLS 1.3/SNI, Alt-Svc, per-endpoint connection and per-connection stream/idle limits, shared proxy policies and bounded shutdown. Preserve TCP HTTP/2 plus HTTP/1.1. -- Hide request-ID/revision headers by default; add individual overrides and optional mock-origin stripping. Apply effective Server policy across all response paths, including extra sockets. -- Additional historical details for this release are retained in the technical archive. - -## 0.3.0 - -- Add optional per-vhost certificate inventory selection and PEM file upload. Validate key/certificate pairing and names, write private immutable files, and activate only through the existing revisioned configuration update. -- Add per-vhost cache overrides and selective invalidation, including stale in-flight fill cancellation while leaving other host entries intact. -- Add administrator-authored, bounded global/vhost error pages, static vhost and missing-path responses, direct-IP and unknown-host responses. Retain ACL checks, HEAD handling and authentication challenges; never execute HTML in the admin UI. -- Move access logs into a separate modal/docked viewer with fixed filters, vhost identity, pause, scoped search/status/method/cache controls and JSON export. -- Add a compact vhost list, tabbed dialog, live health/latency summaries and visual per-host response/request headers; keep the original logo and favicon. -- Add density preferences and a Settings shortcut to Security. -- Additional historical details for this release are retained in the technical archive. - -## 0.2.0 - -- Support compression size thresholds above 64 KiB without truncation. -- Add IP/port/TLS-bound virtual hosts with frontend(s), backend pool and path policies. -- Enforce real accepted destination matching; isolate legacy routes from extra sockets. -- Add weighted round-robin, least-connections, least-traffic, adaptive and IP hashing. -- Add bounded TCP/HTTP health checks, separate passive failures, recovery and slow start. -- Add connection keepalive controls, backup members, drain and opaque cookie/IP affinity. -- Additional historical details for this release are retained in the technical archive. - -## 0.1.0 - -- Persisted public listener addresses/ports, 80/443 presets, startup defaults and restart status. -- Bounded streaming shared RAM cache, TTL/Age, LRU, privacy/Vary protections, per-route bypass, optional HIT/MISS headers and audited purge. -- Streaming gzip, Brotli, Zstandard and deflate controls, final Server policy and a global client-response header editor. -- Compact dashboard with system/process/cache diagnostics, log filters and export. -- Allow ordinary remote-origin 80/443 while guarding local proxy/admin listener targets. -- Additional historical details for this release are retained in the technical archive. - -## 0.0.6 - -- Detect mixed source layouts and preserve the known legacy auth file; refuse to delete unrecognized local changes. -- Support interactive username input for `admin init`. -- Replace managed source directories with rollback on copy failure rather than encouraging overlay extraction. -- DNS and HTTPS manual backends with per-backend pools, bounded DNS workers, validated DNS answers, optional CIDR allowlists and additional PEM trust anchors. -- rustls public TLS listener, persistent development default certificate, exact SNI bindings, local generate/import commands and explicit hot reload. -- Additional historical details for this release are retained in the technical archive. - -## 0.0.2 - -- Introduced RustProxy naming, administrator setup, Argon2id passwords, sessions, CSRF checks, login throttling, password recovery and credential revocation. -- Added host/route ACLs, generic request/response header policies, administration audit, SSE dashboard updates and security controls in the bilingual panel. -- Added an additive SQLite migration, offline validation and online database backup. - -## 0.0.1 - -- Initial manual-backend HTTP/1.1 proxy, streaming and keep-alive. -- SQLite configuration, dynamic host/alias/path routing and local token control plane. -- English/Polish interface, bounded access log, counters, CLI, demo backend and dev.sh. +## 0.27.x — compression and managed deployment + +| Version | Technical changes | +|---|---| +| **0.27.3** | Protect ownership during backup restore; enable scheduled GeoIP updates; fix listener overrides and valid WebSocket upgrade framing. | +| **0.27.2** | Fix managed Certbot issue/renew invocation without requiring operators to delete existing private Python environments. | +| **0.27.1** | Add application-managed Python/Certbot installation with system fallback and clearer runtime errors; extend sortable host lists and first-admin help. | +| **0.27.0** | Enable eligible response compression with zstd, Brotli and gzip by default, with scoped Vhost/Web Service controls; preserve published Docker routes during temporary discovery failures. | + +## 0.26.x — cache, logs and interface operations + +| Version | Technical changes | +|---|---| +| **0.26.7** | Show HOT/COLD cache capacity and progress; improve HTTP `Age` diagnostics and safe compression behavior for eligible cached/static responses. | +| **0.26.6** | Store backend table preferences per administrator; correct combined IP/country ACL logic and clarify the ACL editor's allow/deny modes. | +| **0.26.5** | Align compact filters and streamline navigation from Streams into listener configuration; no configuration migration. | +| **0.26.4** | Add draft recovery for 404-host forms, improve Appearance layout editing and keep idle upstream connection recycling effective. | +| **0.26.3** | Apply display scale consistently across the administration panel, including dialogs and controls. | +| **0.26.2** | Add minimizable draft editing for Vhosts, Web Services, Redirects and Streams so unfinished forms survive page changes. | +| **0.26.1** | Extend persisted protocol error records with the affected Vhost when it can be identified safely. | +| **0.26.0** | Record malformed HTTP, TLS, H2/H3 and connection/protocol failures separately from normal HTTP responses; expand error-log inspection. | + +## 0.25.x — Web Server and recovery + +| Version | Technical changes | +|---|---| +| **0.25.7** | Add quick Vhost enable/disable and clearer active-state controls. | +| **0.25.6** | Show upstream connection age, reuse and recycle diagnostics in the backend panel. | +| **0.25.5** | Add configurable HTTP/1.1 upstream connection recycling by age/request count and expand connection telemetry. | +| **0.25.4** | Restore configuration from retained history as a validated new revision. | +| **0.25.3** | Add per-Vhost and per-location HTTP method allow/block policies and location-level access profiles. | +| **0.25.2** | Support password-encrypted full backups while the service is running, using a consistent configuration/database snapshot. | +| **0.25.1** | Reorganize Traffic into focused Overview, Clients, Requests and Delivery views; improve saved analytics. | +| **0.25.0** | Introduce reusable static Web Services with document roots, indexes and optional directory listing, integrated with Vhosts. | + +## 0.24.x — monitoring, security and transport + +| Version | Technical changes | +|---|---| +| **0.24.10** | Persist separate error-file logging settings and keep compatibility with existing logs. | +| **0.24.9** | Introduce encrypted Secret Store for managed credentials, including migration of ACME DNS secrets. | +| **0.24.8** | Expand Cache diagnostics with memory breakdowns and HOT/COLD occupancy. | +| **0.24.7** | Make response transformation memory and concurrency limits configurable. | +| **0.24.6** | Preserve selected HOT/COLD cache capacity presets across saves. | +| **0.24.5** | Fix empty HTTP/3 request forwarding and strengthen cache memory admission, transient limits and disabled-cache behavior. | +| **0.24.4** | Reduce excessive process memory usage on Linux without changing host-wide memory settings. | +| **0.24.3** | Extend per-certificate reload and multiple scoped certificate API keys; move H2 lifecycle controls to listeners. | +| **0.24.2** | Version-only update; no functional or configuration change. | +| **0.24.1** | Improve portable network/system metrics, runtime metadata and BSD-compatible CLI data-path discovery. | +| **0.24.0** | Add graceful downstream HTTP/2 connection retirement and separate lifetime/idle/drain controls; broaden BSD system statistics. | + +## 0.23.x — certificates and dashboard diagnostics + +| Version | Technical changes | +|---|---| +| **0.23.3** | Make external certificate deploy idempotent and self-reconciling; add opt-in legacy WebSocket compatibility. | +| **0.23.2** | Add configurable Cache/Transform chart history windows and fix live-log CLI connections to HTTPS administration. | +| **0.23.1** | Add live Cache and Transform activity charts with per-scope request and outcome rates. | +| **0.23.0** | Introduce restricted external certificate API keys with token hashing, certificate-specific permissions, revocation and authenticated deploy/reload; limit inactive H2 connections. | + +## 0.22.x — cache controls and protocol handling + +| Version | Technical changes | +|---|---| +| **0.22.3** | Support guarded cleartext H2 upgrade; add hot reload and external deploy for manual certificate IDs from CLI/API. | +| **0.22.2** | Add bounded safe-method failover retry, per-backend concurrency/capacity limits and detailed upstream failure diagnostics. | +| **0.22.1** | Simplify WebSocket transport selection and add focused active-tunnel, protocol and selective cache-invalidation controls. | +| **0.22.0** | Add request collapsing, supported cached range responses, safer stale-if-error rules, proactive refresh and selective cache PURGE/BAN. | + +## 0.21.x–0.19.x — transformations and certificate workflows + +| Version | Technical changes | +|---|---| +| **0.21.0** | Add a dedicated Transform workspace, savings/error counters and cache diagnostics for transformed representations. | +| **0.20.0** | Reorganize Certificates into Installed, Renewal and Generate; expose managed ACME job and Certbot diagnostics. | +| **0.19.4** | Improve presentation of idle and very small backend traffic rates. | +| **0.19.3** | Add a non-blocking release/update checker and simplify dashboard footer diagnostics. | +| **0.19.2** | Restore explicit per-backend protection/exception controls for deliberately targeting local administration listeners. | +| **0.19.1** | Separate HTTP and HTTPS cache identities to prevent accidental cross-scheme reuse. | +| **0.19.0** | Store Live Logs column preferences per administrator without rewriting proxy configuration. | + +## 0.18.x — Docker discovery and UI + +| Version | Technical changes | +|---|---| +| **0.18.8** | Correct the explicitly disabled backend TLS verification path while retaining separate destination protection. | +| **0.18.7** | Make Response Time History collapsible by default. | +| **0.18.6** | Add live network-interface throughput and process diagnostics. | +| **0.18.5** | Break down live request-rate charts by HTTP/1.1, HTTP/2 and HTTP/3. | +| **0.18.4** | Default new backend health checks to TCP and simplify Docker agent/service and Security editors. | +| **0.18.3** | Add Docker-vhost search/hide controls, agent-assigned certificates and per-agent inventory poll intervals. | +| **0.18.2** | Secure Docker agents with HTTPS certificate fingerprint pinning; add per-Docker-vhost backend health checks. | +| **0.18.1** | Support multiple Docker host addresses, selected published IPs, multiple agents and custom service aliases. | +| **0.18.0** | Introduce a separate authenticated Docker discovery agent, automatic service observations and optional generated Docker Vhosts. | + +## 0.17.x–0.13.x — platform and routing improvements + +| Version | Technical changes | +|---|---| +| **0.17.0** | Add Debian package/service assets, a Grafana dashboard, broader Prometheus metrics and local-response/404 hostname support. | +| **0.16.1** | Hide internal configuration revision numbers from normal save feedback while retaining conflict protection. | +| **0.16.0** | Keep unmatched paths on the main backend when custom locations exist; improve guarded self-admin routing, WebSocket/H3 and resource diagnostics. | +| **0.15.1** | Improve visual Custom-location and header-rule editors, previews and cache/process live charts. | +| **0.15.0** | Add Nginx-style path matching/rewrite options, visual header configuration and Nagios/Icinga check scripts. | +| **0.14.0** | Correct administration Host/Origin checks behind trusted HTTPS proxies and add HTTP/3 WebSocket transport support. | +| **0.13.2** | Fix signed-in dashboard refresh; improve managed-update cache handoff and retention of certificate renewal status. | +| **0.13.1** | Add configurable 7–30 day administrator sessions and explicit risk approval for backends pointing to local listeners. | +| **0.13.0** | Add first-class Redirect hosts and local redirects; establish the omitted backend `tls_verify: false` compatibility default. | + +## 0.12.x–0.10.x — API, Streams and disk cache + +| Version | Technical changes | +|---|---| +| **0.12.1** | Add revisioned per-section configuration API, cache retention across unrelated settings saves and configurable COLD demotion behavior. | +| **0.12.0** | Move TCP/UDP Streams into a dedicated editor with session, traffic and active/pending listener views. | +| **0.11.5** | Add guarded TCP/UDP forwarding, Debian 13 source bootstrap and stricter-but-compatible URL path handling. | +| **0.11.4** | Fix force-HTTPS validation; add visual ACL profiles and hide cache diagnostics when a Vhost disables cache. | +| **0.11.3** | Add per-Vhost upstream client-IP forwarding control and improve Vhost search and stored access logs. | +| **0.11.2** | Correct administration HTTP/2 authority validation and add per-Vhost `308` force-HTTPS redirects. | +| **0.11.1** | Add Nginx Proxy Manager import with dry-run/conflict detection and explicit API-based apply; improve managed update health checks. | +| **0.11.0** | Add reusable ACL profiles, wildcard domains and cumulative access enforcement. | +| **0.10.0** | Introduce optional persistent COLD cache, HOT/COLD metrics, capacity controls and structured-log output presets. | + +## 0.9.x–0.7.x — administration and access + +| Version | Technical changes | +|---|---| +| **0.9.0** | Add structured JSONL/Logstash/Loki access-log sinks, expanded Vhost cards, cache inspection and administration HTTPS. | +| **0.8.1** | Enable the shared RAM cache by default and improve conditional/cached static behavior; Vhost and privacy rules still apply. | +| **0.8.0** | Add static-cache extension defaults, per-Vhost rate limits and GeoIP country policies. | +| **0.7.3** | Add first-admin setup token before service start, safer remote-admin opt-in and managed default HTTP/HTTPS listeners. | +| **0.7.2** | Embed offline Swagger UI assets during the build and remove runtime asset download dependencies. | +| **0.7.1** | Protect sign-in and administration routes more strictly; add authenticated `/docs`, Swagger and visual location header editing. | +| **0.7.0** | Add automated ACME renewal, safe Linux install/update rollback, Basic Auth, graphical ACLs and Custom locations. | + +## 0.6.x–0.0.x — initial proxy platform + +| Version | Technical changes | +|---|---| +| **0.6.0** | Unify Vhost configuration, add HTTP-01 and Cloudflare/OVH DNS-01 ACME, and introduce protected Prometheus export. | +| **0.5.0** | Add cache inspection/purge dashboard and first HTTP/3/frontend controls. | +| **0.4.0** | Add validated forwarding identity, Tinyauth protection, named multi-address listeners and HTTP/3 (QUIC). | +| **0.3.0** | Add certificate inventory/PEM upload, per-Vhost cache overrides, error pages and richer request logs. | +| **0.2.0** | Add Vhosts with multiple backends, load balancing, health checks and path policies. | +| **0.1.0** | Add persistent listeners, shared RAM cache, streaming compression and operational dashboard. | +| **0.0.6** | Improve source-upgrade safety, HTTPS origins, TLS certificates and validated backend DNS. | +| **0.0.2** | Add administrator credentials/sessions/CSRF, audit and SQLite migration support. | +| **0.0.1** | Initial HTTP/1.1 reverse proxy with streaming, SQLite configuration, access log and basic panel. | diff --git a/README.md b/README.md index 12fbbd4..3d534dc 100644 --- a/README.md +++ b/README.md @@ -1,1109 +1,49 @@ # RustProxy 0.34.0 -## 0.34.0: HOT HIT critical-section follow-up (source candidate) +RustProxy combines a reverse proxy, static/PHP web server, certificate management and traffic monitoring in one service with a browser administration panel. It supports several domains and backends without requiring a separate proxy configuration language. -Immutable response headers are shared until a HIT; the mutable per-response -copy and Body are built outside the cache's shared HOT mutex. No-Vary lookups, -per-domain counters, repeated cache reasons and inactive BAN checks use -shorter paths. The existing stale-for-revalidation policy makes the previous -full HOT sweep unnecessary. Exact LRU, generation barriers, BAN, COLD, -transformation/ETag, TTL and cache bypass settings remain unchanged. +## Main capabilities -This revision retains the **0.34.0** version and all prior scoped-cache, -`sendfile`, security, logging and vhost/administration controls. In the -uncached reverse-proxy hot path, request observation now passes the immutable -selected route/frontend directly to forwarding; compiled locations are stored -as shared `Arc` handles instead of cloning their headers, regexes and policies. -Exact-host routing avoids wildcard temporary strings unless needed. - -Single-member non-cookie load balancers bypass candidate vector allocation -and the shared weighted round-robin scheduler mutex, but still enforce -health, drain, pool eligibility, capacity and leases. Multi-member policies, -sticky cookies and weighted RR retain their existing selection rules. Healthy -backend observations use an atomic fast path; resolved per-member health -settings are shared across leases. Failure/quarantine/recovery transitions -retain their existing locked state machine. - -Requests without configured error fallbacks no longer clone request headers -for error replay. Safe/empty request retry headers are saved only if a -multi-backend retry or upstream H2 close replay is possible. Exchange source, -cache-enabled and transform-enabled log flags move from mutexes to atomic tags -with **unchanged emitted log fields**. Live log records are cloned for -subscribers only while subscribers exist; history and external sinks remain. - -These changes have **not** been compiled or timed in this environment, and -there is no claimed 0.34.0 RPS improvement. Build and run the regression and -nginx benchmark matrix before deployment. - -## 0.34.0: scoped cache, proxy accounting and opt-in Linux sendfile - -An operational vhost save (including every upstream keepalive/recycle knob, -LB health/weight, backend concurrency, diagnostic controls and timeouts) -no longer purges the HOT or COLD cache. Origin/content policy changes purge -only the affected vhost(s); global cache/transform/headers/forwarding and -Web Service changes still trigger the conservative global invalidation. -Persistent COLD entries carry a vhost content fingerprint, preventing stale -reloading after an interrupted scoped purge. The global cache identity changed -for this release, so existing 0.33.8 COLD entries may require one-time refilling. - -Reverse-proxy response metering now snapshots the selected backend lease after -routing and avoids locking backend selection for each response DATA frame. -Request-body metering, timeouts, cancellation, accounting and HTTP transport -behaviors stay unchanged. This is a source-level optimization candidate: -**build and live RPS comparison are required before claiming a performance gain**. -There are no new LB controls. In **Web Server -> Edit service -> Delivery**, -`sendfile: off | auto` is an opt-in Linux `sendfile(2)` transport bridge for -full static GET responses of at least 64 KiB over plain HTTP/1.1. The already -validated file descriptor is sent directly to the TCP socket; cache-enabled, -compressed, ranged, transformed, TLS/H2/H3 and non-Linux requests retain the -existing streaming path. The default is `off`; toggling this transport option -does not purge HOT/COLD cache. **Native compilation and live protocol tests -remain pending; do not deploy this candidate without them.** - - -## 0.33.8: request-path performance and compatibility verification - -This release reduces unnecessary cache-policy work and header copies when -caching is disabled, creates body-idle timers only when a read actually waits, -and formats request IDs without a generic per-byte formatter. Request IDs retain -fresh operating-system entropy and the same SHA-256 construction. - -Live-log history moves entry cloning and eviction cleanup outside its mutex -critical section. Its bounded retention, best-effort insertion, drop counters, -live stream and external sinks retain their existing contracts. Saturation tests -record history losses explicitly alongside throughput and resource use. - -The Web Server uses a narrow filesystem context and borrows its pinned root -descriptor for nonempty paths. Adaptive reads are bounded at 64 KiB. A local -file-body wrapper reports completion with the last expected byte, fixing HTTP/1 -cache publication and complete-response accounting for ordinary and empty files. -Range, conditional, HEAD, sidecar, cancellation and short-file cases are covered -by additional tests; arbitrary upstream bodies retain their trailer semantics. - -Compatibility testing also fixes existing explicit-Host-port validation, -long-form `.htaccess` Expires parsing, expired cache inventory visibility, -reserved backend-diagnostic header protection, escaped-JSON secret redaction, -and certificate deployment authorization ordering. Obsolete tests are updated -to exercise the existing documented behavior, including scheme-isolated cache -keys, purge generations, private administration responses and bundled error pages. - -The LB panel, database schema, default TLS/caching policies and bounded pool -recycling retain their existing behavior. Historical verification methods and -results are documented in [docs/TEST_REPORT.md](docs/TEST_REPORT.md) and -[docs/PERFORMANCE_0.33.8.md](docs/PERFORMANCE_0.33.8.md). - -## 0.33.7: named dashboard presets, panel components and chart typography - -Titles across metric tiles and charts now use the same understated text tone -(`--muted`) as the small Overview counters. The shade follows the active UI -color theme; numerical readings keep their original contrast and fonts. -The shared Vhosts Backend/H2 quick-view popover follows Corner rounding -settings (including square, subtle, rounded and extra-rounded) for its panel, -rows and close button, immediately and without losing the open popover. - -Every Overview card title is now set at 12px in sentence case: counters, -Process, RAM cache, backend tiles, charts and diagnostics use one proportional -heading font. Secondary labels and small numeric readings use consistent -monospace, while established HTTP/RAM/H1/H2 abbreviations retain their case. -This Overview-only refinement does not change the uppercase styling of chart -headings elsewhere in the interface. - -The Cache page separates **Domains and effectiveness** from **Stored objects** -with a consistent 10px gap. Vhosts upstream H1/H2 protocol badges now open -the same anchored click-to-open popover used by the backend quick-view. It -shows protocol observations and H1/H2 totals without a native hover tooltip; -live status updates change its contents in place, without closing it. - -Chart legends use the same 9px monospaced size as **Requests per second** -throughout the interface, including numeric values and Overview density modes. -In the Vhosts list, click the **Backends** count or **Status** metric to show a -compact per-origin overview (ID, configured address and port, HTTP/HTTPS and -HTTP version policy, health and last HTTP status from a real proxied response). -Missing HTTP responses remain explicitly unknown; the configured hostname is -shown rather than inventing a DNS-resolved IP. No additional probe or request -is made to open the panel. Click outside or press Escape to dismiss. -The H1/H2 policy badges use that same click-to-open popover instead of a -transient hover tooltip. Its text and response counters update in place on -live status ticks. Clickable Backend and Health tiles retain the square shape -of adjacent Vhost metrics at every corner-rounding setting. Cache's two -adjacent domain/object tables have a 10px gap. - -- **Dashboard Presets** replaces the former Arrange Overview modal. Save up to - 16 named layouts per administrator, including widget order, visibility, card - widths, density and Overview history sample count. Load, overwrite, rename, - delete or restore factory defaults from one dialog. **Unlock layout** remains - the interactive editor; existing saved preferences are retained. -- Presets are persisted in a separate SQLite `dashboard_presets` table (schema - 10), outside the 4096-byte administrator appearance document. They do not - change the active proxy configuration revision. A factory reset changes the - current dashboard, not existing named presets. -- Chart headings throughout Overview, Web Server, diagnostics, Cache, - Transform, Traffic, Vhosts and Streams use the established proportional font - at **12px** (Overview sentence case, other sections uppercase). Chart labels, values and - entire legends (including Compression activity series names) use the existing - monospaced font. Chart behavior is unchanged; two Overview titles now - use sentence-case English/Polish translations. - Proxy forwarding, trusted-client IP headers and WAF behavior were not - modified. See [API](docs/API.md) and [verification](docs/TEST_REPORT.md). - Rust compilation and live server acceptance remain outstanding. - -## 0.33.5: fixed CPU chart scale on Overview - -- **System and process CPU** uses a fixed 0–100% vertical scale, without - autoscaling when measured CPU usage is low. Both lines and the legend retain - real CPU percentages; values above 100% are clipped at the top of the plot. -- Keep the CPU plot 125px high across viewport sizes and dashboard densities - so it does not stretch neighboring widgets. Other chart scales and heights, - grid layout, sampling, APIs, databases and saved preferences are unchanged. -- Overview heading follow-up: **Requests per second** shows the existing - dynamic `max XX.X req/s` value in the upper-right instead of the LIVE badge; - remove the redundant LIVE TRAFFIC eyebrow and its former peak footer label. - Remove the LIVE badge from **Connection Activity**, without changing its - counter, history, legend or chart rendering. - -## 0.33.4: unified Web Server live metrics - -- Web Services now display six always-visible live metric tiles using the same - responsive layout, borders, labels and typography as Vhost live metrics. -- Each service's request-rate history uses the same full-width Vhost chart - container, responsive SVG rendering and chart palette. The live metrics no - longer need to be expanded, and values appear as soon as status is available. -- Preserve the existing per-service counters, rate sampling, history limits, - authenticated status stream and PHP availability semantics. No configuration, - database or API changes are required. - - -## 0.33.3 sidebar appearance follow-up - -- Main navigation takes active/hover backgrounds and selection colors from the - selected **interface theme**, not the chart color palette. Dark and ultra-dark - use theme brand tones; light and cream blend theme brand/text for contrast. - Switching the chart palette no longer changes sidebar or mobile-menu highlighting. -- Sidebar navigation and the mobile menu follow the existing Appearance → Corner - rounding setting. Changes take effect live with the current preferences; no - additional JavaScript, API changes or preference migrations are required. - -## 0.33.3: resizable Overview dashboard and free card placement - -- **Visual layout editor fix:** **Arrange Overview cards** uses uniformly sized, - readable preview tiles (three columns on desktop, two on tablets, one on - phones), not the 12-column canvas used by the actual Overview. Its `2/12` - through `12/12` width controls still resize the real dashboard cards. - Toolbars and hidden-card actions wrap cleanly without horizontal modal scroll; - dragging, hiding, Save/Cancel and factory reset work as before. -- Fixed time-series SVG viewport scaling after widening cards. All Overview line plots - (numeric sparklines, full-width charts and diagnostic/network interface charts) - now span their available width while keeping their vertical sizing and stroke - thickness; donut charts remain circular. No new status polling is required. -- In **Unlock layout** or **Arrange cards visually**, use − / + to change a - card's width in the 12-column dashboard grid. Available widths are 2, 3, 4, - 6 and 12 columns, restricted by card type for readability. **3 columns fits - four graphs in one row** on a desktop canvas wider than 1080px. Even the - smallest number/sparkline cards can be expanded to 4 columns (twice their - original width) and moved to the very top, beside regular charts. -- The first manual move or resize enables a freely ordered layout, with no - enforced separation between numbers, small charts, wide charts or panels. - Responsive widths clamp to six/two columns on smaller canvases; dense grid - packing reduces empty gaps without changing chart sampling or polling. -- Card sizes are saved with the account's existing Overview preferences in - nullable `dashboard_widget_sizes`; **null retains 0.33.2 group ordering** - for older saved layouts, while `{}` enables free order with normal widths. - Save, cancel, factory reset and modal preview include dimensions. Existing - per-account layouts, factory visibility and 600-sample default are preserved. - No SQLite migration or extra status fetches are needed. Runtime Rust acceptance - remains required. - -## 0.33.2: unified progressbar appearance - -- Cache HOT/COLD object, RAM and disk occupancy bars now use the same selected - chart palette (`--chart-accent`) as Overview meters, instead of fixed green. - Changing Appearance → Chart style updates every bar immediately, including - native `` controls. The neutral track retains theme contrast. -- Progressbar rounding follows Appearance → Corner rounding (default, square, - subtle, rounded and extra) across Cache, Overview, WWW and traffic ranking - bars. Both the track and its fill follow the same radius. No metrics, APIs, - polling behavior, dashboards or saved preferences are changed. -- The **System Load → RAM used / total** meter uses the same custom track/fill - as other Overview cards (including the chart palette and corner radius), - instead of browser-specific native `` rendering. Its original - 5px height and status-based used/total percentage are unchanged. Missing - total RAM does not produce an invented percentage. - -## 0.33.1: factory Overview preset and live layout preview - -- `src/admin/web/dashboard-defaults.json` defines the factory Overview card order, - visibility, density and initial 600-sample history. Both the Rust preference - defaults and authenticated JavaScript use this same file. New accounts without - stored preferences use the full preset; **Reset to default** restores card - order/visibility, not density or browser-local sample history. Existing saved - layouts and browser-persisted sample choices are preserved. Future registry additions remain opt-in. -- An unlocked inline layout is now authoritative during live backend/Stream - updates and unrelated preference refreshes. Showing/hiding those cards takes - effect immediately and stays stable until **Save and lock** or **Cancel**. - Cancel restores the saved layout. No extra polling, storage migrations or - production request-path changes are introduced. - -**0.31.0 panel/cache additions:** browser-negotiated Zstd/Brotli/gzip for HTML, JS and CSS (Settings → Administration); optional HTTP BAN/PURGE restricted to explicit IP/CIDR allowlist (Settings → Cache, disabled by default); System information Refresh button beside the API badge. See [Settings](docs/SETTINGS.md) and [Cache](docs/CACHE.md). - -Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration, -streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools. - -**0.31.9 Certificate API management: inspect and edit credential settings without exposing/rotating the current token, or regenerate one existing token with one-time display. Rust/Cargo and live testing required.** - -## 0.33.0: Web Server / PHP-FPM observability - -- **Fixed dashboard startup:** initialize the shared Overview history selector before any - bundled top-level change listener. This prevents `onChange` startup errors - and allows the authenticated panel to load. The History selector supports - 60/120/300/600 samples, temporary edits and browser-persistent choices, - in the header row beside the connection indicator (Overview only), - independently of Cache/Transform, without additional status polling. -- **Compilation cleanup:** restrict `record_web` visibility to the Web Server - module, matching its `ResponseKind` argument and removing the - `private_interfaces` diagnostic without changing request accounting. -- **Compact donut layout:** move HIT/MISS totals beside the rings/legends rather - than above them, preserving standard card-row heights and responsive behavior. -- **Twelve opt-in WWW widgets** in the existing Overview layout registry: HTTP - status split, static/PHP/other, PHP permit capacity, HTTP methods, recent 5xx - percentage, PHP requests/s, requests/s, response-header latency, FastCGI - failures, emitted-body byte rates, status-class rates, and top services. - **Every new widget starts hidden** for both new accounts and upgrades from - older saved layouts. Existing cards remain as configured. -- **Web Server per-service live details:** expandable metrics in existing service - cards, including RPS, 5xx, header latency, PHP concurrency permits, static - versus PHP classification, and body bytes. The main live SSE/status snapshot - is reused; no new API polling, SQL schema or per-request untrusted labels. -- **Prometheus exporter:** bounded per-Web-Service response-kind/method/status, - body byte and downstream compression counters, plus active headers, PHP - concurrency limit/use/available gauges and classified FastCGI failures. - Response byte counts are sampled from produced body frames **before** final - downstream compression (not wire throughput). Response latency is time to - headers (not complete delivery). PHP capacity is RustProxy's semaphore, - **not** the actual PHP-FPM worker pool. -- **Grafana:** retain the existing operations dashboard and extend it with - Web Server, PHP-FPM, per-service breakdown and WWW/PHP correlation rows, - all filterable by configured `web_service` IDs. - - -## 0.32.8: Overview metric visualizations - -- HOT cache RAM occupancy, COLD write-queue RAM occupancy and COLD disk occupancy appear as compact progress bars with used/limit details on the Overview. COLD cache objects live on disk; queue RAM and disk storage have separate budgets. Without a configured budget, the card shows bytes and an indeterminate meter rather than an invented percentage. -- The main cache HIT ratio has a small HIT/MISS donut with counts and legend. The optional recent-window cache ratio uses the same format; bounded file-descriptor, HTTP protocol-share and backend-capacity widgets use progress bars. -- Transformation and compression activity use two full-width, four-series live charts like the existing HTTP protocol mix. They show sampled per-second counts with named series; compression charts count gzip, Brotli, Zstd and deflate responses (not uncompressed ones). -- These changes use existing status/SSE data without extra polling, API routes, database migrations or altered appearance presets. Existing account-specific Overview ordering/visibility preferences remain in effect; newly added cards are visible by default in factory layouts. Runtime integration testing remains required. - -## 0.32.8: Overview action layout - -- The Overview heading stays left-aligned while the refresh, arrangement and layout-lock actions flow to the right; small screens wrap buttons without squeezing them into three equal columns. -- The inline layout actions and visual editor keep their existing IDs and behavior while sharing the standard button styles (including Compact mode). - -## 0.32.7 development follow-up: Appearance and H2 diagnostics - -- Overview **Refresh now**, **Arrange Overview**, **Unlock layout**, and Appearance **Visual Overview editor** / **Show all cards** use the common button sizing, including the global **Compact** preset on mobile. -- Dashboard density is more distinguishable in metric tile spacing/numbers, chart heights and non-table panel padding/headers. The grid gutters, visibility/order preferences, refresh cadence and proxy routes remain unchanged. -- A terminal request failure tied to a recently confirmed `GOAWAY(NO_ERROR)` is only traced at `TRACE` (not `DEBUG`) as a duplicate upstream diagnostic. It still returns 502 and appears in the access diagnostics; unrelated `Canceled -> connection closed` failures remain genuine errors. No blanket suppression by error string. - -## What changed in 0.32.7 - -- **H2 upstream recovery:** gracefully drained upstream H2 connections sending - `GOAWAY(NO_ERROR)` no longer poison Passive Health. Retire the affected Hyper - pool generation and retry **once**, only for empty, replay-safe GET/HEAD/OPTIONS - without WebSocket/administration tunneling. Non-replay-safe requests are not - resent and can still receive 502 if the peer closes their connection. -- **Passive Health:** collapse bursts of I/O failures within 250 ms into one - failure observation; when active checks are enabled, consecutive successful - probes can reopen a passively quarantined backend. Passive-only monitoring - uses a bounded cooldown before a half-open traffic attempt. Keep the I/O error - checkbox enabled to count unrelated, genuine I/O failures. -- **Error log:** `WARN` when a backend is first removed from traffic, including - backend ID, health source, failure reason, timestamp/description of the first - error in that failure streak and the triggering error. Requires enabling the - existing Error log sink (Settings -> Logging); when disabled, WARN goes to - stderr/journald. Access logs still show individual 502s and subsequent 503s. -- **Acceptance:** Rust compile/test and live negotiated H2/GOAWAY testing on - staging are required before production installation. - - -- **Appearance Dashboard layout ** Overview cards now use a - full-width horizontal strip instead of a long list in the right half of - Settings. Navigate sideways with the arrow buttons, toggle card visibility - or reorder in place, or select **Show all cards** to expand to a full grid. - The existing visual editor and saved per-account preferences are unchanged. - -- **Rate-chart pause recovery:** share one - counter continuity policy across Cache HIT/MISS/BYPASS, Transform, HTTP - Overview/protocols, optional wide/small charts, backend and Stream/Vhost - traffic charts. After tab hide/pagehide (including BFCache) or a delayed - refresh, keep plotted history, use the first reading as a new baseline, - and resume rate points on the next normal interval. Full-width Overview, - WWW and Web Server charts keep their drawn lines continuous by not appending - the unavailable first rate sample; the current rate displays "-" until a - valid interval is measured. A genuine measured zero still appears as 0/s. - Cache and Transform intervals and the separate - System information interval remain configurable; no additional polling. - -- **Development fixes :** keep Network diagnostics - chart DOM stable across live refreshes, preventing repeated row/SVG replacement - during scrolling. Network-interface chart history advances at the Overview - live-status cadence using the latest sampled rates; fetching system data still - follows the independent System information interval in Settings. Current - CPU/RSS and per-interface RX/TX chart points now come from the existing - background OS sampler through compact SSE/fallback data, every **3 s** by - default (saved **2 s** settings remain valid), while Process details still - follow the configured System information refresh. On mobile the Network - interface chart list no longer traps touch scrolling in a nested scroll area. - Origin protocol badges use exactly the same rounding as adjacent pills for - all corner presets, without changing their color. Repair CSS descendant - selector transformation and protect admin responses with - `Cache-Control: no-transform`. Real iOS 27 scrolling remains unverified. - -- **Mobile Overview:** display two metric tiles per row on phone-width canvases, - including 320-430px layouts and the initial pre-script paint. Keep horizontal - overflow disabled, allow vertical touch scrolling and avoid unnecessary DOM - reparenting/scroll anchoring during Dashboard preference refreshes. -- **Global corner presets:** apply the selected Appearance shape consistently to - vhost cards, status badges, entity cards and secondary panels as well as the - existing metric and content cards. Keep the square-style draft dock unchanged. -- **Vhosts backend HTTP badges:** show the configured upstream mode alongside - the last HTTP/1.1 or HTTP/2 version actually returned by a real upstream - proxy request for each origin, in the same lower badge row as the Vhost - policy indicators (retaining the original observed/unknown badge colors). `AUTO → ?` means there is no response from the - past five minutes; never guess a negotiation result or run an extra probe. - The authenticated status payload includes last observation timestamp and - counters per upstream client; the active connection may vary across requests. -- **Tests:** Chromium simulated-mobile layout/scroll and shape presets, - JavaScript live-status protocol badges and Rust response-version recorder test. - The actual Rust build, iOS Safari interaction and end-to-end negotiated ALPN - verification remain pending on a deployment host. - -## Previously in 0.32.5 - -- **ACL editor startup hotfix:** fix the JavaScript `i18n` initialization order - that could prevent the entire administration UI from loading. This does not - change stored configuration or DNS ACL behavior. -- **Dynamic DNS/DDNS ACL allow rules:** reusable ACL profiles, host policies, - inline vhost/location ACLs, and Docker ACL assignments now accept `allow - office.example.ddns.net` in place of a static client IP/CIDR. Existing IP, - CIDR and country-based controls retain their previous precedence. -- DNS answers (A and AAAA) are refreshed every **60 seconds**, with up to - **128 distinct hostnames**, **32 unique addresses per hostname**, **8 parallel - lookups** and a **4-second timeout**. A failed/empty/invalid resolution is - removed from the allowlist (fail closed); successful entries expire within - 180 seconds if refresh tasks stall. Initial access is denied until the first - successful lookup. No DNS lookup or locks on the proxy request path. -- The ACL profile editor now offers an IP/CIDR vs DNS/DDNS selector and - explains the allow-only semantics. Existing JSON documents keep the `network` - string format; no database migration, extra public route or backend resolver - behavior change. Name resolution follows the machine's DNS configuration. -- DNS names are **allow-only**: dynamic deny rules are rejected because a DNS - outage could otherwise silently remove a security deny. Hostnames must be - ASCII FQDNs (punycode for IDNs, no wildcards, URL or ports). -- **Security note:** allowing a DDNS hostname delegates address authorization - to whoever controls its DNS records. Use trusted domains and keep a - default-deny ACL profile; old IPs may remain allowed until the next DNS refresh. -- Rust/Cargo compilation and real DNS/address-rotation testing are **pending** - before production deployment. - -## Previously in 0.32.4 - -- **Follow-up build correction:** a retry request now passes a cloned URI to - the HTTP request builder, retaining the original for backend-specific - HTTP/2 `:authority` selection. This resolves Rust `E0382` (borrow after - move) in `src/proxy/types.rs`. Neither retry policy nor backend dialing - changes. Rust compilation is still required on a host with Cargo. -- **Tighter, consistent Overview gutters:** the aligned Dashboard uses 4px - gaps at normal UI density, 3px at dense and 12px at comfortable, in both - axes. This applies between small tiles *and* between tiles and large panels. - The more-specific dashboard-density CSS rules no longer override the grid - gutter. Widget sizes, responsive rows and saved layouts are unchanged. - -- **Preserve Host for every upstream protocol.** `http1`, `auto`, compatible - `http2` and strict `h2` accept `preserve_host: true` without forced UI - downgrades. For HTTP/2, the outgoing HTTP authority follows the chosen Host; - network address validation/DNS/TCP continue to use the configured backend URL. - HTTP/2 ALPN and H2C retain their previous protocol/retry semantics, and - legacy WebSocket Upgrade stays isolated. -- **Separated HTTP authority from transport identity.** The existing TLS - server-name setting still controls SNI and certificate identity independently - of the visitor Host. No extra DNS queries, forced HTTP/1.1 fallback, protocol - probing or request replay are introduced. For a verified certificate issued - to a DNS name, specify `tls_server_name` and private CA as appropriate. -- **Less restrictive HTTPS-by-IP warning.** The H1 diagnostic preset remains an - explicit *optional* troubleshooting action, not a validation requirement for - Auto/H2, pools or Preserve Host. Warn only when verifying HTTPS-by-IP without - an explicit certificate DNS name (IP-SAN certificates are also possible). -- **Overview spacing:** remove the leftover outer margin of the shared grid so - the transition from metric tiles to large charts uses the same density-aware - gutter as every other row, without altering saved layouts or card sizes. -- No database/configuration migration or new privileges. **Rust compilation, - runtime protocol acceptance against real Angie and benchmarks are still - required before production rollout.** - -## Previously in 0.32.3 - - -- **Consistently sized Overview rows:** the dashboard now uses ordinary CSS Grid for both default and customized layouts. Six numeric tiles, three standard live charts, two optional wide charts and three diagnostic panels form separate, equal-height rows with the original density-controlled gaps. Blocks cannot float into unfinished rows of another size or acquire arbitrary heights. Ordering and Hide/Show remain editable **inside each size group**; existing saved widget IDs, visibility, preference API, chart data and reset/default behavior stay compatible. CSS Grid automatically resizes whole rows when live content grows, without pixel offsets, layout timers or extra polling. - -- **Layout and shape polish:** all 62 blocks remain movable, hideable, recoverable and resettable using existing account preferences. The X Hide control now matches the rounding of the arrow buttons for all five global corner presets; the RustProxy logo keeps its established outline and is excluded from global corner changes. No new metric polling, backend settings, API routes or version changes. -- **62 optional/standard blocks:** six more miniature history cards and seven new full-width multi-series charts. The new views cover requests/s, H1/H3 shares, cache misses, backend activity, protocol traffic rates, CPU, RAM, cache behavior, open connections, backend availability and latency. All 13 additions are hidden by default, consume the existing snapshot and have bounded histories; no extra backend polling. Buttons remain right-aligned, with the badge before them. -- **Backend upstream protocols:** four explicit, backward-compatible choices: HTTP/1.1 (`http1`, default), Auto (`auto`: HTTPS ALPN H2/H1, cleartext H1), HTTP/2 compatible (`http2`: HTTPS ALPN H2/H1, cleartext h2c) and H2 only (`h2`: HTTPS requires H2, cleartext h2c). No post-failure request replay, extra retry loop or changes to WebSocket semantics. -- **Whole-Overview arrangement:** the same visual editor now manages all 62 independent blocks: the previous 30 metric cards, 12 optional time-series cards and seven full-width panels (live traffic, system, connections, cache, process, network and backend table). A responsive, row-aligned grid allows free ordering/hiding of every block without sacrificing the symmetrical factory layout. **Default view** previews the factory layout without writing it; **Reset layout** requests confirmation and restores only Dashboard order/visibility in one preference PUT. Existing saved layout and unrelated account settings remain unchanged. -- **More opt-in charts:** CPU, RSS, file descriptors, open connections, WebSocket tunnels, TLS failures/s, request errors/s, recent cache HIT percentage, cache HOT RAM, incoming HTTP/2 share, mean response latency and unhealthy backend count. All are optional and hidden initially; 30-sample history uses the existing status updates and never creates network calls. Ratios and rates require genuine counter deltas; missing observations remain unavailable, not fabricated zeros. -- **Overview Cards registry:** source of truth for widget IDs, translations, selectors, defaults and data-source metadata is `src/admin/web/dashboard-widgets.json`. Both server-side preference validation and the authenticated dashboard JavaScript use this registry. Existing order and opt-ins are preserved; stale unknown widget IDs no longer invalidate an entire saved preference document. -- **Optional HTTP backend cards:** fourteen opt-in Overview tiles: origin health, in-flight requests, errors/s, health-probe RTT, byte rates, configured upstream modes, request-rate trend, concurrency-capacity utilization, HTTP 5xx, timeouts, DNS/TCP/TLS/I/O errors, retry attempts, queue pressure and Hyper client-pool rotations. All reuse the existing status stream; all fourteen are hidden by default, including upgraded accounts. Configured protocol modes are not negotiated ALPN results. -- **No protocol migration or behavior regression for saved origins:** unchanged `protocol: "http2"` HTTPS origins retain their 0.32.2 H1 TLS fallback. The new strict mode must be selected explicitly as `protocol: "h2"`. The Origin editor labels ALPN/H2C transport behaviors, and HTTP/1.1 WebSocket Upgrade continues using its separate client. The OpenAPI Dashboard preferences schema now matches all 49 registered blocks. -- **Two optional appearance themes:** **Ultra dark** uses an AMOLED-style near-black canvas and restrained mint highlights; **Warm cream** uses a muted ivory, low-glare palette. Both are selectable in Settings -> Appearance, persisted per administrator via the existing preferences API, available on the login screen after a cached choice, and preserve the original Dark/Light options and dark default. -- **Direct block hiding and restoring:** when Overview is unlocked, each numeric tile, chart, diagnostic panel and table has an accessible **× Hide from Dashboard** button. This changes only the current layout draft; the collapsible **Hidden blocks** shelf restores any block immediately. **Save and lock** persists order/visibility once; **Cancel** reverts both. Hidden blocks also remain available in the visual editor. No measurement, registry or server data is deleted. -- **Direct, lockable Dashboard arrangement:** choose **Unlock layout** on Overview, drag the handles on actual number tiles, charts, diagnostics panels or the backend table, or use the arrow buttons. **Save and lock** saves once; **Cancel changes** restores the previous order. Moving to another panel discards unsaved edits. No status re-polling or cloning of live charts is necessary. The modal editor remains available for enabling hidden blocks. -- **Labeled block types:** all 49 entries have explicit registry metadata for **Number**, **Number + chart**, **Chart**, **Diagnostics panel** or **Table**. Appearance settings and the arrangement modal show the type, and the modal filters hidden blocks by type. Existing selections and defaults are unchanged. -- **Visual Overview layout editor:** use **Arrange cards visually** on Overview or **Visual layout editor** in Settings -> Appearance -> Dashboard layout. Drag previews of metrics or large panels to reorder; Show/Hide controls and keyboard arrows are available. Save commits both lists in one account-preference update; Cancel leaves the layout unchanged. Existing Appearance controls still work. -- **Connection-level ALPN reuse:** H1/H2 is negotiated only when opening a TLS connection, not on every HTTP request. The existing per-backend Hyper keep-alive/pool and bounded generation recycling (default 25,000 requests or 3,600 seconds) reuse negotiated connections and permit renegotiation when new connections open. No shared protocol-decision cache or extra probes were added: DNS and multi-origin backends may have peers with different H2 support. Explicit H1, H2C and H2-only, and WebSocket isolation keep their existing behavior. -- **Regression coverage:** add local TLS peers for H2/H1 ALPN, H2-required failures, cleartext Auto, protocol serialization, and WebSocket isolation; add registry/preference regressions. Existing pooling, health probes, HTTP/3/QUIC and WebSocket transport behavior are unchanged. -- **Verification limit:** pre-Cargo checks are runnable here, but Rust 1.99+, real-peer Rust tests, process benchmarks and production acceptance must be executed on a Rust-enabled host; see [test report](docs/TEST_REPORT.md). - -## Previously in 0.32.2 - -- **Web Server cache revalidation:** browser refresh (`Cache-Control: no-cache` or `max-age=0`), stale objects and freshness-constrained requests now use stored origin `ETag` / `Last-Modified` validators. An unchanged local static resource returns `304` to RustProxy; it reuses the cached body and increases `X-Cache-Hits` rather than resetting it to a new MISS. -- **Correct conditional delivery:** retain the client's original validators for downstream `304` decisions (or serve a body on an unconditional refresh). HEAD, single byte ranges, transformed-response headers and `Vary` isolation are retained. If the local resource changes, a successful response replaces the previous representation; an uncacheable 304 discards the shared copy. -- Reverse Proxy caching, frontend routing, PHP-FPM, configuration/storage schemas and the administration API remain unchanged. Rust/Cargo and browser-based runtime acceptance are required; see [verification report](docs/TEST_REPORT.md). - -## Previously in 0.32.1 - -- **Overview readability at 90% UI scale:** compare the supplied 0.31.9 source with 0.32.0, remove the latter's forced desktop card heights and restore content-sized rows. The six-column desktop layout and visible chart legends remain intact. Increase compact Dashboard headings (10 → 11px), values (22 → 25px), secondary text (8.5 → 10px) and the Payload breakdown (9 → 10px). -- **Consistent tile spacing:** Overview now uses `--ui-gap`, the same density-aware spacing variable as other Dashboard sections, instead of its own fixed 10/8/6px spacing in normal/compact/ultra Dashboard modes. UI scale (including 90%) affects all gaps equally. -- **Appearance hints without overflow-causing tooltips:** Dashboard layout Stream card descriptions now use native, localized browser tooltips and accessible button descriptions instead of absolute-positioned custom bubbles. The settings grid fits narrower containers and obsolete tooltip CSS has been removed. -- Preserve Dashboard visibility/order preferences, density choices, responsive column breakpoints and opt-in Stream cards. No runtime, API, SQLite, proxy routing or language-pack changes. The Rust-independent `./dev.sh test` checks passed, then stopped with code 127 because Cargo is unavailable; **Cargo/build and authenticated-browser validation remain open** until executed on a supported runtime. - -## Previously in 0.32.0 - -**Dashboard follow-up:** saved opt-in cards show on Overview even without active Streams (neutral no-data state). Appearance card-setting explanations remain on hover/focus; Overview metrics and legends are permanently visible inside the original six-column desktop grid, with taller tiles and no hover popovers. Stream editor explains the detailed-metrics inheritance and omits the redundant enable hint. `scripts/start_stream_backends.sh`, `scripts/test_stream_tcp.py` and `scripts/test_stream_udp.py` provide runnable mock peers and separate black-box tests. See [STREAMS.md](docs/STREAMS.md). - -- TCP and UDP Stream hosts support optional pools of up to 32 IP:port backends with weighted Round Robin and weighted Least Connections/Sessions, stable backend IDs, session caps, enabled/drain flags and per-session backend affinity (including UDP). -- Optional bounded TCP Connect checks with rise/fall thresholds, and explicit UDP probes with exact response matching. UDP connect without a verified response is **Unverified**, not Healthy. Health checks are canceled on listener shutdown and globally bounded to 16 concurrent probes. -- Existing per-Stream counters, status snapshot, Prometheus `include_streams` and RX/TX charts are retained. Opt-in backend detail counters and health observations use bounded config IDs only. -- **Settings → Appearance → Dashboard layout** adds Stream overview, traffic, sessions and backend-health cards. These use the existing preference persistence, layout, status updates and SSE/polling; all new cards are hidden by default for existing and fresh accounts. No UI action restarts the proxy. -- Old single-`upstream` configs remain valid, with an implicit backend `primary`. Backend pool edits still follow the existing Stream restart-required lifecycle. See [Streams](docs/STREAMS.md). -- **Release acceptance remains open** until Cargo check/tests and live TCP/UDP/health checks are exercised on a Rust toolchain host. - -## Layer-4 streams - -Raw TCP/UDP forwarding has its own **Streams** page in the primary navigation, alongside **Vhosts**, **Redirects** and **404 hosts**. Streams use the same card/modal workflow as vhosts, but each stream owns a dedicated Layer-4 listener and an optional backend pool and intentionally bypasses HTTP ACL/cache/header/compression logic. The page exposes live per-stream connections/sessions, rejects, errors, transferred bytes and RX/TX rate charts. Listener changes require a restart. See [TCP/UDP streams](docs/STREAMS.md). - -Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners. - -## Previously in 0.31.9 - -- **Settings → Certificate API:** view the saved non-secret configuration of any Key ID, including comment, allowed certificate IDs, reload/deploy permission and creation/last-use timestamps. Current bearer tokens cannot be retrieved. -- **Edit settings:** change a credential's comment, certificate allowlist and deploy permission without replacing its bearer token, Key ID, creation timestamp or last-use timestamp. Existing automation can continue with the current secret. -- **Regenerate token:** replace only the selected credential's token, keep its settings, immediately invalidate the old secret and display the new token once. New per-Key-ID endpoints: `GET` and `PATCH /api/certificate-api/{key_id}`, `POST /api/certificate-api/{key_id}/regenerate`. Administrator session and CSRF are required for writes; external automation bearer tokens cannot call the management endpoints. -- No SQLite migration or public proxy change. Existing `PUT /api/certificate-api` creation/manual rotation, revocation and token grants remain compatible. Verify with Rust 1.99+ before production use. - -## Previously in 0.31.8 - -- **Test organization:** merge 28 mechanically numbered Rust test fragments into seven coherent `tests.rs` suites. Test-only files are exempt from the 500-line production-module limit; no test cases or runtime behavior are intentionally changed. See [test report](docs/TEST_REPORT.md). -- **Stored Logs:** click a desktop column heading to sort ascending/descending (including discovered nested JSON columns). Sorting applies to the entire bounded server-side matching scan **before pagination**, with missing values last. Direction and selected sort column persist per administrator. Existing filters, mobile cards, and repeated-value analysis are unchanged. -- **Built-in HTML responses:** add missing styled pages for 200, 400, 408, 410, 413, 500 and 502. Any other built-in HTTP status uses the same compact dark appearance as the established 401/403/404/429/503/504 templates. Administrators' custom HTML remains authoritative, and 204/205/304 remain bodyless. -- **GeoIP:** freeze loaded CSV arrays to exactly sized boxed slices; pack IPv6 range addresses in network byte order (34 bytes versus 48 bytes per record) without changing lookup semantics. Expected decrease in retained GeoIP allocations is workload-dependent, approximately 17 MiB for the reported 293,569 IPv4 and 284,256 IPv6 records; verify actual RSS separately. -- **Cache fills:** avoid a duplicate body allocation when a transformed response supplies a single uniquely owned frame with known, tightly sized capacity. Preserve the existing bounded Vec-based streaming/fallback path and charge the shared backing capacity to HOT. This targets temporary peak allocations, not a guaranteed reduction of steady-state RSS. -- **Docker VHosts:** display a neutral waiting state while the first agent observation is pending after restart, then the result from background discovery. Opening Docker VHosts reads the latest cached status even with per-page Live view off; first-result synchronization stops afterward. A configured TLS fingerprint no longer falsely signals a verified connection. Agent polling and security remain unchanged. -- No settings/SQLite schema migration or change to the HOT limit. **Runtime acceptance gate remains open** until Cargo build/tests, browser, H1/H2/H3 and real-process resource tests pass. - -## Previously in 0.31.7 - -- **Source-package cleanup:** old per-version audits and implementation reports may be omitted; preflight still requires active sources and docs embedded in the panel Reader. This fixes the removed `docs/V0.25.5_AUDIT.md` preflight error without restoring obsolete files. -- **Settings → Rules & headers:** the global User-Agent blocklist, global custom header editor and client response settings now share a Cache-style tabbed page (three persistent sub-tabs). Settings → Security keeps sessions/password and host access policies. -- **Vhosts → Traffic policy:** optional User-Agent-specific per-client-IP request limits. First matching case-insensitive substring or `*` wildcard rule overrides the default/country rate for that request, even when ordinary throttling is disabled. Each vhost, UA rule and IP has independent bounded fixed-window accounting. Up to 64 rules per vhost; patterns are validated and the feature is opt-in. -- **Revision history:** show changed leaf fields rather than object-sized JSON previews, including stable IDs for array members. Full before/after JSON is available through a separate comparison dialog. Restore behavior unchanged. -- **Listeners:** space apart HTTP/2 and HTTP/3 tuning shortcuts. **ACL profiles:** “Attached to” is initially collapsed but expandable. -- **`update.sh`:** retain exactly two installed binary release directories (including `current`) and three private pre-update backups. Correct the previous mtime-sort path deletion bug; old releases were only printed, not actually deleted. After a successful update, optionally purge only incremental Cargo state unused for 30+ days, never fingerprint, dependency, build-script or compiled executable artifacts. A source-tree upgrade retains its own backups and target directory. -- **Graceful managed update:** trace marker → systemd stop → forced best-effort HOT→COLD handoff when COLD is enabled; record completion, even when zero items were demoted. Existing update flush works regardless of `cold_on_shutdown`; no persisted schema or cold format change. -- **Compatibility:** retains all 0.31.6 H3/iPhone diagnostic fixes, 0.31.5 mobile Logs and protocol-independent cache behavior. SQLite schema 9 unchanged. Rust/Cargo and live reload/update verification remain mandatory. See [test report](docs/TEST_REPORT.md) and [configuration](docs/SETTINGS.md). - -## Previously in 0.31.6 - -- HTTP/3 full-navigation GET/HEAD receive-stream handling, client cancellation classification and diagnostic events. Keep logo behavior unchanged. See [HTTP/3](docs/HTTP3.md) and [test report](docs/TEST_REPORT.md). - -## Previously in 0.31.5 - -- Add bounded **User-Agent blocking** globally under Settings → Security and per vhost under Vhosts → Security. Matching is case-insensitive; plain text matches a substring and `*` is a wildcard. Global and per-vhost rules are cumulative and run before opening an upstream. -- Make Traffic table/chart values directly copyable, including User-Agent, IP, host/path and other displayed dimensions, to speed up blocklist creation and investigation. -- Add **Advanced parser · repeated values** in Stored Logs. It counts repeated values for `user_agent`, `client_ip`, `host`, `path` or any supported parsed field across the bounded matching scan before pagination. -- Add a default-enabled **Consolidate rotated logs per vhost** switch. Active `vhost.jsonl` and matching `vhost.jsonl.*.gz` rotations are queried as one logical vhost source while retaining individual source IDs internally. -- Preserve SQLite schema 9 and existing saved configurations through serde defaults. See [Stored Logs](docs/FILE_LOGS.md) and [test report](docs/TEST_REPORT.md). -- Make `/logs` usable on phones with expandable log cards, full parsed-field details and no global horizontal scrolling; dynamic columns remain available. -- Treat H3 peer-cancelled pre-header requests (e.g. F5 navigation) as stream cancellations, not fabricated HTTP 400 results. This corrects error reporting and avoids redundant internal response resets; it does not yet prove the reported mobile white-screen symptom resolved. -- Keep both memory and cold cache across per-vhost H2/H3 switches; changing frontend protocols alone does not invalidate cached representations. -- Keep optional Administration **public panel HTTPS origin** for the trusted-proxy Origin/CSRF security context; **additional DNS hostnames** are a separate Host allowlist, not its replacement. -- **Release gate remains open:** Rust/Cargo compilation and real proxy/browser acceptance are still required before production deployment. - -## Previously in 0.31.4 - -- Fix intermittent `auth.invalid_origin` on the **administration panel only** behind a trusted reverse proxy. The optional `administration.public_origin` pins the browser-facing HTTPS origin and allows a bounded `X-Forwarded-Proto: https, http` chain when the immediate proxy peer is trusted. Without a pin, ambiguous forwarded-proto values still return HTTP 403. -- Set **only** `administration.public_origin`, for example `https://panel.example.test`, in Settings → Administration. Its DNS hostname is automatically included in the panel Host allowlist; it does **not** need to be repeated in `allowed_hosts`. -- The advanced **Additional allowed DNS hostnames** section remains optional. Older configurations with a duplicate pinned hostname still load, and editing/saving omits that duplicate while preserving independent entries. Removing or changing the pinned URL never silently keeps the previous hostname implicitly allowed. -- Keep strict checks for foreign `Origin`, mismatched or duplicate `X-Forwarded-Host`, cross-site requests, CSRF and untrusted proxies. The narrow opt-in self-admin session-cookie path may use the derived hostname only under its existing local-listener-risk conditions. Public proxy traffic and SQLite schema 9 are unchanged. -- Restart RustProxy after modifying the setting. See [Administration HTTPS](docs/SETTINGS.md) and [test report](docs/TEST_REPORT.md). -- **Release gate remains open:** Rust/Cargo compilation, resolved dependency lockfile and real mobile/reverse-proxy acceptance are still required before production deployment. - - -## Previously in 0.31.3 - -- Correct mobile tab/BFCache recovery: share in-flight session validation across resume events and defer SSE/polling until verification succeeds. A failed check keeps a visible retry UI instead of reloading the page, and 401 still requires a fresh login. -- Preserve the one-time initial Origin reload guard until **full** panel initialization succeeds. Authenticated dashboard navigation rejected for invalid Origin now returns a visible 403 HTML explanation rather than raw JSON; API requests retain JSON errors and the same Origin/Sec-Fetch-Site authorization policy. -- Log bounded, credential-redacted Origin mismatch diagnostics (HTTP method, path, public host/scheme, fetch site and trusted forwarding metadata). No cookies, authorization headers, query strings or session tokens are logged. -- New deterministic UI tests cover simultaneous visibility/BFCache events, session errors, manual recovery, stale session epochs and repeated transitions. No configuration or database migration; SQLite schema 9 and proxy traffic remain unchanged. -- **Release gate remains open:** Rust/Cargo were unavailable in the validation environment, and the inherited 0.31.2 Cargo.lock still requires a real `cargo update`/build on Rust 1.99+. Reproduce mobile behavior in Safari/Chrome through the actual reverse proxy before deploying to production. - -## Previously in 0.31.2 - -- Refresh Rust dependencies to current stable API families, including SQLx 0.9, Rand 0.10, Argon2 0.6, rcgen 0.14, Tower HTTP 0.7, Brotli 9 and Sysinfo 0.39. Retain SQLite schema 9, serialized settings, TLS/QUIC behavior and administration contracts. -- Raise the minimum compiler version to Rust 1.99 for current dependency requirements, including Docker and RouterOS build images. -- **Release gate open:** the updated dependency graph must be resolved with Cargo; the bundled lockfile is a prior-resolution snapshot and must be refreshed and tested before release. The source package is not production-verified. -- **Complete the dependency upgrade on a Rust 1.99+ build host** with `bash scripts/resolve_dependencies_0312.sh` (registry and the pinned Swagger UI asset must be reachable, or supply an audited local archive). This calls `cargo update`, verifies the refreshed lockfile and runs formatting, clippy, build and tests. Review the lockfile diff and fix any Rust compiler errors before publishing. - -## Previously in 0.31.1 - -- Improve mobile panel startup via an ordered JavaScript resource, versioned URLs and retry of temporary config failures. - -## Earlier panel improvements - -- Improved mobile panel loading behind reverse proxies: one ordered JavaScript resource, versioned assets and retry of temporary configuration errors. - -- **Panel startup/mobile follow-up:** replace the former white loading state with a RustProxy spinner, an automatic delayed reload suggestion and an error screen with reload link. Paint the shell before waiting for preferences/configuration/initial status; the header's **Refresh now** action sits beside the Overview title without adding a row. Negotiate Zstd/Brotli/gzip for fixed panel HTML and assets, with one-time encoding per resource; preserve public login and identity clients. -- Refresh Overview through a shared, authenticated SSE channel with per-administrator live pacing (1, 2, 3, 5 or 10 seconds; default 3); fallback status/log polling is active only after SSE failure. Detailed system diagnostics remain in the separately scheduled API; live status carries a small cached CPU/RSS/network chart snapshot. -- **Settings → Appearance → Data refresh** now stores per-account Live enable/disable and independent Traffic, Cache and System information intervals. Manual mode (`0`) and the global Live switch disable automatic requests while preserving manual refresh, charts, filters and pagination. -- Poll only the current Traffic/Cache view, and fetch system diagnostics independently from `/api/system-info` when Overview or the relevant Settings section needs them. The previous full `/api/status` response remains supported, while `?view=live` provides compact fallback. -- Centralize dashboard polling with cancellation/in-flight guards, hidden-tab pause and logout cleanup; retain SQLite schema 9, previous preferences, proxy traffic and the existing authentication model. -- Rust compilation and live wire/performance acceptance remain required on an equipped target; see [verification report](docs/TEST_REPORT.md). - -## Previously 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. -- 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. -- The previous 0.29.7 improvements remain included. SQLite schema 9 and Cold Cache format 3 are unchanged. - -## Previously in 0.29.7 - -- Configure active/passive monitoring per backend independently from backend enablement and balanced-pool eligibility. Optional per-member active TCP/HTTP health-check overrides inherit the vhost's HTTP Host when omitted. An unmonitored internal 404 backend remains routable but is no longer probed or passively ejected. -- Add global and per-vhost **HIT-only cache diagnostics**. Suppress X-Cache, X-Cache-Hits, X-Cache-Reason and Cache-Status on MISS/BYPASS, while retaining diagnostic headers on actual HIT. The separately configured Via header still applies; internal metrics are unaffected. -- Detect and warn about Web Services sharing the same PHP-FPM endpoint with different PHP_VALUE or PHP_ADMIN_VALUE settings. Shared PHP-FPM worker processes can retain dynamic ini settings between requests (including open_basedir), so WordPress/Roundcube must use distinct PHP-FPM pools/sockets when their policy differs. See [PHP-FPM isolation](docs/PHP_FPM_ISOLATION.md). -- Add configuration/Rust source regressions for per-backend monitor overrides, HIT-only cache policy and cache revision stability. Existing persisted settings load with their prior defaults; SQLite schema 9 and Cold Cache format 3 remain unchanged. -- Cargo and live PHP-FPM tests remain required before production deployment. - -- Expand per-service and shared-profile PHP-FPM editor help: field-by-field examples, FastCGI `NAME = value`, `PHP_VALUE` and `PHP_ADMIN_VALUE` syntax, and dedicated WordPress/Roundcube pool guidance. Fix checkboxes/labels and add explanatory info tooltips for PHP execution, PATH_INFO and Authorization forwarding. See [PHP-FPM settings](docs/PHP_FPM.md). -- Add opt-in **vhost origin-error interception** (Vhosts → Pages) with selected 400/404/5xx statuses. RustProxy replaces application HTML with a configured vhost/global page or a built-in page; the application's HTTP error status is always preserved, final internal fallbacks have precedence, and intercepted responses are `no-store`. Origin responses remain unchanged when interception is disabled, even if a global RustProxy error page exists. - -## Previously in 0.29.6 - -- Fix native Web Server/PHP-FPM response compression across HTTP/1.1, HTTP/2 and HTTP/3 by fusing the FastCGI response stream; prevent a `futures-util::stream::unfold` panic after EOF. -- When **Error file** is enabled, write warnings, errors, PHP-FPM stderr and worker panic diagnostics to the error file instead of duplicating them in systemd. When disabled, use the console. If the file cannot be written, fall back to console diagnostics. -- Add EOF, gzip and error-log routing regressions; actual Rust/Cargo build and Roundcube runtime verification are still required. - -## Previously in 0.29.5 - -- Fix PHP-FPM `PATH_INFO` dispatch for existing scripts such as Roundcube 1.7 `/static.php/skins/...`: when PATH_INFO is enabled, invoke the script before `try_files` can fall back to `/index.php`. -- Keep FastCGI `SCRIPT_FILENAME`, `SCRIPT_NAME`, `PATH_INFO`, `REQUEST_URI` and the query string accurate, and check access rules on the resolved PHP script path. -- Add mock FastCGI regression tests proving that Roundcube static assets reach `static.php`; opt-out (`path_info: false`) retains the previous fallback behavior. -- No database migration or saved Web Service configuration changes. Rust compiler/runtime verification is still required before production deployment. - -## Previously in 0.29.4 - -- Reuse named PHP-FPM profiles across Web Services, or keep per-service PHP-FPM settings; edit profiles in the expandable Web Server modal. -- Open the same PHP-FPM profile manager from the **PHP-FPM profiles** button beside **New Web Service** on the Web Server page. -- Run vhost RustProxy cache and enabled response transformations for Web Server filesystem/PHP sources with normal response privacy protections. -- Preserve the original request Host in FastCGI `HTTP_HOST` for WordPress, including HTTP/2/HTTP/3 authority-only requests. -- Added regression tests; the Rust compiler/runtime gate is still required before production deployment. - -## Previously in 0.29.3 - -- Fix internal error fallback for empty GET/HEAD requests over HTTP/2 or HTTP/3 with unknown body-length hints by safely checking if the body stream is empty; nonempty and unbounded bodies remain protected from unsafe replay. -- Preserve the final status, headers and HTML returned by the internal fallback location, including its own 404 page, instead of replacing it with RustProxy's global error page. Warn when a named internal target cannot be found. -- Add Rust regression tests for empty H2/H3 GET/HEAD, unknown-length request bodies and response precedence. These Rust tests require a Cargo-enabled environment before production deployment. - -## Previously in 0.29.2 - -- Add Nginx-style **internal error fallbacks** for the vhost source and Custom locations. Configured upstream/Web Server statuses can internally hand the original method, URI, query string and request context to an `internal` Custom location without an HTTP redirect. Supported bounded statuses are `403/404/500/502/503/504`; configuration cycles are rejected and runtime dispatch has a depth limit. -- Execute error fallback before an intercepted response is finalized or cached. The target location re-enters the normal vhost/location pipeline, including ACL, headers, compression, cache policy and response processing. Request bodies are replayed only when safely bounded and replayable; otherwise the original error is retained. -- The main Web Server source ignores backend health/unavailability; Custom Locations configured for Reverse Proxy continue to use their backend pool. Error-fallback-specific Prometheus counters have been removed. -- Add streaming **multipart byte-range** responses for requests such as `Range: bytes=0-99,200-299`, with a maximum of 16 ranges and identity representation for multipart responses. -- Add opt-in PHP-FPM **PATH_INFO** splitting. With `path_info: true`, `/index.php/foo/bar` securely resolves the existing `index.php` script, keeps `SCRIPT_FILENAME` on that file and sends `/foo/bar` as `PATH_INFO`. - -0.30.0 keeps SQLite schema 9 and Cold Cache format 3. Existing configurations remain loadable; new optional health and diagnostic fields receive backward-compatible defaults. Historical details are in [CHANGELOG.md](CHANGELOG.md). - -### Historical 0.26.7 release notes - -- Show explicit HOT and COLD cache occupancy progress on `/cache`, including object-count and byte/disk limits from Cache settings plus percentage usage. -- Preserve compatible upstream client pools across unrelated active-snapshot rebuilds so generation age, request count and `Rotations` diagnostics do not restart at zero unless the backend connection policy actually changes. -- Keep age-based backend rotation in the maintenance loop and retain request-count rotation; compatible snapshot reuse now makes the existing counters observable over long-running processes. -- Refresh container documentation and ship the OCI image with one pinned Certbot environment containing all RustProxy-supported DNS plugins: Cloudflare, OVH and RFC2136. -- Review current installation/container/cache/backend lifecycle documentation for stale version or behavior statements. -- Keep SQLite schema 8 and the saved proxy configuration format unchanged; upgrading from 0.26.6 requires no data migration. - -0.26.7 retains the 0.26.6 access-policy and administration fixes below. - -### Retained 0.26.6 release notes - -- Keep backend diagnostics visible-column selection, sort key and sort direction synchronized with the account-scoped administrator preferences whenever preferences are loaded or updated. -- Clear the ACME certificate-creation form after the issuance job finishes successfully, so successful certificates do not leave stale values while failed jobs keep the non-secret form values available for correction. -- Compose explicit IP allows and **Allow only** countries as alternatives inside one ACL policy scope. Explicit IP denies and blocked countries still win, and profile/vhost/location scopes remain cumulative. This makes `default=deny` support private CIDRs **or** selected countries as intended. -- Document the sibling `migrate_npm_core.py` and `migrate_npm_api.py` modules required by `scripts/migration/migrate_npm.py`, and fix the container example to mount the dedicated `scripts/migration` directory. -- Detect the Linux runtime environment in Process diagnostics. LXC reports `Linux / LXC` and overrides system uptime with PID 1 age plus load average from the container-visible `/proc/loadavg`; VM/host/Docker and other Linux environments keep the existing host statistics path. -- Keep SQLite schema 8 and the saved proxy configuration format unchanged; upgrading from 0.26.5 requires no data migration. - -0.26.6 retains the 0.26.5 interface-scaling and listener-navigation fixes below. - -### Retained 0.26.5 release notes - -- Keep compact toolbar `select` controls on the same scaled box model as neighboring inputs, including Stored Logs, Backends, Docker, vhost-style lists and Settings filters. Form-control dimensions consume the interface-scale field variables consistently. -- Make **Streams -> Add TCP/UDP listener** navigate to **Settings -> Listeners** instead of opening a new Stream configuration dialog. -- Keep SQLite schema 8 and the saved proxy configuration format unchanged; upgrading from 0.26.4 requires no data migration. - -### Retained 0.26.4 release notes - -- Extend session-only editor drafts to **404 hosts**, preserving the complete unfinished form state when another editor is opened or the dialog is minimized. -- In **Settings -> Appearance**, the Overview list shows clean card names and visibility/reorder actions without type badges; the draggable visual editor still identifies numeric cards, charts, tables and diagnostic panels. Appearance is the first Settings tab on a new browser, then the last active tab is remembered; explicit links such as `/settings?tab=cache` always win. -- Independently of Dark, Light, Ultra Dark and Cream, administrators can choose **Corner rounding** (Original, Square, Subtle, Rounded or Extra rounded). Original matches the established geometry in every theme and is the default for existing accounts. Ultra Dark preserves the original coral/soft-green logo mark while keeping its near-black background. -- Complete the Polish translation for **Settings -> Appearance -> Button style** and keep the button-size preference independent from global interface scale. -- Keep search/filter inputs in compact toolbars no taller than the active button style, including Backends, vhost-style lists, Docker, Logs and Settings search controls. -- Reuse the vhost Enabled control presentation in 404 hosts, Redirects and Streams so modal enable/disable state is visually consistent. -- Replace the tall Overview-card checkbox list in Appearance with a compact responsive tile grid; each tile toggles visibility and retains reorder controls. -- Run time-based upstream-client pool recycling from backend maintenance as well as request dispatch, so an idle backend rotates after its configured age instead of remaining on generation 0 until the next request. -- Keep SQLite schema 8 and the saved proxy configuration format unchanged; upgrading from 0.26.3 requires no data migration. - -### Retained 0.26.3 release notes - -- Make interface scaling truly global: the 70-125% setting now scales the authenticated dashboard, navigation, dialogs, overlays, draft dock and other top-level administration UI consistently. Form fields receive an additional proportional size adjustment so inputs/selects/textarea do not remain visually oversized at low scale or undersized at high scale. -- Tighten the Overview metric cards so wide dashboards fit more telemetry with less empty vertical space. -- Expand chart presentation presets to Default, Soft, Technical, High contrast, Neon, Monochrome, Warm and Cool. Presets now alter palette, surface treatment, grid character, line weight and chart contrast instead of changing only stroke shape. -- Restyle the bottom-right draft dock as a square-corner panel that follows the existing RustProxy card/navigation language instead of using a rounded glass/floating treatment. -- Let the draft dock collapse to one bar and show the age of every suspended draft. -- Add Settings search across translated section labels and settings content, per-section Appearance resets, and a full Appearance reset. -- Add visual palette buttons for all chart presets, independent table/log density, independent dashboard density, and an Ultra compact option for both data tables/logs and Overview cards. -- Add a per-user Button style selector: **Normal** keeps the refreshed buttons and **Compact** restores the smaller source-style button sizing independently from interface scale. The selection now applies consistently to generated vhost actions and tab buttons inside dialogs. -- Let administrators hide/show and reorder the Overview telemetry cards from Appearance without changing metric collection. -- Keep all new appearance choices account-scoped in the existing SQLite administrator-preferences document; schema 8 and the saved proxy configuration format remain unchanged. - -0.26.3 includes the 0.26.2 administration workflow improvements below: - -- Add session-only editor drafts for Web Server, Vhosts, Redirects and Streams. Closing an edited form minimizes it into a bottom-right draft dock, while opening another item starts a clean independent editor. -- Add **Settings -> Appearance**. Theme, language, interface scale, 12/24-hour time display and chart style are account-scoped preferences stored in SQLite; the scale slider now spans 70-125%. -- Apply time-format preferences to dashboard timestamps and compact expanded Stored Logs details to reduce scrolling on large records. - -- Merge the Stored Logs and Live Logs top-bar actions into one **Logs** menu. -- Replace the Stored Logs filter row with an optional log-source selector, one query field and page-size control. Search accepts Kibana-style field clauses such as `req: GET AND !country: Poland`, quoted values, wildcards and numeric comparisons. -- Parse JSON/JSONL records into dynamic selectable columns discovered from returned fields instead of maintaining fixed protocol-specific columns. `matched_by` and `sni` are no longer hard-coded table columns, but remain available when present in a record. -- Parse the built-in RAW error-log format into timestamp, level, target, message and detected `key=value` fields while retaining the original raw line. -- Expand selected records as parsed field/value rows with optional raw JSON, and move the column picker to the upper-right edge of the result table. -- Remove the Configured files inventory while keeping an optional source selector; the explorer searches all bounded configured log sources by default and narrows to one selected source when requested. -- Replace backend sort text (`A-Z` / `Z-A`) with direction arrows (`↑` / `↓`). -- Keep SQLite schema 8 and the saved proxy configuration format unchanged; upgrading from 0.26.1 requires no data migration. - -Historical release information remains in [CHANGELOG.md](CHANGELOG.md) and [docs/CHANGELOG_TECHNICAL_ARCHIVE.md](docs/CHANGELOG_TECHNICAL_ARCHIVE.md). Current verification status is maintained in [docs/TEST_REPORT.md](docs/TEST_REPORT.md). - -## Documentation - -- `/docs` - authenticated project documentation Reader plus the API/OpenAPI tab. -- [docs/README.md](docs/README.md) - canonical documentation index. -- [docs/WEB_SERVER.md](docs/WEB_SERVER.md) - Web Server configuration, HTTP behavior and filesystem security. -- [docs/FILE_LOGS.md](docs/FILE_LOGS.md) - stored-log explorer, protocol-error records, bounds and filesystem security. -- [docs/PHP_FPM.md](docs/PHP_FPM.md) - implemented FastCGI/PHP-FPM integration, safety model and runtime requirements. -- [docs/TEST_REPORT.md](docs/TEST_REPORT.md) - verification results and environment limitations. - -## Release history - -Historical user/operator-visible changes are maintained in [CHANGELOG.md](CHANGELOG.md). Detailed historical implementation notes are retained in [docs/CHANGELOG_TECHNICAL_ARCHIVE.md](docs/CHANGELOG_TECHNICAL_ARCHIVE.md), and completed roadmap milestones are retained in [docs/ROADMAP_HISTORY.md](docs/ROADMAP_HISTORY.md). - -## Core capabilities and references - -Managed certificates imported into the library renew automatically unless disabled. Initial issuance/import remains explicit and staging-first; imported PEM alone is not auto-managed. See [renewal and recovery](docs/RENEWAL.md). - -Read [installation and update](docs/INSTALL.md), [host access and locations](docs/VHOST_ACCESS.md), -[migration](docs/MIGRATION.md), [NPM migration](docs/NPM_MIGRATION.md), [ACME providers](docs/ACME_PROVIDERS.md), -[Prometheus](docs/PROMETHEUS.md), [cache diagnosis](docs/CACHE_DIAGNOSTICS.md), -[HTTP/3 scope](docs/HTTP3.md), [TCP/UDP streams](docs/STREAMS.md) and [container deployment](docs/CONTAINER.md). Cloudflare Tunnel remains deferred. Docker discovery is available through the standalone agent; see [Docker discovery](docs/DOCKER_DISCOVERY.md). -Examples are disposable full configurations: applying one replaces, not merges, -an existing document. - -## Installation and source build - -### Official package repositories - -The preferred installation/update path is the project-maintained package repositories: - -- **DEB/RPM packages:** `https://rustproxy.linuxiarz.pl/` -- **OPNsense plugin:** `https://rustproxy-opn.linuxiarz.pl/` - -Use the repository instructions appropriate for the target operating system. Package/service installs do not need a packaging-time GeoIP download: the proxy starts an enabled-by-default background updater after its listeners bind, performs the first verified refresh immediately, and then checks again every 24 hours unless changed in **Settings -> GeoIP updates**. Building on Debian is kept as an optional source/development path rather than the primary installation method. - -The production CLI default data directory is `/var/lib/rustproxy`. `dev.sh` explicitly overrides it to the repository-local `./data` directory for development commands. - -### Optional: Debian 13+ one-line source install/update - -The bootstrap installs the compiler/build dependencies and Rust, clones this repository and -builds the locked release. With no argument it runs in `auto` mode: a host without a managed -RustProxy installation is installed, while an existing `/opt/rustproxy/install.json` triggers -the safe `update.sh` path with backup, health check and rollback. First install starts HTTP on -`0.0.0.0:80`, HTTPS on `0.0.0.0:443` and keeps the administration panel on loopback HTTP -`127.0.0.1:9090`. Updates preserve the saved listener and service settings. Use an SSH tunnel for -initial remote access, then enable Administration HTTPS before exposing a non-loopback admin bind. - -```bash -# automatic: install on a new host, update an existing managed installation -wget -qO- https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/install-debian13.sh | bash - -# explicit update -wget -qO- https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/install-debian13.sh | bash -s -- update -``` - -Manual build prerequisites used by that bootstrap: - -```bash -apt install -y build-essential gcc git ca-certificates python3 pkg-config wget sudo python3-certbot-dns-rfc2136 python3-certbot-dns-cloudflare -wget -qO- --https-only --secure-protocol=TLSv1_2 https://sh.rustup.rs | sh -``` - -Rust stable (declared minimum 1.88), Cargo, C compiler/linker, Bash and Python 3.10+. -Linux is the primary deployment target; -macOS/WSL2 need normal native build tools. Detailed OS diagnostics currently use -Linux procfs and cgroup v2. Unsupported measurements remain null/N/A. - -The first build needs access to the Rust toolchain and crate registry. `dev.sh` -never installs system software or runs sudo. Rustls handles runtime TLS; zstd's -native dependency still needs a C build toolchain. - -The Debian bootstrap intentionally builds with one Cargo job, LTO disabled and -`codegen-units=8`. This avoids `rustc` being killed by the OOM killer on small VPSes -while leaving the repository's normal release profile unchanged. For a manual -low-memory build use: - -```bash -CARGO_BUILD_JOBS=1 CARGO_PROFILE_RELEASE_LTO=false \ - CARGO_PROFILE_RELEASE_CODEGEN_UNITS=8 cargo build --release --locked -``` - -Package-build metadata remains outside this source tree and is maintained with the official package repositories above. - -The 0.28.1 source archive preserves the resolved `Cargo.lock` supplied with the source tree and -updates only this package's own version entry. This packaging environment has no Cargo, so the -dependency graph was not re-resolved here. On the compiler-enabled deployment host, run the -locked check/test/release gates below and retain the verified lockfile. `dev.sh` can generate a -lockfile only when one is genuinely absent; it does not replace normal locked verification. - -```bash -cd RustProxy -bash dev.sh doctor -bash dev.sh check -bash dev.sh test -bash dev.sh -``` - -| Service | Development default | +| Area | Available features | |---|---| -| Default HTTP | `http://127.0.0.1:8080` | -| Default HTTPS | `https://localhost:8443` | -| Administration (default) | `http://127.0.0.1:9090`; HTTPS/remote bind is opt-in in Settings | -| Optional protocol fixture | `127.0.0.1:9000`, only with `RUST_PROXY_DEMO=1` | +| Reverse proxy | HTTP/HTTPS origins, HTTP/1.1 and HTTP/2 upstream modes, health checks, load balancing, connection reuse, WebSockets, path-based routing, retries and error fallbacks | +| Frontends | HTTP, HTTPS, HTTP/2, optional HTTP/3 (QUIC), named listeners, domain and wildcard routing, redirects, TLS certificates | +| Web Server | Static content, downloads, directory/index policy, rewrite and `try_files`, selected `.htaccess` rules, optional PHP-FPM, application presets | +| Access | IP/CIDR and DNS/DDNS rules, country restrictions, reusable ACLs, rate limits, method rules, Basic Auth, Tinyauth, security headers | +| Delivery | Optional shared HOT/RAM and COLD/disk cache, cache invalidation, compression, response transformations, static-file optimizations | +| Integrations | Docker discovery via separate agents, TCP/UDP Streams, ACME certificate providers, Prometheus, Grafana/Nagios examples and administration API | +| Operations | Dashboard presets, traffic analytics, live/stored logs, configuration history, encrypted backups and certificate reloads | -In a second terminal, print the one-time setup capability and use the browser, -or initialize an administrator locally: +Features and default settings vary by component; configure only the capabilities you need. The [documentation index](docs/README.md) covers each workflow, including prerequisites, security boundaries, troubleshooting and API access. -```bash -bash dev.sh admin setup-token -# Alternative: interactive account creation, no default credentials. -bash dev.sh admin init -``` +## Installation -There is no default username/password. Normal startup serves a neutral informational -page for an unconfigured address and does not seed sample vhosts. The self-signed -bootstrap certificate is retained under `data/certs` and is not publicly trusted. -Assign intended certificates explicitly. Public frontend TLS does not implicitly expose the separate administration listener. Administration remains loopback HTTP by default; enable its own HTTPS setting before choosing a remote bind. Existing databases are never reset by this default. +Use the maintained packages where available: -Use the protocol fixture only explicitly when running the documented test suites; -`RUST_PROXY_DEMO=1 bash dev.sh` seeds a new disposable database. The separate -`bash dev.sh mock` helper remains available for cache/backend testing. +- Linux DEB/RPM: https://rustproxy.linuxiarz.pl/ +- OPNsense: https://rustproxy-opn.linuxiarz.pl/ -For a system service, build as an ordinary user and follow the complete procedure -in [INSTALL.md](docs/INSTALL.md): +For Debian 13+ source installation, manual compilation, managed updates or Docker, see [Installation and updates](docs/INSTALL.md). -```bash -bash dev.sh release -bash install.sh --plan --binary target/release/rust-proxy -sudo bash install.sh --binary target/release/rust-proxy -# A subsequent release: -sudo bash update.sh --binary target/release/rust-proxy -``` +Typical managed Linux defaults: HTTP on `:80`, HTTPS on `:443`, local administration at `http://127.0.0.1:9090`, private data in `/var/lib/rustproxy`. The initial public certificate is self-signed. **There is no default administrator password.** Initialize administration locally and assign trusted certificates before public use. -Copy `.env.example` to `.env` for startup defaults; it is trusted shell code. -`RUST_PROXY_DEMO=0` disables the demo. `RUST_PROXY_TLS=0` removes the startup TLS -default used when bootstrapping listeners. +## First configuration -The release checker reads its compile-time defaults from repository-root `UPDATE_CHECK.toml`, -which is embedded in the final binary. It checks the configured `Cargo.toml` URL immediately -on startup and then every 3600 seconds by default. A failed request is ignored until the next -interval; it never blocks startup or proxy traffic and it never installs updates. On Unix-like -systems the checker keeps its bundled public roots and also imports certificates from -`SSL_CERT_FILE`, `CURL_CA_BUNDLE` and common Linux/BSD CA bundles, including the -FreeBSD/OPNsense `ca_root_nss` path. It follows at most five credential-free HTTP(S) -redirects and never downgrades an HTTPS check to HTTP. Failed checks stay invisible in the dashboard, preserving the existing footer -behavior, but are emitted as application `ERROR` events so the optional error-file sink can -capture them. Operators may override the embedded values with global CLI options or equivalent -environment variables: +1. Install and start the service, then initialize the administrator using the local CLI. +2. Open the panel and review **Settings → Listeners**, administration access and certificates. +3. Create a **Vhost** with a domain name and a reachable HTTP backend or **Web Service**. +4. Confirm frontend TLS, backend health, access permissions and routing. +5. Enable additional cache, compression, Docker or monitoring functions as needed; inspect **Overview**, **Logs** and **Cache** for results. -```text ---update-repository-url RUST_PROXY_UPDATE_REPOSITORY_URL ---update-version-url RUST_PROXY_UPDATE_VERSION_URL ---update-check-interval-secs RUST_PROXY_UPDATE_CHECK_INTERVAL_SECS -``` +## Guides -Set the interval to `0` to disable automatic checks. Debian/systemd installs may place the -environment overrides in `/etc/rustproxy/environment`; source/development runs may use the -normal process environment. +- [Installation](docs/INSTALL.md) · [Settings](docs/CONFIGURATION.md) · [Vhosts](docs/VHOSTS.md) · [Web Server](docs/WEB_SERVER.md) · [PHP-FPM](docs/PHP_FPM.md) +- [Certificates](docs/ACME_PROVIDERS.md) · [Cache](docs/CACHE.md) · [Response delivery](docs/RESPONSE_DELIVERY.md) · [Docker discovery](docs/DOCKER_DISCOVERY.md) · [Monitoring](docs/MONITORING.md) +- [Security](docs/SECURITY.md) · [API](docs/API.md) · [Troubleshooting](docs/TROUBLESHOOTING.md) +- [Changelog](CHANGELOG.md) · [Roadmap](ROADMAP.md) -Persisted frontend sockets live only in -`listeners.endpoints` and are edited under Settings -> Listeners. Listener address/TLS/H2/H3 -capability changes need a process restart; vhost H2/H3 switches only narrow those -capabilities and routing/pool policies otherwise use validated hot publication. +The authenticated **`/docs`** page includes the guides and a separate interactive **OpenAPI/Swagger** tab. -## Private vhosts and backend pools +## Status of this source archive -Use **Settings -> Listeners** to configure frontend sockets, then use **Vhosts** to -select those named listeners and configure backend members. `examples/vhosts-private.json` -binds its named listener only to `127.0.0.2` and uses two -explicit loopback origins. Replace these with addresses assigned to your machine -and your real services. `examples/vhosts-balanced.json` shows an opt-in public -80/443 deployment; do not apply it unintentionally. - -A private vhost is selected by hostname **and the accepted socket's actual local -IP, port and TLS state**. Sending its Host/X-Forwarded-* values to a different IP -does not select it. A shared wildcard socket may accept TCP at multiple addresses, -but each vhost keeps its original routing constraints. IP binding is not a firewall -or authentication: NAT/another proxy can still forward traffic to that private IP. -Keep network restrictions and application authentication. - -New health-checked members remain in `checking` until the configured successes -make them eligible. No healthy member means 503; a healthy backup may be selected. -HTTP checks measure time to response headers, not whole-body throughput. Affinity -cookies bypass shared cache; they contain only an opaque routing token and are not -login sessions. Existing cookie sessions may finish on a draining healthy member. -The keepalive controls are connection reuse, **not Keepalived/VRRP**. - -Save pool/routing/security changes live. New/removed sockets and TLS ALPN changes -need a process restart; active vs desired endpoints are visible in Settings/API. -Old top-level routes are converted once into vhosts during validated startup/import. -There is no separate legacy routing UI or dispatcher; path-to-origin ownership is -retained. Read the migration guide before updating an existing database. See [VHOSTS.md](docs/VHOSTS.md) -for algorithms, defaults, bounds, caveats and the complete configuration contract. - -## Standard ports without running as root - -Settings -> Listeners accepts multiple named endpoints and socket addresses such as `0.0.0.0:80`, -`0.0.0.0:443`, `[::]:443` and custom ports. Presets change only the port: they do -not silently replace a loopback interface with a public one. The admin listener is independent of these controls and stays loopback by default. In Settings -> Administration, enabling HTTPS permits a persisted non-loopback bind using either the application-managed certificate or a selected certificate. Plain remote HTTP remains available only with the explicit `--allow-remote-admin` startup flag. - -Linux low-port binding can require `CAP_NET_BIND_SERVICE`. Use a dedicated service -account and the example in [deploy/README.md](deploy/README.md), not a root-run -application. Port conflicts and OS permissions cannot be proved by merely saving -configuration; bind failures are reported on restart. See -[settings, defaults and rollback](docs/SETTINGS.md). - -## Cache and response controls - -The shared cache is enabled by default. Normal fresh `200 GET` and cacheable HEAD -requests use `s-maxage`, `max-age` or `Expires`; a configurable default TTL applies only -to explicit `public` responses without an origin freshness lifetime and defaults to -zero. GET/HEAD share the same frontend scheme + Host + exact URL object, response `Vary` selects -representation variants, and unrelated request headers or the selected backend-pool -member do not fragment that object. Since 0.19.1 HTTP and HTTPS use distinct primary -cache identities so scheme-dependent origin responses cannot cross protocols. Non-GET/HEAD methods pass without globally -purging unrelated cached objects. - -Cookies (except the configured static-asset exception), authorization, ranges, -`If-Match`/`If-Unmodified-Since`, private/no-store responses, Set-Cookie, `Vary: *`, -streaming events and failed/incomplete bodies are not reused. `If-None-Match` and -`If-Modified-Since` can be answered from a fresh cache entry as local conditional HITs. -Request `no-cache`/freshness constraints revalidate a stored validator instead of -blanket-bypassing the cache. Response `no-cache` with a usable validator may be retained, -but must validate before every reuse. Request/response `no-store` remains a hard exclusion. - -Default cache memory limits are 256 MiB HOT allocated/charged data, 16 MiB per object, 64 MiB transient RAM, a 32 MiB COLD writer queue, 4096 HOT entries and eight fill permits. The panel also offers memory-aware conservative/recommended/performance presets derived from the lower of detected host RAM and a finite cgroup memory limit; automatic presets are disabled below 256 MiB effective RAM. `max_object_bytes` remains an independent per-object ceiling and cannot exceed the global HOT budget. If it is configured above the transient budget, the panel warns that objects near that size will pass through uncached because a fill cannot reserve enough bounded transient RAM. Optional cold cache defaults to disabled; when -enabled it uses a bounded disk path under the private data directory and asynchronously -demotes LRU/idle hot objects. Cold objects survive process restart for the same -configuration revision and are promoted to RAM on a fresh hit. Large fill buffers grow -incrementally and consume multiple permits. Actual process RSS also includes in-flight -responses, compression buffers and runtime allocations. HIT/MISS/BYPASS, hit count and -standardized RFC 9211 `Cache-Status` diagnostics are independently optional; required `Age` advances on ordinary cache hits even when the origin does not send `Age`. A successful origin `304 Not Modified` starts a new freshness-age calculation, so the next `Age` may legitimately be `0`; optional `X-Cache-Resident-Age` tracks RustProxy residency across revalidation and tier movement. -Purge is authenticated, CSRF-protected, audited and invalidates both tiers plus older -fills. See [cache behavior and limits](docs/CACHE.md) and -[cache diagnostics](docs/CACHE_DIAGNOSTICS.md). - -Response compression is streamed and negotiated with `Accept-Encoding`; zstd, Brotli -and gzip default ON while deflate defaults OFF, and equal client quality uses -`zstd -> br -> gzip -> deflate` server preference. The selected algorithm is reported -in `Content-Encoding` with `Vary: Accept-Encoding`. Existing `Content-Encoding`, ranges, -SSE, upgrades and `no-transform` are passed through without recompression. Cookie, -Authorization, Set-Cookie, private and no-store headers do not by themselves disable -dynamic compression. Ordinary vhosts and Web Server services may inherit/enable/disable -compression and individual codecs. Docker publications expose a single Compression on/off switch -and always use the globally enabled codec set; explicitly disabling a configurable scope -hides/disables its codec selectors in the dashboard while preserving their saved values. -Strong ETags are weakened after transformation, and obsolete content digests are removed. -Server policy is applied after generic response rules. Protected framing/identity headers -and cache-owned `Age` cannot be edited through generic rules. - -With the separate demo enabled, `/cache-demo` returns an explicitly public 30-second -object. Enable cache and X-Cache in Settings, then issue this twice without cookies: - -```bash -curl -i -H 'Host: localhost' http://127.0.0.1:8080/cache-demo -``` - -The first eligible request is MISS, then HIT while fresh. The origin timestamp -stays unchanged on HIT. Unlike proxy/system measurements, this endpoint is explicitly -demo content. The reserved admin cookie is removed before eligibility; application cookies still -trigger privacy bypass. Use anonymous curl requests to check cache behavior. - -## Adding a language - -Create `langs/de_DE.json` with metadata and string translations, then rebuild and -restart. No JavaScript language switch or registry edits are needed: - -```json -{ - "meta": { - "locale": "de-DE", "name": "German", "native_name": "Deutsch", "direction": "ltr" - }, - "translations": { "settings": "Einstellungen", "overview": "Ubersicht" } -} -``` - -Missing keys fall back to `en_US.json`. Translation strings are rendered as text, -not HTML; locale and direction metadata also control formatting/RTL. The two -bundled languages are English and Polish. New packs are compile-time assets, not -untrusted runtime plugins. See [langs/README.md](langs/README.md). - -## Updating an installed source tree - -Read [MIGRATION.md](docs/MIGRATION.md). Back up actual data/certificates, stop the -old server and extract this archive into a separate, non-nested directory. From -the new source directory: - -```bash -rust-proxy --data-dir /actual/RustProxy/data backup create \ - --output /private/rustproxy-before-upgrade.rpb -bash upgrade.sh /actual/RustProxy --stopped -# Also pass --data-dir /actual/private/data when it is external. -cd /actual/RustProxy -bash dev.sh test -bash dev.sh -``` - -The supported operator backup is the password-encrypted `rust-proxy backup create` -command. `tools/legacy/backup.py` is retained only as an offline release-test helper and -must not be used as a Secret Store backup. See -[Secret Store and encrypted backup/restore](docs/SECRET_STORE_BACKUP.md). - -`.env`, data, unmanaged files and previous managed sources are preserved. Extra -language JSON files survive the update; shipped files replace their old names, -whose previous bytes remain in the backup. Unknown custom source edits are not -merged. Do not overlay individual source files onto an old tree. - -RustProxy 0.25.3 upgrades SQLite to schema version 8 by adding retained manual-configuration -history. Existing schema-7 data is migrated automatically; the current configuration is seeded -as the first retained history row and future successful saves keep the newest 512 revisions. -Vhost/location method and access-policy fields are additive and default to disabled/inherited -behavior when omitted. Once a 0.25.3 configuration containing the new fields is saved, an older -binary may reject unknown configuration fields. Roll back using the pre-upgrade data backup as -well as the old source, not just the binary. - -## Source helper layout - -`script` responsibilities are intentionally separated: - -- `scripts/runtime`, `scripts/build`, `scripts/deploy`, `scripts/migration` are coupled to application/build/deployment behavior; -- `tools/dev`, `tools/audit`, `tools/legacy` are optional operator/development helpers; the removed `tools/checks` suite is not shipped; -- `integrations/` contains external platform helpers such as OPNsense ACME and RouterOS OCI tooling. - -See `scripts/README.md`, `tools/README.md` and `integrations/README.md`. - -## Documentation and checks - -[documentation index](docs/README.md), [API](docs/API.md), [architecture](docs/ARCHITECTURE.md), -[security](docs/SECURITY.md), [transport](docs/TRANSPORT.md), -[vhosts and pools](docs/VHOSTS.md), [Web Server](docs/WEB_SERVER.md), [settings](docs/SETTINGS.md), [cache](docs/CACHE.md), -[roadmap](ROADMAP.md), [changelog](CHANGELOG.md), [verification](docs/TEST_REPORT.md). - -```bash -# Source preflight without Rust: -./dev.sh doctor -# Cargo validation and the Rust test suite (Rust required): -./dev.sh check -./dev.sh test -# Source-only package, with no Git checkout required: -python3 make_zip.py /tmp/RustProxy-0.33.0.zip -``` - -## New management and test-origin guide - -See [MANAGEMENT.md](docs/MANAGEMENT.md) for the certificate staging workflow, -page/status behavior and logs; [MOCK_SERVER.md](docs/MOCK_SERVER.md) provides a -ready-to-use cache/health test setup. The example `examples/mock-vhost.json` is a -complete minimal document for a fresh test installation, not a merge patch. - -## Authentication and mock development - -In **Vhosts -> Authentication**, enable Tinyauth and supply its base URL (or the -complete `/api/auth/nginx` endpoint) and public login URL. Private verifiers need -the explicit private-network permission; optionally constrain allowed networks. -Default identity headers are Remote-User and Remote-Email. No embedded provider -password or disabled TLS verification is required. Do not protect the login host -with itself. See INTEGRATIONS_040.md for exact status/redirect behavior and limits. - -```bash -# Separate terminals, after building the real proxy on a toolchain-enabled host: -bash dev.sh mock --port 9001 --name origin-a --ttl 30 -bash dev.sh mock-auth --port 9300 -``` - -The fake-auth server accepts the intentionally insecure cookie `mock_auth=allow`; -it is a loopback-only development peer, never an authentication product. - -### Docker vhosts operational controls - -Docker vhosts now publish/unpublish immediately per service. Each Docker agent has a configurable inventory polling interval, while the optional Live view refreshes cached runtime state only. Docker-backed applications can use ACL profiles/inline ACLs and per-vhost proxy connect/header/read/send timeouts, matching the ordinary vhost proxy controls. - - -## Dashboard layout export (0.33.1) - -To share a preferred Overview arrangement for a future factory preset, open the authenticated -Overview and run this in the browser developer console (no privileged database access): - -```javascript -(async () => { - const response = await fetch('/api/preferences', { credentials: 'same-origin' }); - if (!response.ok) throw new Error(`Preferences HTTP ${response.status}`); - const prefs = await response.json(); - const exportData = { - dashboard_widgets: prefs.dashboard_widgets, - dashboard_hidden_widgets: prefs.dashboard_hidden_widgets, - dashboard_density: prefs.dashboard_density, - overview_samples: localStorage.getItem('rustproxy.overview.liveChartSamples') - }; - console.log(JSON.stringify(exportData, null, 2)); -})(); -``` - -Copy the JSON shown in Console using the browser's selection or context menu. -Do not export the full preferences response or session cookies. This is an export -for review, not an automatic factory-default change; existing account layouts -remain unchanged. - -The original Cache HIT donut shows accumulated cache counters; the optional recent -Cache HIT chart displays interval-delta counters. They intentionally measure -different windows and can produce different percentages. +This archive is **source code, not a production-tested executable**. Some 0.34.0 changes, particularly proxy-path and caching optimizations, have not completed compilation, full regression testing or controlled performance measurements in the supplied evidence. Feature descriptions reflect the included code and settings; they do not constitute a runtime acceptance certificate. Validate the exact build and your TLS, routing, access, cache, logging and backup/restore setup before deployment. No measured throughput increase is claimed for 0.34.0. diff --git a/ROADMAP.md b/ROADMAP.md index 4dcf000..c99f535 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,728 +1,22 @@ # RustProxy roadmap -## 0.34.0 - Scoped cache correctness and proxy body hot path +This file records **work still requiring acceptance or planned future consideration**, rather than repeating completed release history. Implemented functionality is described in the [documentation](docs/README.md), and operator-visible changes by release are in the [changelog](CHANGELOG.md). -- [x] Compile supplied OPT4 binary; run real HTTP regression (151 passing, - 0 failed, 1 skipped) and a short OPT3/OPT4/nginx 1,040-byte diagnostic run. -- [x] OPT5 source: remove per-request SHA-256 while retaining CSPRNG entropy, - skip Tinyauth-only original URL work when Tinyauth is absent, avoid a pooled - backend success-detail allocation, batch analytics processing, use borrowed - status/UA labels, avoid scoped compression resolution for unaccepted codecs. -- [x] Preserve exact forwarding and analytics semantics with new Rust tests; - retain 0.34.0, TLS/H2/H3, WebSocket, cache, ACL, logs and bounded queues. -- [ ] Compile and test OPT5; run full real-protocol regression and >=7 - 30-second CPU-isolated A/B/nginx runs before accepting a throughput gain. -- [x] OPT4 source candidate: borrow original no-rewrite public target, - bypass the request body adapter only for bodies reporting end-of-stream, - avoid redundant upstream URI refcount operations, and simplify common - forwarding/header/log branches without weakening validation. -- [x] Add OPT4 regression tests for body accounting and permits, Unicode, - URI identity, and safe Connection header filtering. +## 0.34.0 — outstanding release verification +- Compile the exact 0.34.0 source candidate on the supported target environments and run the current Rust regression suite. +- Exercise full HTTP/1.1, TLS, HTTP/2, HTTP/3, WebSocket, frontend/backend routing, retry, cache/privacy, certificate and administration scenarios. +- Test selective HOT/COLD invalidation through configuration edits, restart and persistent cache recovery; verify related regression cases and migration behavior. +- Verify optional Linux `sendfile=auto` with eligible static files and that all ineligible requests use safe normal delivery. +- Run controlled before/after performance and resource measurements. **No 0.34.0 throughput improvement is established yet.** +- Perform backup/restore and managed install/update acceptance on supported systems, including the container deployment where applicable. -- [x] DEV 1 KiB: remove shared HOT store mutex from cache outcome/reason - accounting; preserve domain cap, exact quiescent totals and reset rules. -- [x] Reserve bounded analytics queue capacity before allocating dropped samples; - preserve queue-drop accounting and every downstream feature. -- [x] Move per-vhost histogram aggregation to scrape time; retain the same - cumulative Prometheus and Docker-vhost bucket semantics while one exclusive - atomic bin is updated per completed exchange. +## Longer-term directions -- [x] Share compiled route handles and reuse middleware routing in forwarding; - avoid exact-host wildcard allocation and full-route cloning. -- [x] Fast-path single-backend LB; share immutable member health settings - between leases; reduce health locks and fallback/retry header snapshots. -- [x] Convert three exchange metadata locks to atomic tags; avoid unused live - log clones and add Rust regression fixtures for modified semantics. +Future work must be tracked separately from shipped features. Potential areas include better operational verification, additional diagnostics/alerting, clearer configuration and migration UX, and compatibility work as HTTP/ACME/runtime dependencies evolve. These are **not commitments or implemented features** without a separately tested release. -- [x] Disconnect keepalive/LB/diagnostic-only vhost saves from cache revisioning. -- [x] Invalidate only a vhost scope when its origin/representation policy changes. -- [x] Persist per-scope COLD fingerprints; old metadata cannot revive after an - interrupted content update or restart. -- [x] Reduce response-body per-frame backend mutex operations without losing metrics. -- [x] Add Rust regression cases and preserve unchanged LB controls/API. - regressions with a compatible Rust toolchain. - CPU, RSS, cache retention after saves, and rollback on failed acceptance. -- [x] Implement opt-in Linux HTTP/1.1 `sendfile(2)` and a per-Web-Service - Delivery toggle, with TLS/H2/H3/range/cache/compression fallback and bounded - per-connection slots. Implementation is source-only until native tests run. +## Documentation policy -## 0.33.8 - Performance with retained functionality - -- [x] Reduce cache-disabled request-policy work, header copies, timer allocation - and request-ID formatting overhead without changing security or telemetry. -- [x] Move live-history cloning and eviction cleanup outside the mutex while - retaining bounded history, explicit loss counters, live streams and sinks. -- [x] Narrow filesystem job context, borrow pinned root descriptors and bound - adaptive static-file reads at 64 KiB; preserve range and symlink behavior. -- [x] Correct local HTTP/1 file completion for cache publication and accounting, - with explicit empty, short-read, cancellation and multipart coverage. -- [x] Reproduce baseline test failures and distinguish real inherited defects - from obsolete fixtures; correct both without weakening functional contracts. -- [x] Add repeatable live HTTP, verified TLS/H2/H3/WebSocket, nginx comparison, - resource sampling, syscall profiling and mixed-traffic soak runners. Measured - outcomes and limits are recorded in `docs/PERFORMANCE_0.33.8.md` and raw evidence. - -## 0.33.7 - named dashboard presets, focused HTML extraction and chart typography - -- [x] Apply uniform 12px sentence-case labels to all Overview widgets, - including Process, RAM cache and backend tiles. Normalize secondary - captions and small values to monospace without changing other panels. -- [x] Space the two Cache table cards by 10px without altering other sections. -- [x] Reuse the backend quick-view popover for clickable Vhost upstream H1/H2 - protocol details; keep it stable during repeated live status refreshes. -- [x] Keep the clickable Backend and Health fields square, visually matching - adjacent Vhost tiles under every interface corner style. -- [x] Standardize the full legend text (including every value) at 9px monospace, - matching Requests per second across desktop, mobile and density modes. -- [x] Add a compact, nonmodal Vhost backend quick-view through both backend - count and health metric; use existing configuration and status fields, with - accurate unknown/last HTTP status handling and accessible dismissal. - -- [x] Introduce account-scoped, bounded SQLite storage for 16 named Overview - layout snapshots without changing proxy configuration or existing preferences. -- [x] Replace Arrange Overview modal with save/load/overwrite/rename/delete, - and factory reset; keep Unlock layout editing intact. -- [x] Extract metrics and charts only into compile-time embedded HTML partials - without changing existing card DOM order, IDs or polling. -- [x] Standardize chart headings on 12px proportional type (Overview in - sentence case, all other panels uppercase); use mono for full legends, - captions and chart data across all chart families. -- [x] Validate SQL migration and run available simulated-browser/JS tests. - -## 0.33.6 - Stable dashboard chart ranges and plot heights - -- [x] Fix all 14 Overview/WWW full-width SVG heights to 125px and align their - grid lines with the actual drawing range. -- [x] Apply zero-based, unit-specific scale floors to transform/compression, - rate, connection, latency, memory and WWW series; retain meaningful high - readings with stepped-up ceilings and no scale collapse on low samples. -- [x] Keep CPU/other percentage sparklines on 0-100% without changing legends. -- [x] Fix full Cache/Transform live charts at 180px and stabilize their rate - axes, including scope-change resets. -- [x] Test Chromium layout/scaling across 320-1920px and run available UI, - schema and source tests. Rust runtime acceptance remains pending. - -## 0.33.5 - Fixed Overview CPU chart scale - -- [x] Set System and process CPU to an absolute 0–100% axis, independent of - history, while keeping real percentage labels and both plotted series. -- [x] Fix the CPU plot height at 125px regardless of viewport width or - measured values; leave other chart scales and sizes unchanged. -- [x] Check stable CPU chart geometry, percent mapping, clipping and labels - in a simulated browser across viewport widths and Dashboard densities. -- [x] Replace the Overview Requests per second LIVE badge with its live maximum - rate, remove the LIVE TRAFFIC eyebrow and the duplicate peak footer entry. -- [x] Remove Connection Activity's LIVE badge, preserving its metrics. -- [x] Browser-test the two card headings, dynamic peak update and responsive - placement across viewport widths, dashboard densities and card spans. - -## 0.33.4 - Web Server/Vhost live-metric visual parity - -- [x] Show per-Web-Service live metrics without expanding a section. -- [x] Reuse Vhost metric tiles and the full-width traffic chart with the same - desktop/mobile layout, chart styling and theme-aware appearance. -- [x] Preserve counter sampling, PHP fallback, existing translations and the - existing status feed without new API or persistence state. -- [x] Add simulated Chromium layout, rendering and sampled-status regressions. - - -## 0.33.3 - Adjustable Overview card widths and cross-type ordering - -- [x] Apply Appearance **theme** highlighting and corner radius to sidebar - navigation, including mobile layout and accessible contrast on light themes; - keep menu colors independent of the chart palette. -- [x] Test navigation colors in Chromium across 4 themes and 8 chart palettes - (invariance within each theme), 5 corner settings, hover, mobile and RTL. -- [x] Keep the visual layout editor readable at 320-1920px with stable - three/two/one-column previews, wrapping controls and no horizontal overflow; - leave actual dashboard widths and free ordering unchanged. -- [x] Stretch plotted SVG history across the actual card width when resizing, - including dynamic network-interface plots, without stretching donut charts. -- [x] Allow widths of 2, 3, 4, 6 and 12/12 columns in inline and visual editors; - keep the smallest text/sparkline cards expandable and add a 4-chart desktop row. -- [x] Allow free card placement across former size groups, including at the - top of Overview, and reduce unused row gaps with CSS dense packing. -- [x] Preserve legacy grouped layouts until edited, retain saved hidden/order - preferences, and support cancel, save, modal preview and reset. -- [x] Add browser coverage for real move/size clicks and responsive layouts. - -## 0.33.2 - Shared progressbar visual settings - -- [x] Apply Appearance corner rounding to native Cache progressbars and - custom Overview, WWW and traffic bars with matching track/fill shapes. -- [x] Apply the chosen chart color palette to Cache progressbars and other - progress fills; retain theme-aware tracks, without new API/status work. -- [x] Unify the System Load RAM meter with other Overview progressbars, - retaining the 5px height and accessible percentage updates. -- [x] Verify live CSS changes with Chromium across corners, chart palettes, - light/dark themes and mobile/desktop viewport widths. - -## 0.33.1 - Factory dashboard preset and inline live preview - -- [x] Ship the operator-supplied default ordering, hidden cards and compact density - in one shared Rust/UI JSON preset; use 600 as the first-use history sample default. -- [x] Keep all existing explicitly saved preferences and restore the factory preset - only for new accounts or an explicit dashboard reset. -- [x] Honor unsaved inline hide/show changes while backend and Stream status refresh; - preserve the draft across unrelated preference updates. - -## 0.33.0 - Web Server observability (implementation complete; live acceptance pending) - -- [x] Add one Overview 60/120/300/600 history selector on the connection-status header row, with temporary changes and explicit browser-persistent choice. Use the same source stream and leave the Cache and Transform selectors independent. -- [x] Match the Web Server metric method visibility to `ResponseKind` (no `private_interfaces` warning). -- [x] Align donut totals beside the ring, preserving the legend and compact tile heights. -- [x] Add opt-in Web Server Overview cards, with all new metrics hidden by default and per-service drilldown. -- [x] Extend Web Server/PHP diagnostics and bounded Prometheus metric families without untrusted labels or additional request-path database calls. -- [x] Extend the packaged Grafana operations dashboard with Web Server/PHP panels and per-service filters. - -## 0.32.8 - Overview action alignment (runtime acceptance pending) - -- [x] Convert bounded cache RAM/queue/disk, file descriptor, HTTP protocol share and backend capacity cards into meter/progress-bar visuals, with clear unknown-limit behavior. -- [x] Replace cache hit ratio sparklines with compact HIT/MISS donut visualizations and legends. -- [x] Add large transformation/compression four-series live charts and Polish/English labels, preserving existing status sampling and layout/editor preferences. -- [x] Add deterministic UI metric tests and Chromium responsive-layout regression checks. -- [x] Replace mobile three-column Overview actions with natural wrapping: keep the heading at left, actions to the right, without shrinking buttons. -- [x] Remove redundant button-only classes and outdated small-screen overrides while preserving existing common button variants, layout controls and editor handlers. - -## 0.32.7 development follow-up: Appearance and H2-close diagnostics - -- [x] Use common button sizing for Overview quick actions and Appearance visual editor/show-all; preserve the normal and compact global button styles. -- [x] Strengthen the distinction between Dashboard density levels for tiles, charts and non-table panels without changing grid gutters or preferences. -- [x] Lower only correlated graceful H2 close terminal diagnostics to TRACE; keep 502/access details and uncorrelated cancellations as errors. -- [x] Verify the UI sizes with a simulated Chromium browser at desktop and phone widths. - -## 0.32.7 - Mobile Overview and upstream protocol visibility (runtime acceptance pending) - -- [x] Share counter-rate pause/resume handling between Cache, Transform, - Overview, backend, Stream and Vhost plots. Retain historical points after - backgrounding, skip suspended intervals and restart normal samples after - one fresh counter baseline, with no new polling. -- [x] Development follow-up: preserve Network diagnostic chart DOM through - periodic refresh and fix descendant CSS selector minification. -- [x] Feed Process CPU/RSS and per-interface network charts from the cached - background OS sampler through live status (default 3 s). Leave slow Process - details on `system_info_seconds` and retain valid 2-s saved preferences; do - not add per-request probes. Group upstream badges with other Vhost badges - and match their corner radius while preserving observed protocol colors. -- [x] On phone-size Overview, remove the nested Network interface chart scroll - region, while retaining chart SVG/text nodes on ordinary live updates. - -## 0.32.6 - -- [x] Reinstate two small Overview cards per row across phone-size widths. -- [x] Apply global corner styles to additional vhost/entity panel surfaces. -- [x] Avoid reparenting already-ordered Overview nodes; retain natural touch - scroll and stable values/row boundaries during live updates. -- [x] Include last HTTP protocol observed from real proxy upstream responses - in the authenticated status and each Vhost's per-backend badge. Show unknown - when no fresh observation exists instead of guessing Auto's ALPN outcome. -- [x] Add simulated mobile Chromium and mocked status UI regressions plus a - Rust unit regression for protocol-version observation. - -## 0.32.5 - Dynamic DNS client ACLs (runtime acceptance pending) - -- [x] Preserve CIDR/IP rules and introduce allow-only FQDNs for client ACLs. -- [x] Add bounded A/AAAA periodic resolver, immutable cache, expiration and - immediate refresh on published configuration changes. -- [x] Add DNS/DDNS editor selector, PL/EN guidance and source/regression tests. - -## 0.32.4 - Upstream Host/authority parity and Overview gutters (runtime acceptance pending) - -- [x] Remove `preserve_host` H1-only validation and the Auto/H2 UI forced-uncheck. -- [x] Align outgoing H2 authority with selected Host while keeping the origin DNS, - ACL, TCP destination, TLS SNI and certificate verification independent. -- [x] Update HTTPS-by-IP guidance; preserve opt-in diagnostic H1 preset. -- [x] Make Overview small-to-large row gutter consistent across density modes. -- [x] Add protocol/authority and UI/source contracts; preserve WebSocket isolation. - -## 0.32.3 - Backward-compatible upstream modes and Overview Cards registry (runtime acceptance pending) - -- [x] Replace ragged custom Overview masonry with symmetric CSS Grid rows grouped by widget footprint. Equal-height peers, traditional gaps, responsive columns and live-content growth are verified in Chromium. Block dragging remains supported inside its size group; group boundaries prevent visually inconsistent mixing. Registry (62), preferences and version stay unchanged. - -- [x] Snap manually rearranged Overview columns to common pixel-aligned coordinates and align the bottoms of a tall panel and its stacked short neighbors where bounded distribution allows. Keep factory rows, saved widget IDs/visibility, spacing controls and narrow-screen layout unchanged. Verify with a Chromium fixture, including dynamic height, density and zoom. - -- [x] Remove numeric/chart type badges from the Settings → Appearance Overview list, retaining them in the actual visual editor; keep right-aligned controls. -- [x] Open Appearance first unless Settings has a remembered last tab; honor explicit URL tabs and legacy configuration/logging links. -- [x] Decouple roundness from the color palette through a backward-compatible, account-scoped five-preset global `corner_style`. Keep original corners as the default for all four themes; restore coral/green logo marks in Ultra Dark. - -- [x] Preserve original symmetric and density-aware Overview rows for the default configuration. When blocks are manually reordered on desktop, keep the same column widths/gutters while allowing multiple natural-height charts to stack next to tall diagnostics; use CSS-layout-pixel row spans for zoom safety and fall back to classic rows on narrow canvases. -- [x] Exclude the RustProxy logo from global corner style overrides. Keep unlocked Overview × Hide and arrow controls consistent under all five presets, without changing color themes or account data. -- [x] Add 6 small and 7 large opt-in time-series views (62 registered blocks total), reusing existing status updates, bounded samples and version 0.32.3. Place block metadata before right-anchored editing controls. - -- [x] Preserve legacy `http2`: HTTPS ALPN H2/H1 fallback; HTTP H2C. Add a separate explicit `h2` mode requiring H2 over TLS / H2C over plaintext. Retain H1/Auto and isolate WebSocket clients. -- [x] Retain one safe Auto ALPN negotiation before the first request, without H1 retry after a potentially executed operation. -- [x] Establish a shared JSON registry for Overview IDs/metadata, server validation, ordered visibility settings and compatibility with unknown saved widget IDs. -- [x] Add fourteen opt-in HTTP origin monitoring cards, including request-rate, capacity, 5xx, timeout, network errors, retries, queue pressure and client-pool recycling; reuse existing status counters without extra polling. Retain the original 16 and existing account preferences. -- [x] Add an authenticated Overview modal for draggable visual card ordering, keyboard move, hide/show catalog, draft cancel and a single preferences PUT for Save. Keep existing Appearance controls and defaults. -- [x] Add opt-in AMOLED **Ultra dark** and muted **Warm cream** palettes with translated selectors, login-cache support, server-side preference validation and high-contrast palette checks. Preserve current dark/light themes, default and schema version. -- [x] Let administrators hide/remove visible Overview blocks directly while unlocked, including panels and tables; recover them from a compact hidden-block shelf in the same draft and persist visibility only after Save. -- [x] Add a direct Overview **Unlock layout** mode: drag actual blocks by dedicated grips, move with arrows, cancel without persisting, and save+lock in one request. Classify and label all 49 blocks by display type, including mixed number+chart cards, with a hidden-block type filter. -- [x] Expand to a shared 49-block Overview grid (30 prior widgets, 12 new opt-in charts and seven original full-size sections). Add visual default-layout preview, confirmed reset and account preference compatibility without new metrics probes or API routes. -- [x] Verify ALPN is already negotiated on connection establishment, then reused by Hyper per-backend pooling. Retain age/request-count generation rotation and new-connection negotiation rather than introduce a global H2/H1 cache or unsafe protocol-retry probe. -- [x] Present all four modes with clear HTTPS ALPN versus h2c semantics, keeping persisted `http2` records valid with unchanged meaning. -- [x] Add source/JS checks and Rust tests for strict ALPN, old TLS H1 fallback, H2C, WebSocket isolation and widget preferences. -- [x] Resolve the 0.32.2 HTTPS `http2` compatibility regression with a new serialized strict `h2` mode rather than a silent migration. - -## 0.32.2 - Web Server conditional cache revalidation (runtime acceptance pending) - -- [x] Reuse local Web Server `ETag` / `Last-Modified` revalidation candidates for forced browser refresh and stale/freshness-constrained requests, keeping hit counts and cache bodies on `304`. -- [x] Preserve client conditional semantics, HEAD, range, response policy and transformed representations; invalidate superseded objects. -- [x] Add Rust regression cases and source-only checks. - -## 0.32.1 - Dashboard Overview typography and tile height (runtime acceptance pending) - -- Restore content-driven heights for desktop Overview tiles (six columns) seen in 0.31.9, removing 0.32.0's fixed-height whitespace at 90% scale. -- Increase compact Dashboard headings, values and secondary descriptions, including the fixed-size Payload legend. Align tile gaps with the same `--ui-gap` as other dashboard blocks for all dashboard density settings. -- Keep opt-in Streams, all responsive breakpoints and chart legends visible inline. Add layout-regression checks. No proxy or persistence changes. Rust/Cargo and authenticated-UI runtime acceptance are still outstanding. - -## 0.32.0 - Stream backend pools and opt-in Dashboard cards (runtime acceptance pending) - -- [x] Extend legacy TCP/UDP upstream with an optional validated backend pool, Round Robin/Least Sessions, session pinning, and bounded concurrency. -- [x] Add bounded TCP Connect / verified UDP Probe health monitoring with rise/fall hysteresis. -- [x] Add opt-in, bounded backend metrics and reuse the existing Prometheus stream gate and API status. -- [x] Add four translated opt-in Dashboard cards through existing Appearance preferences and common status refresh. - -## 0.31.9 - Certificate API credential management (runtime acceptance pending) - -- [x] Inspect individual saved Certificate API credential metadata without reading its bearer secret. -- [x] Edit comment, explicit certificate allowlist and deploy grant independently of token rotation. -- [x] Regenerate only a selected credential's token while keeping Key ID, comment and grants, invalidating the old secret and showing the replacement only once. -- [x] Preserve existing scopes, administrator-only access, audit trail, schema 9 and backwards-compatible creation/revocation endpoints. -- [ ] Compile and execute Rust regression tests with Rust 1.99+; verify end-to-end panel flows on a real server before production acceptance. - -## 0.31.8 - Stored Logs sorting, built-in pages and allocation reduction (runtime acceptance pending) - -- [x] Consolidate 28 numbered Rust test fragments into seven large `tests.rs` files; exempt test sources from the 500-line production limit and remove obsolete fragment paths. -- [x] Sort Stored Logs across the bounded server-side scan before pagination, including dynamic columns and durable account preferences. -- [x] Complete the built-in status response pages in the existing visual style, including 200 OK. -- [x] Pack GeoIP IPv6 records and remove unused CSV Vec capacity without changing lookup results. -- [x] Add bounded zero-copy transformed-cache fill where exclusive ownership and exact allocation capacity are available. -- [x] Correct Docker VHosts post-restart pending/failed status, first-read synchronization and TLS pin indicator, without changing poll/TLS/SQLite protocols. - -## 0.31.7 - UA-specific throttling and managed update housekeeping (runtime acceptance pending) - -- [x] Group global UA blocking, headers and response controls in a tabbed Settings page. -- [x] Add per-vhost UA rate-limit overrides with separate bounded counters. -- [x] Show leaf-level revision diffs, with optional full JSON comparison dialog. -- [x] Space apart listener protocol buttons and collapse ACL attachments by default. -- [x] Fix release retention path deletion; preserve two releases; limit Cargo cleanup to expired incremental state. -- [x] Verify update marker and forced HOT→COLD shutdown code path, and add completion logging. -- [ ] Compile and test with Rust/Cargo; run real update/rollback and browser/UI tests on a controlled host. - -## 0.31.6 - H3 iOS navigation hardening (runtime acceptance pending) - -- Finish live Safari/Chrome iPhone logo-navigation and repeated-refresh acceptance over H3 after Rust compilation. -- Collect matching `http3_*` diagnostic records and navigation/asset response records; verify the new top-level GET is not interrupted when old SSE/API streams cancel. - -## 0.31.5 - User-Agent blocking and consolidated log analysis - -- [x] Add bounded global and per-vhost User-Agent blocklists and enforce them before upstream contact. -- [x] Add one-click copy for Traffic values used during investigation. -- [x] Add repeated-value aggregation for arbitrary parsed Stored Logs fields. -- [x] Consolidate active and rotated gzip per-vhost logs into one logical source on demand. -- [x] Update OpenAPI, translations, documentation and Rust-independent regression checks. - -## 0.31.4 - Administration-only public HTTPS Origin with one-field configuration - -- [x] Add explicit HTTPS `administration.public_origin` and restrict its use to trusted reverse-proxy peers for panel requests. -- [x] Derive the pinned DNS hostname for the effective Host allowlist without duplicating it in saved `allowed_hosts`. -- [x] Keep additional allowed DNS hostnames in an advanced panel field and remove redundant legacy duplicates on edit/save. -- [x] Preserve strict Origin, CSRF, trusted proxy, forwarded Host and HTTPS-first header checks and the opt-in self-admin session-cookie boundary. -- [x] Add panel settings UI, translations, API schema, Rust source regressions and Node simulations without changing public proxy forwarding. - -## 0.31.3 - Mobile session recovery and Origin diagnostics - -- [x] Reproduce the overlapping resume-session checks, reload-loop marker reset and early SSE restart using the 0.31.2 source in a deterministic JavaScript simulation. -- [x] Coalesce page visibility/BFCache session verification, defer background requests until revalidated and offer a visible retry on failure without weakening Origin enforcement. -- [x] Display a safe 403 HTML message for invalid-Origin dashboard navigation and provide credential-redacted rejection diagnostics. - -## 0.31.2 - Dependency updates - -- [x] Prepare stable dependency-family and code API migrations, preserving existing wire/configuration/SQLite formats. - -## 0.31.1 - Mobile panel loading stability - -- [x] Load dashboard modules as one ordered JavaScript resource and wait for document/module readiness before session initialization. -- [x] Version dashboard JS/CSS requests; show a recoverable startup error on missing modules. - -## 0.31.0 - Panel refresh orchestration - -- [x] Centralize authenticated polling with source IDs, view lifecycle, hidden-tab pause, abort, in-flight protection and cleanup. -- [x] Separate compact live telemetry from infrequent cached system diagnostics; allow per-client SSE pacing and reconnect-only fallback. -- [x] Add per-account refresh preferences, reset/manual actions, language packs, endpoint contracts and regression tests. - -## 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. - -## 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] Deduplicate file/Logstash JSONL serialization; cap Stored Logs directory scan retention during enumeration; review long-lived queues and other memory-heavy facilities. -- [x] Preserve vhost wire schema, SQLite schema 9 and Cold Cache format 3. - -## 0.29.7 - Backend health scoping, PHP-FPM isolation and HIT-only cache diagnostics - -- [x] Support per-backend active/passive health monitoring disablement and independent health-check settings. -- [x] Expose optional global and vhost-level cache HIT-only diagnostic headers. -- [x] Identify unsafe PHP-FPM shared-socket directive differences and document dedicated pool migration for Roundcube/WordPress. -- [x] Preserve existing settings and schema 9/Cold format 3. - - -## 0.29.6 - FastCGI compression EOF and diagnostic routing - -- [x] Fuse FastCGI response streams so compressors may safely probe the end of a PHP-FPM response multiple times. -- [x] Route WARN/ERROR diagnostics and panic-hook events to the configured error file, or stderr when the file is disabled/unavailable. -- [x] Add source tests for repeated EOF polling, gzip output, and live error-log settings changes. -- [x] Retain schema 9, Cold Cache format 3 and 0.29.x saved-configuration compatibility. - -## 0.29.5 - Roundcube PHP PATH_INFO routing - -- Resolve PHP `PATH_INFO` on an existing script before `try_files` falls back to the front controller, while preserving normal URL and script security checks. -- Verify FastCGI parameters from an actual mock PHP-FPM exchange in Rust integration tests when Cargo is available. -- Retain schema 9, Cold Cache format 3 and all previous PHP-FPM profile UI and vhost-cache behavior. - -## 0.29.4 - Shared PHP-FPM profiles and local Web Server processing - -Current direction: 0.29.3 -> 0.29.4 - -- [x] Add reusable PHP-FPM profiles attachable to individual Web Services, keeping their local settings on detach. -- [x] Apply RustProxy cache and response transformations for Web Server files and PHP-FPM responses. -- [x] Supply FastCGI `HTTP_HOST` from request authority for HTTP/2 and HTTP/3 WordPress requests. -- [x] Add source/UI regression coverage for new configuration and response behavior. -- [x] Keep SQLite schema 9 and Cold Cache format 3 unchanged. - -## 0.29.3 - HTTP/2 and HTTP/3 internal fallback stabilization - -Current direction: 0.29.2 -> 0.29.3 - -- [x] Allow empty GET/HEAD bodies with unknown length hints to enter the internal error fallback path. -- [x] Preserve the final internal fallback response, including the fallback backend's HTML on HTTP 404. -- [x] Add Rust regression tests for fallback dispatch, request-body safety and error-page precedence. -- [x] Preserve SQLite schema 9 and Cold Cache format 3 without configuration migrations. - -## 0.29.2 - PHP-FPM operator controls and observability - -- [x] Add bounded, operator-only PHP_ADMIN_VALUE INI overrides per Web Service. -- [x] Show admin overrides in the Web Service UI/API, reusing existing location-to-service routing. -- [x] Track PHP responses by status class, response-header latency and free concurrency slots in Prometheus. - -## 0.29.1 - Internal error routing, multipart ranges and PHP PATH_INFO - -Current direction: 0.29.0 -> 0.29.1 - -- [x] Add configurable internal error fallbacks for the main vhost source and Custom locations. -- [x] Preserve original method, URI, query string and request context during internal fallback dispatch. -- [x] Reject fallback cycles at validation time and cap runtime fallback depth. -- [x] Intercept configured errors before final response/cache handling and run the target through normal route processing. -- [x] Add bounded Prometheus counters for triggered, successful and failed error fallbacks. -- [x] Support fixed fallback status codes 403/404/500/502/503/504. -- [x] Add streaming multipart byte-range responses with a bounded range count. -- [x] Add opt-in secure PHP PATH_INFO splitting for PHP-FPM. -- [x] Keep SQLite schema 9 and Cold Cache format 3 unchanged. - -## 0.29.0 - Redirect, health-host, forced-cache and rotated-log controls - -Current direction: 0.28.2 -> 0.29.0 - -- [x] Add one-checkbox canonical www/non-www redirects per vhost. -- [x] Add first-class Custom location redirects with capture-based rewrite targets and query preservation. -- [x] Default HTTP health-check Host to the first exact vhost hostname while keeping the manual field as an override. -- [x] Add explicit per-vhost forced-cache targets with response privacy protections retained. -- [x] Read bounded logrotate gzip rotations in Stored Logs. -- [x] Verify local/static HTML pages can intentionally return statuses such as 503. -- [x] Preserve valid nested URL query parameters while retaining ambiguous-backslash rejection. -- [x] Keep SQLite schema 9 and Cold Cache format 3 unchanged. - -## 0.28.2 - External certificate hardening and least-privilege reload API - -Current direction: 0.28.1 -> 0.28.2 - -- [x] Accept POSIX ACL-based read access for external certificate keys without requiring one exact chmod value. -- [x] Reject unsafe world-readable private keys, group/world-writable external PEMs and untrusted/writable external-path parent directories. -- [x] Validate both configured Certbot `live/` paths and their resolved `archive/` targets while keeping hot reload behavior. -- [x] Scope Certificate API credentials to explicit certificate IDs, default new tokens to reload-only, block global reload for tokens and make deploy an explicit permission. -- [x] Migrate existing Certificate API credentials to reload-only compatibility grants and move SQLite to schema 9. -- [x] Document read-only systemd access plus POSIX ACL examples for `/etc/letsencrypt` and `/etc/ssl` in English and Polish. -- [x] Keep the saved proxy configuration format and COLD format 3 unchanged. - -## 0.28.1 - External certificate paths and certificate-source clarity - -Current direction: 0.28.0 -> 0.28.1 - -- [x] Allow manual certificate entries to reference an absolute external certificate path and absolute external private-key path. -- [x] Resolve Certbot-style `live/` symlinks on every reload while preserving bounded PEM parsing, regular-file checks and private-key permission checks. -- [x] Keep targeted certificate reload revision-stable so renewal wrappers can call `POST /api/certificates/{certificate_id}/reload` after renewing external files. -- [x] Preserve managed `/certs` filenames and reject mixed managed/external pairs. -- [x] Exclude external certificate material from RustProxy backups while retaining the configured absolute paths. -- [x] Clarify certificate-source, storage and path expectations in English/Polish UI text and operator documentation. -- [x] Keep SQLite schema 8 and COLD format 3 unchanged. - -## 0.28.0 - GeoIP control, listener clarity and cache policy refinement - -Current direction: 0.27.3 -> 0.28.0 - -- [x] Add on-demand GeoIP refresh through the authenticated administration UI and reuse the automatic updater pipeline. -- [x] Hot-swap validated GeoIP databases so subsequent country ACL/rate-limit/logging lookups use the new data without a process restart. -- [x] Use direct GeoIP file URLs with optional per-file SHA-256 verification and support either separate IPv4/IPv6 CSV ranges or one native MaxMind MMDB database. -- [x] Present active listeners as readable HTTP/HTTPS endpoint cards with explicit H3/UDP state. -- [x] Add configurable `Via: RustProxy` cache response metadata while preserving upstream Via values. -- [x] Preserve cache objects across bypass and response-only diagnostic/Via setting changes. -- [x] Separate vhost Security header-rule Delete controls from the Operation selector. -- [x] Keep SQLite schema 8 and COLD format 3 unchanged. - -## 0.27.3 - HTTP/3 cache parity and bounded memory retention - -Current direction: 0.27.2 -> 0.27.3 - -- [x] Make body-less `HEAD` cache eligibility consistent between HTTP/2 and HTTP/3. -- [x] Reduce persistent COLD-index RAM while preserving the existing on-disk cache format and cache behavior. -- [x] Reclaim expired rate-limit client buckets and avoid duplicating the vhost name for every tracked IP. -- [x] Confirm downstream compression remains streaming and does not retain complete response bodies in memory. -- [x] Exclude rebuildable managed Certbot Python environments from backups. -- [x] Document DEB/RPM and OPNsense repositories as the official installation paths; keep Debian source builds optional. -- [x] Separate application-coupled scripts from QA/dev/audit tooling and external platform integrations while preserving all entry points. -- [x] Keep SQLite schema 8 and saved configuration compatibility with 0.27.2. - -## 0.27.2 - Managed ACME renewal repair - -Current direction: 0.27.1 -> 0.27.2 - -- Make the managed Certbot runtime independent of pip console-script shebang relocation. -- Reuse existing 0.27.1 `acme-python` environments without operator cleanup. -- Preserve the system Certbot fallback and schema/config compatibility. - -## 0.27.1 - Portable certificate runtime and list sorting - -Current direction: 0.27.0 -> 0.27.1 - -- [x] Show complete default and standard-service setup-token commands during first-administrator onboarding. -- [x] Initialize the Certbot Python environment under the RustProxy data directory with app-owned `venv`/`pip` state and a local `virtualenv` bootstrap when stdlib `venv` is unavailable. -- [x] Fall back to distribution Certbot binaries when usable and report missing Python/pip or setup failures in the Certificates panel. -- [x] Add account-scoped column + direction sorting for vhosts, Docker vhosts, redirects, 404 hosts and streams; keep Redirects/404 toolbar placement identical to Vhosts. -- [x] Use the installed production binary in the Certbot hook example and expose official source/package repository links plus Python/pip/Certbot runtime state in application information. -- [x] Keep SQLite schema 8 and additive/defaulted administrator-preference compatibility. - -## 0.27.0 - Compression policy and persistent Docker publications - -Current direction: 0.26.7 -> 0.27.0 - -- [x] Enable zstd/Brotli/gzip downstream compression by default, keep deflate opt-in, honor client q-values and use deterministic `zstd -> br -> gzip -> deflate` tie priority. -- [x] Allow authenticated/cookie/private/no-store responses to compress while retaining explicit no-transform/range/SSE/upgrade/existing-encoding exclusions. -- [x] Add inherited dynamic-compression overrides and per-scope codec filters for ordinary vhosts and Web Server services under the global codec ceiling; keep Docker publications intentionally simple with one Compression on/off checkbox using the global codec set. -- [x] Default fresh installations to `Server: RustProxy` without rewriting existing saved policies. -- [x] Remove Debian package-build metadata from this source tree; packaging is maintained separately at `rustproxy.linuxiarz.pl`. -- [x] Keep published Docker hostnames materialized while a service is missing, return 503 until it returns, and reuse last-known inventory during temporary agent communication failures. -- [x] Render cache occupancy progress fill consistently across Chromium/WebKit and Firefox. -- [x] Keep SQLite schema 8 and additive/defaulted saved configuration compatibility with 0.26.x. - -## 0.26.7 - Cache occupancy and backend rotation continuity - -Current direction: 0.26.6 -> 0.26.7 - -- [x] Show HOT/COLD cache occupancy progress against configured entry and byte limits on `/cache`. -- [x] Preserve compatible upstream client generations across unrelated runtime snapshot rebuilds so rotation age/counters remain continuous. -- [x] Keep request-count and age-based backend client recycling covered by tests and visible diagnostics. -- [x] Refresh container documentation and include all supported Certbot DNS plugins in the shipped OCI image. -- [x] Review current installation/cache/backend lifecycle documentation for stale behavior statements. -- [x] Keep existing 0.26.6 custom `Age` rules loadable while isolating response-side Age overrides from cache freshness/storage semantics. -- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.6. - -## 0.26.6 - Access-policy composition and administration fixes - -Current direction: 0.26.5 -> 0.26.6 - -- [x] Keep backend table visible columns, sort key and sort direction synchronized with account-scoped user preferences. -- [x] Clear the certificate-issuance form after the ACME issuance job succeeds while preserving non-secret values after failed jobs. -- [x] Document the sibling Python modules required by `scripts/migration/migrate_npm.py` and fix the container helper example. -- [x] Treat explicit IP allows and allow-only countries as alternative allow sources inside one ACL policy scope, with explicit denies taking precedence and separate scopes remaining cumulative. -- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.5. - -## 0.26.5 - Scaled controls and listener navigation - -Current direction: 0.26.4 -> 0.26.5 - -- [x] Keep compact toolbar selects and inputs on the same interface-scaled control height. -- [x] Reuse the interface-scale field variables for ordinary form controls so inputs/selects resize consistently with the UI scale setting. -- [x] Route the Streams **Add TCP/UDP listener** shortcut to Settings -> Listeners instead of opening a Stream editor. -- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.4. - -## 0.26.4 - Administration consistency and backend rotation reliability - -Current direction: 0.26.3 -> 0.26.4 - -- [x] Add 404-host forms to the session-only draft workflow. -- [x] Complete Polish translations for the Button style appearance setting. -- [x] Align toolbar search/filter input height with the active button style. -- [x] Reuse the vhost Enabled control styling in 404 hosts, Redirects and Streams. -- [x] Replace the Overview-card checkbox list with compact visibility/reorder tiles. -- [x] Run age-based backend client recycling independently of request arrival. -- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.3. - -## 0.26.3 - Administration appearance and draft workflow polish - -Current direction: 0.26.2 -> 0.26.3 - -- [x] Make interface scaling global across navigation, workspace, dialogs, overlays and the draft dock; extend the range to 70-125% and make form-control sizing respond more visibly across that range. -- [x] Make Overview telemetry cards denser without changing responsive breakpoints. -- [x] Expand chart themes to eight full-palette presets affecting colors, surfaces, grid and line treatment. -- [x] Restyle the draft dock as a square-corner RustProxy panel without rounded/glass styling. -- [x] Add a collapse-to-bar state and per-draft age indicator to the session-only draft dock. -- [x] Add Settings search without rebuilding forms, preserving unsaved values while categories are filtered. -- [x] Add chart-style palette previews beside the eight chart presets. -- [x] Add independent Normal/Compact/Ultra table-log density and Overview dashboard density preferences. -- [x] Add per-account Overview card visibility and ordering controls. -- [x] Add per-section Appearance resets plus a full Appearance reset while keeping unrelated account preferences intact. -- [x] Add an account-scoped Normal/Compact button-style selector, with Compact matching the smaller source-style control dimensions independently from global scale, including generated vhost actions and modal tabs. -- [x] Retain sticky Settings category navigation and unsaved-category indicators. -- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.2. - -## 0.26.2 - Unified logs search and dynamic fields - -Completed direction: 0.26.1 -> 0.26.2 - -- [x] Add independent in-session editor drafts for Web Server, Vhosts, Redirects and Streams, with minimize/restore/discard actions in a bottom-right dock. -- [x] Consolidate theme/language/display controls under **Settings -> Appearance**, replace density modes with an interface-scale slider and persist appearance choices in administrator preferences. -- [x] Add 12/24-hour timestamp formatting, chart presentation presets and a denser expanded Stored Logs record view. - -- [x] Merge Stored Logs and Live Logs into one top-bar Logs menu. -- [x] Replace fixed Stored Logs filters with one bounded field-query syntax supporting AND, negation, quotes, wildcards and numeric comparisons. -- [x] Discover selectable Stored Logs columns from parsed record fields and keep arbitrary safe field choices in account preferences. -- [x] Parse the built-in RAW error-log line format into basic structured fields while retaining the raw record. -- [x] Remove the configured-file inventory/source selector from the Logs workspace and keep bounded all-source scanning by default. -- [x] Move Stored Logs column configuration to the table toolbar and use arrow symbols for backend sort direction. - -## 0.26.1 - Vhost-aware protocol diagnostics and Logs Explorer UX - -Current direction: 0.26.0 -> 0.26.1 - -- Attribute persistent protocol errors through one shared vhost resolver using routed vhost, authority/Host and SNI in that order. -- Keep unresolved traffic in `_unmatched.jsonl` and keep low-level protocol errors out of Live Logs. -- Add bounded protocol diagnostic identity fields for Stored Logs filtering and inspection. -- Add selectable Stored Logs columns, sticky headers, highlighted selection and inline details. -- Hide configured-file inventory by default and repair mapped client-IP/search behavior. -- Preserve schema 8 and the existing saved proxy configuration format. - -## 0.26.0 - Persistent protocol diagnostics and stored-log explorer - -Completed direction: 0.25.7 -> 0.26.0 - -- [x] Persist malformed HTTP, TLS/QUIC handshake failures, HTTP/2/HTTP/3 protocol failures, invalid h2c attempts and connection-limit rejects through the existing bounded logging sink. -- [x] Keep low-level protocol failures out of the Live Logs memory ring so hostile scans cannot displace ordinary live request history. -- [x] Add a local read-only **Logs** explorer with bounded server-side filtering and pagination over configured access/error files. -- [x] Prevent arbitrary-path browsing and symlink following; enumerate only paths derived from active RustProxy logging configuration. -- [x] Add `/logs` dashboard navigation plus a Logging-settings launcher while retaining the existing Live Logs dialog unchanged. -- [x] Keep schema 8 and configuration compatibility with 0.25.7. - -## 0.25.7 - Vhost operations and backend observability - -Current direction: 0.25.6 -> 0.25.7 - -- [x] Add a fast vhost enable/disable action to the vhost cards using the existing revisioned save path. -- [x] Add backend-table search, sorting and account-scoped persisted column visibility preferences to the main dashboard. -- [x] Style scrollbars for dark and light dashboard themes. -- [x] Export retry and upstream-client lifecycle metrics for ordinary and Docker backends. -- [x] Add Grafana panels for backend client generation, age, retry activity and rotations. -- [x] Make the vhost editor Enabled control visually distinct from ordinary settings. - -## 0.25.6 - Backend memory diagnostics visibility - -Current direction: 0.25.5 -> 0.25.6 - -- [x] Show upstream client generation, current-generation request count, generation age and total rotations in the main Backends diagnostics table. -- [x] Reuse the existing `/api/events` status snapshot refresh path; do not add a second diagnostics polling loop. -- [x] Keep the 0.25.5 client-generation recycling defaults and configuration compatibility unchanged. - -## 0.25.5 - Traffic country consistency and Revision History styling - -Current direction: 0.25.4 -> 0.25.5 - -- [x] Use trusted `CF-IPCountry` when verified Cloudflare client-IP mode is active, with local GeoIP fallback for missing/invalid country headers. -- [x] Keep country analytics, retained country logging and country policy evaluation on the same verified country source. -- [x] Align the Revision History dialog with the shared square-corner modal styling and remove duplicated history-only dialog CSS. -- [x] Add Cloudflare-country trust regression coverage and release/source-contract checks. -- [x] Extend Prometheus with a live Traffic analytics collector, fixed-cardinality User-Agent/method/source families, country/top-route views and built-in Web Server inventory/source metrics. -- [x] Extend the bundled Grafana dashboard with a Traffic vhost filter, Web Server request rate, countries, User-Agent families, serving sources, methods, status classes, cache outcomes, capture-state gauges and configured routes. -- [x] Bound backend Hyper client lifetime with configurable request/age recycling and an upstream HTTP/1 buffer ceiling so sustained traffic can release pool/TLS high-water allocations without a vhost save. - -## 0.25.4 - Configuration history restore - -Current direction: 0.25.3 -> 0.25.4 - -- [x] Restore an older retained configuration through the normal validation and hot-publish path. -- [x] Save restore as a new revision without deleting or rewinding existing history. -- [x] Require current-revision CAS plus administrator session/CSRF and audit restore source/new revision. -- [x] Add restore action and confirmation to the existing revision-history modal. - -## 0.25.3 - Request policy, correlation and configuration history - -Current direction: 0.25.2 -> 0.25.3 - -- [x] Add per-vhost HTTP method allow/block policy. -- [x] Add per-location HTTP method policy, reusable ACL profile, inline IP ACL and country allow/block policy. -- [x] Enforce host and location policies before upstream selection/connection. -- [x] Generate one SHA-256 request ID per exchange and forward it unchanged to the backend as `X-Request-ID`. -- [x] Persist bounded manual-configuration revision history in SQLite schema 8. -- [x] Add a calendar/day revision-history modal with change preview in the existing dashboard style. - -## 0.25.2 - Live backup and resumed-session hardening - -Current direction: 0.25.1 -> 0.25.2 - -- [x] Allow encrypted full-backup creation while RustProxy owns the data-directory process lock. -- [x] Snapshot SQLite consistently and stage durable files before archive encryption. -- [x] Preserve only validated relative symlinks that resolve inside the staged data tree, including Certbot `live` -> `archive` links. -- [x] Keep restore offline, validated, staged and atomically activated. -- [x] Recover transient invalid-origin state in restored/resumed admin tabs with a one-shot same-origin reload, without weakening server Host/Origin/CSRF checks. - -## 0.25.1 - Traffic UX and source modularization - -Current direction: 0.25.0 -> 0.25.1 - -- [x] Split Traffic into Overview, Clients, Requests and Delivery sub-tabs. -- [x] Chart every bounded Traffic dimension with donut or horizontal bar visualizations suited to the data shape. -- [x] Keep every Rust source file at or below 500 lines and keep unit tests outside implementation files. -- [x] Split oversized dashboard source bundles and the Docker Agent into bounded source modules while preserving behavior and URLs. -- [x] Guard the Web Server split against dangling attributes and missing `htaccess`, `server_protocol` and `anyhow::Context` imports. - -## 0.25.0 - Web Server foundation - -Current direction: 0.24.10 -> 0.25.0 - -- [x] Add independent reusable Web Service configuration with name, document root, index list and directory-listing switch. -- [x] Add Web Service CRUD through the dashboard/API and persist it through the existing revisioned configuration store. -- [x] Add `Reverse Proxy` / `Web Server` source selection to vhost fallback and custom locations without storing Web Server filesystem settings in vhosts. -- [x] Allow one Web Service to be referenced by multiple vhosts and one vhost to mix Web Server and Reverse Proxy locations. -- [x] Add `GET`, `HEAD`, MIME, `Content-Length`, streaming, index lookup, directory slash redirect and optional directory listing. -- [x] Add repeatable per-request directory listing, per-directory index-before-listing behavior, Web Service MIME overrides/default MIME and a default static `Cache-Control` policy. -- [x] Organize Web Service editing into General, Routing, MIME, Cache & headers, Delivery, PHP-FPM and Security tabs. -- [x] Add Web Service `try_files`, internal/redirect regex rewrites, ordered per-extension/MIME Cache-Control rules, fixed response headers, precompressed `.br`/`.gz` delivery and forced-download extensions. -- [x] Add bounded FastCGI/PHP-FPM execution over Unix sockets or loopback TCP with `index.php`/front-controller routing and no source fallback on upstream failure; expose advanced bounded limits, Authorization forwarding, custom FastCGI parameters and controlled `PHP_VALUE` overrides with in-panel syntax documentation. -- [x] Add explicit symlink policy: deny by default, with Linux-only inside-root following for static files; PHP-FPM remains restricted to deny. -- [x] Add a bounded `.htaccess` compatibility layer for common Apache migration rules, including rewrite conditions/rules, directory index/listing, MIME, headers, Expires and PHP AddHandler integration, with a dashboard documentation modal and fail-closed security-sensitive directives. -- [x] Add one-shot Web Service presets for Static, SPA, PHP generic, WordPress, Drupal, Laravel, Symfony, Joomla, PrestaShop and Downloads, with optional deterministic hardening for common exploit probes/sensitive paths and fingerprint-header stripping. -- [x] Expose vhost-wide request/active/error/EWMA/traffic metrics for Web Server-only vhosts and fix custom logging mappings for route/cache/transform effective fields. -- [x] Add bounded per-vhost Traffic analytics, User-Agent logging and opt-in country/GeoIP logging with top-dimension views, effective-client-IP rankings and donut charts for the main dimensions. -- [x] Keep Web Service dashboard rendering stable when configuration loads before the deferred Web Server UI module and use the existing generic/custom 403 error-page pipeline when directory listing is disabled. -- [x] Add automatic allocator release and idle HOT-cache waterline trimming so sustained traffic does not require a vhost save/reload to return reclaimable RSS. -- [x] Add metadata-based `ETag`/`Last-Modified`, conditional `304`, single `Range`/`If-Range`, `206` and `416`. -- [x] Enforce decoded-path validation and document-root confinement; Unix traversal uses `openat` + `O_NOFOLLOW` and does not serve symlinks. -- [x] Keep Web Server inside the existing routing, ACL/auth, custom-header/error-page/security and H1/H2/H3 response pipeline; do not create separate routing/error/cache subsystems. -- [x] Add unit/source-contract coverage, OpenAPI/UI language coverage and Web Server documentation. -- [x] Clean README/Docs/CHANGELOG/ROADMAP material, add a canonical docs index, authenticated Markdown Reader, 0.25 specification audit and open-work TODO. -- [ ] Run compiler-enabled `./dev.sh fmt`, `./dev.sh check`, `./dev.sh test` plus live H1/H2/H3/static-file acceptance on the release host. - -## Web Server follow-up - -Future Web Server work must retain the 0.25 split between reusable **Web Service** configuration and vhost/location source selection. - -- Multipart byte ranges if operational demand justifies them. -- Named locations without introducing a second URL router. -- Opt-in PHP `PATH_INFO` script/path splitting with an unambiguous confinement rule. -- Race-resistant filesystem confinement for non-Unix targets before enabling local file serving there. -- Real PHP-FPM release-host acceptance over Unix socket and loopback TCP. - -See [`docs/ROADMAP_HISTORY.md`](docs/ROADMAP_HISTORY.md) for completed historical milestones. +- Update existing guides when code behavior changes; remove obsolete instructions and avoid version-by-version implementation notes inside functional guides. +- Keep the changelog technical from an operator's perspective (features, settings, compatibility, defaults, fixes) without Rust allocation/lock/microbenchmark details. +- Preserve old audit/development evidence in a separate archival package if an engineering investigation requires it, not in the live product Reader. diff --git a/SHA256SUMS b/SHA256SUMS index 511fc09..033665c 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,13 +1,13 @@ 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore -5135a996f12151e481942bf3b4c46c42b675c1cf7086bd44733f3be260177d8c AGENTS.md -e7bea33973a805fdd1fd3af967555004b3509ef9b81576819c707c3dd5c47497 CHANGELOG.md +b123132d0f1230f7fe30a1a80f51ee931fe87c82487b3d172c3db7db0876b7da AGENTS.md +88e6825d29a5864b6b7412b08a3d35be85d5f0f53fcc9c9a57a6283c0feef577 CHANGELOG.md ca201e3cd3f9842eae0006d0b40a0a72bcd3d4da6e4e5ecaf200d1b9a1390db2 Cargo.lock fec5f1bae9a59ef1fabd27c5e45d31448dd8f811736eebfa8826b3806a7e3349 Cargo.toml 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile -f0d89d95166d47b6ac2033cc8f5eaf2246c0c70139437394c98e10a95ab3d0dc README.md -b1d6174f509c743622cb27c92e047b724ac9d44bbf1c0987178da8a3bbd74b01 ROADMAP.md +9802fb994a5361186e83e71d95163c53baa890a6f93b09cb77611d25ee92376d README.md +79e6156f363636fe89c49444de79dc4793cd22db3021123d6d2fd0b4b3e592fb ROADMAP.md da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md @@ -25,54 +25,31 @@ b68234472bec5cedd67c7cfe1de7d8806a965dfd4101f944aa6320132f18c14e docker-agent/m e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go 634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service 5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go -875faa3969df9409d79f050a84b4183496f557edda3d34aaec19fe556cf8c471 docs/ACME_PROVIDERS.md -ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md -70588369169c06e71925d6f45f98258153626cccbd168a77eb882a4a7ded7702 docs/API.md -f27053f8a93d7883025a04922d015e0b697cad653e714be632b757e167297de1 docs/API_ACCESS.md -d30551a52ffd34c5963094d2826ba39795210a4e6a4e4d7333066dae7456fc50 docs/ARCHITECTURE.md -7f61bc6e5c82b6519b50525eb2516298a5e28dbb28537d31eaf2f56bb7d5ff59 docs/CACHE.md -9113f3abbfe81bafc4855d8680df8aa2a381ecc358cc3e1a0f78b6b756421117 docs/CACHE_DIAGNOSTICS.md -de75fae9e387b2e8b3d478677963f8d67a124f3f02b746de285ca8b697b3816b docs/CACHE_HOTPATH_0.34.0.md -3822d72e12f82c37a9e70ab40c7e89e89145ad37f29914e4702db4bf024a5501 docs/CHANGELOG_TECHNICAL_ARCHIVE.md -c8e69e492179ce39958cc63615cf46c4ac08cfdb7f3483aaab66868a3abcd687 docs/CONTAINER.md -833ddb12f23dcac796223521388b19c6ec3ea04c5550cf1c5f4ea4eb7c58f3a0 docs/CUSTOM_LOCATIONS.md -3cbcf562114fae47f01ed4614d49844b88e7a4d8aee7eb724400d5681be90936 docs/DASHBOARD_PRESET.md -4d70797bda8d227787fcff897951e702e2458fc6c6f5352aa0b5577595bd834b docs/DOCKER_DISCOVERY.md -46811a09a03d83f4af686c33e30e3f94d0508afe2c75147a26885c7247c49cbd docs/ERROR_LOGGING.md -f749f7bd2eb4ae9c484f77a3cd8cd5ff21445319a3938e0384a3414134fe713a docs/FILE_LOGS.md -36599e0b59164e662442f3e8203c16b3123d09b7684d8ecdbffce42d22cc221b docs/HTTP3.md -a00ec3c1d3b28ba10677435128e031e5f18fe8c5cef2ac90eb0f6779cefeb434 docs/INSTALL.md -3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md -bd85d4040fb7d213d7983596ad6f207cecbb03bfcfc399a4333f1e3fdf9211b9 docs/LIVE_LOGS.md -7c35c4cdbe7bbb81d94f3bf6aeb22b8a866af3e7f0d62bbedc299e4d6f264e0d docs/MANAGEMENT.md -d8829cc68d982c933d04207cf7bc78cc918044c72e024e7ecad5220953bb3a79 docs/MEMORY_STRESS.md -5551dc403f1b450a5d4bfd48b1130971751ec0ec51e4fc9b2f5cff1971a212ab docs/MIGRATION.md -5f090a190000c6badab2a74e5d4968e70c34de43b59c1452f5b05503b5c5c18a docs/MOCK_AUTH.md -1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md -6cd54c6e2c3e5c23ae0104e09e88ab6d93bde26bd92b23c1a078fe7c4ee04b55 docs/NGINX_GAP_0.34.0.json -be53a58afbeddbd7deb4b5f6447f2dc74db0327839bd83407c20915df98dcfa0 docs/NOT_FOUND_HOSTS.md -89a1af6c9fe10c572b211f9632ce256166e67990b5dacacf2ae07265d99a5172 docs/NPM_MIGRATION.md -02126c23264b3afdf1e639c0334d621520e7538ff92d60eb6398c5fb10a23f2e docs/PERFORMANCE_0.33.8.md -e68d5a5cf0991de9308f78afb0264e02979f7463a5f395eb7a6e9ec2bd3a3d54 docs/PHP_FPM.md -bc068a176056f5ea4d3b637071e644ed58a2140494c233ef22aa66e9700e3299 docs/PHP_FPM_ISOLATION.md -ad6e3f003b820a4f45e30e39577d120a69d167c70910bda4891971759458c3dd docs/PROMETHEUS.md -df6dc1070f94eeee7d4273251b93ab03b34e6cf0b02789d5b8f538a37814b382 docs/README.md -6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md -0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md -d85c5567fc6a7714b96ad7cfe4ee9ff2085f84c4f31534a6484340d03a73e0f7 docs/RENEWAL.md -82bbcd1c3a3bb6a44065f96f160f8633b2f2450ab4723412570cc885a6de459b docs/ROADMAP_HISTORY.md -759bf2987f2b602488829d1856fa33d57c2a3b23bc681ef5a931b9633af3a533 docs/SECRET_STORE_BACKUP.md -ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.md -620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md -3434a638ef0198d659ad77e0a70d0540cf4f203e10e147713e6c2530ba84324a docs/SETTINGS.md -8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md -95148bfa1d2f8ff52b591c9acefc6c8992dadc4ebda43a195101d9b31d67a576 docs/TEST_REPORT.md -751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md -e6d270894cafe96f8aa9cf58c9609223d03c9bcf6f872adfcbfb767a7b313f0a docs/TRANSPORT.md -915da874d0b24e39b359fb70cc557730f7053a8334ced9ed406b6fa933c0dfaf docs/VHOSTS.md -09b60fb7b40db4d0139fa2a9e69a2aa9f28d4e61b8643521cfc7e936d7df8774 docs/VHOST_ACCESS.md -cdf208dc6438d11c8119b6de81cad7a838eaeeb9cbbb5417e691253760d69ddf docs/WEB_SERVER.md -c336f998027330c58d5afbbbb3a3a682fd44ae3997643882fe342633822f8bb3 docs/screenshots/README.md +b0eaa2cfe4f6e86582f67891e457591ae650d15145e025d85cab43e37c883ac8 docs/ACCESS.md +bade64057fd180e63845816336181f997f720a2c7b209c8d4a10333365323cfd docs/ACME_PROVIDERS.md +a11748015bb07a9f53a231a6a3fc318f6de9eac5cbc45bcb000fe34be5163255 docs/API.md +7f7021fff285a18e548c7e769a02cfec9e573f089f3e6b1ab3b4f6be0523fe1e docs/API_ACCESS.md +728b15b9825f547f34c4121219784798dd046fbbdd72ece8906d0cd77e8efc64 docs/BACKUP.md +7e6a7b9421541c94181a712d17f6166a328e7e28c5a1d629e7d335c0ae5aa1de docs/CACHE.md +bcc8a847cfac67ef945d98ff808d92adeaa9bd7afa1878194b41faa009c41777 docs/CONFIGURATION.md +991af7afa69c0e1f7e48baae6309a5e5431e4c23c0e4538eb37e1948854b991c docs/CONTAINER.md +dc670d912a6a946160c2f8528c9bb710ec38ebe406dcb7dcaf18fb2653f667b6 docs/CUSTOM_LOCATIONS.md +b4940716f8a89bf964f00ad6032aab1083fc1983e1d2e42515028f7b1a34c9e5 docs/DASHBOARD.md +71fcb8e67135e2197207bd8b979805a9b0ae5df9c6a8594de9b7fadb72fa15e2 docs/DOCKER_DISCOVERY.md +4238ba7e0e6071cd3b30816f29f87d8c9f8f264d1dbe53e4073d606806c5a50f docs/INSTALL.md +37001cbf8ed248ca05ec40f12dd9876d33f6e0c97dcb21eb2e6f53c3d97a60bb docs/INTEGRATIONS.md +8c5007550ebae8408cbb72a75b2af9894578a1b9acda6bd34bc5f533916ddf72 docs/LOGS.md +888cdc8c56e10b8a7615012e192e085bf2899d0ec9b3ddfb5731e7de6ab5bcde docs/MIGRATION.md +481b7c420f388c1d9338ffc794ca7d27fbcc8b01afaf4edbd2405e59295416ba docs/MONITORING.md +d5894c07c68ec4230987767198434abee13b0e525842fea21bcbe752fa82742b docs/PHP_FPM.md +0efb12afcfccce6ca9018ddad3a93f3d81e04711f12154bd570c939e30c30a60 docs/README.md +7c47aed9c178e8c707b98d1b20ec985c1170f193de0c7b10b682e76da576e764 docs/RESPONSE_DELIVERY.md +701023a4cdcac145dcb2bc5bb709afe79599a5d70cbda6fdfe19ab39ad703bfc docs/SECURITY.md +519f9c45fe31ede5afdfb8c4e4ef05f17540843ac93fe8b05ff6c4fbea7bcf68 docs/STREAMS.md +f2f8916e5782bc9d4deef3405c36eeddcc1c1dde5198f9b415407c0351bf364e docs/TRANSPORT.md +f4c23ee07723a78e1e104991c99bac6be61d72b7b912141e69cffffddf7232fe docs/TROUBLESHOOTING.md +23765b0d3e32766966e922aa79951b8bc94fc93c0ffa38824e9dfc7e6cd4a1aa docs/VHOSTS.md +d8f30b1aaaa6a3a4679dbc4bfdbddb85b065b2fb5bf7c2760e7f92d2fbbf417b docs/WEB_SERVER.md 5502e8ce44276213803b66c58e3a4d804a1354f5ed5d83f47ee805e8c44ab088 examples/acl-headers.json 2431dd88a2d9ad2cf3cd4b8fcdfa602563ab1013c612d8917fddbe7febfb2dc3 examples/cache-dashboard.json e31ac05a99615e4ea6c4c9a8b2e090968583fe3959f838a600b5740cb170f8c5 examples/config.json @@ -125,7 +102,7 @@ a641bd35400c56f3b68b0a92e97b602d06090f03972ca8f01fa744e90dd97403 scripts/deploy 2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migration/migrate_npm.py a459d95bbb0076f33ade8b273b813308167f9d8d45242b309a2d18320ca0093c scripts/migration/migrate_npm_api.py 31ccad3ec3c880119f3b7002f0c6652f8b43634edad8ab3763cb0e4497d0bb1f scripts/migration/migrate_npm_core.py -e380bf369dcfd16e0e82d243d072bf8739e005d66b049c9b849a0d8b2d34162c scripts/preflight.py +901a6ff645fa4796bbf2c7fae53d085eaf24f9421a34965c431b81b41d6501ea scripts/preflight.py 3d6a0503afc25beab79ddc86864bcbb15248ded7368d33aa64321b8cdff59933 scripts/resolve_dependencies_0312.sh 20124173452b4514bad8850c1637569a9a191882478451e549bd16b432605fc1 scripts/runtime/acme_provider.py e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/runtime/acme_rfc2136.py @@ -151,7 +128,7 @@ b4a2a068acfa1143b0032b9cd072c19cf3fdda2580f0b932d11d6368cd2ab3ad src/admin/cert 51f46a2cb3167c4e3a784dfbb59b8fe0e9d5d3d51365b26699498e31d0afb6a9 src/admin/certificates.rs b5f6d49f1add583cfc8cd4aa36ffe4db56dac4de3b151fe8239fe42f507019cc src/admin/dashboard_presets.rs 5e150d4ef0413a2f92db5b6631669933b84d4b4796633d01476c4b39188cd05c src/admin/documentation.rs -3bc1425c0dd142471a48391474ab2551de7d6f2b788280ed4788ab94a435c712 src/admin/documentation/documents.rs +ea7aafb4466caa66c6147f0e08d189c42222c43212bd7acd8e8842fd935a48ef src/admin/documentation/documents.rs 67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs 1ce779b84aa8f47bdb3c34f83593c1f2f06d2235f0319847096cfcd035434168 src/admin/file_log_search.rs ab18acf6fb4ac86fb22635b9219a12dac76d24952c8f15538486267a9b4b7855 src/admin/file_logs.rs @@ -175,7 +152,7 @@ a40adfbaa908da3ea726d7aa3d45394a70a6a55b6fc0063270ec4d2a2f2026a3 src/admin/sess 4b8a0483ede651d08489a4d6e5bb616f06423094bbbefcee70b8755d65c04398 src/admin/status_live.rs c97881b17b7a42511868b0e48e2f25aab6fb5b613d538f5e1f1967508454e486 src/admin/status_system.rs cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs -58c9dbabfcfe37ce41d18e38777ead17781da91b3fc97bff19073fa09b262bd2 src/admin/tests.rs +4a49ec5b515034e839e1a38e9950d0915538340d1871987578b8a10c2b0a8042 src/admin/tests.rs 48fcd818c522fe80f2c7b2e218f87de6a7772ffd0a24e93502780b03ec78e335 src/admin/web/acme.js c0d08c5b8fc13dec9bf53fd842d224b9df3874fc3bdf31413e3ab182a8cec2aa src/admin/web/app/backend-dashboard.js a085ee336e6669229f3ad2f74536f19bd088412ca79bcaff30dff293f1e1baf9 src/admin/web/app/chart-scales.js @@ -221,7 +198,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/ 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg 7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js -ab762658c3f668a2890d275a869cecb7255352bbd97c8b6b619572627f3196f9 src/admin/web/index.html +0f1b53afcaac4f9ab6a0b9401d3200ec32bd42c1464e6e8c3de73559e2312a70 src/admin/web/index.html 03ad9572bb14c34059636d612cf6b876d6361ce601817cff674c57b715dc65dd src/admin/web/integrations.js 7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html @@ -309,11 +286,11 @@ e0146bf5d2e2e33aedcccd9205e5bd457559c9d77dc7ce266f8c165d1b867f0f src/compressio 7bc8285cb2012506fd1a38a71dffa561f885f4ab65a9b2a7c8344319e3cd1cf6 src/config.rs 56b3d37e7447995bec2fdda1c17dcc74b60958a63ca88b88ff57e56347d8b0b9 src/config/backend.rs beb831a208c4e5f7ebe8327686b1e0edc66608c7032f895cb4fc3366b189aae0 src/config/compile.rs -dfa89a9e214d0b33b4efce8fa678f1fec17d17656e203acf84dfd09ce33868e0 src/config/internal_routing.rs +a4bb70f4da2ae1fa890e65ec15f87e919e2288637dc0eb2c383234e8babbcb05 src/config/internal_routing.rs 40e09627e655d9319b576592f8d635e8c2da12905e5cee5952f7b6596010eb8c src/config/model.rs fc993c356386a9ff17379d48a70ef388df5b2b10ca9ee04f06342f68f9dbbaae src/config/routing.rs -a7ccffdeeb258adae41c616022ed0181cf590d708d305d48c63317e4a3f68699 src/config/snapshot.rs -41a4fa4f9a919a8c848ee01d01423d82a136790c9d74f27319c1e5a6d9f18097 src/config/tests.rs +48ecd9de5a54b0a4aa484f38a7e171f60a033b725e14b158d1ef07b67997f9eb src/config/snapshot.rs +975f168b0630f71ed6eb1b27fef8a3d5c356238a0ef61b85fd9401416dbecd4b src/config/tests.rs 23541660151d9005555a07db6d491455d61b185f0cc70c3ec3a0bee2aef08dc3 src/db.rs 30f0b7269224a64e3f1ec9e6fbe9e27ddb1d308dc84cc9882e50545cf612695d src/db/certificate_api.rs 20ba52d3755c359d670968efa53b0604ff7cbed63a416f9f765b50a1f45074ac src/db/dashboard_presets.rs @@ -397,12 +374,12 @@ cc7fe83f42acb19d79abe83107427cdc6b0ed14083702f4fe66af22f7cdfc4cc src/proxy/head 55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs 2280841494c3e14ccc5e3f02b026fe3c4328a3dab04ab449b9286bb69e39c028 src/proxy/request_id.rs b9a2307c6aa7f16613d935508a61574dfee964020b1b5be86293df2d684615d8 src/proxy/request_id/tests.rs -94a50c0bf147565098e06672df3625e44d5af34ef1f12fd355d82cc7a72cf033 src/proxy/tests/authority_tests.rs +977a28e0a90a72ac902591996fe97236240f0e05c5e668e7154f8f64b1b47ce0 src/proxy/tests/authority_tests.rs 1aa9bbbfb2659facdaeaf185e11221b1c8cd5fe6e23c7c1dacd3aa55e3db4fcb src/proxy/tests/cache_fast_path_tests.rs 0f2eacadf1447db8aa1c1a78005aa262e0f747096bfe7e0a0e41a2cd31a0d17f src/proxy/tests/h3_navigation_tests.rs d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs fecc1c7ffff740caa7168370e59234a17b9836a6dce6d08b770ee9648c22709b src/proxy/types.rs -0f9655d4ca1de35dcd7d5807d146cb3127a78b993beaeec7deec30e2f4040bb8 src/proxy/upstream_helpers.rs +84713a5904f9687a72aafaff6f1dbfc997343a97a4f3031d498c6a4b01d7db93 src/proxy/upstream_helpers.rs 2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs 69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs diff --git a/docs/ACCESS.md b/docs/ACCESS.md new file mode 100644 index 0000000..5b2f3b6 --- /dev/null +++ b/docs/ACCESS.md @@ -0,0 +1,34 @@ +# Access, authentication and traffic protection + +Access control can be configured globally, on a Vhost and on selected Custom locations. Policies are **cumulative**: a more specific allow rule does not cancel a denial at a broader level. Changes should be tested with both an allowed and a denied client. + +## IP, country and DDNS rules + +- **IP/CIDR ACL:** allow or deny IPv4/IPv6 networks. Use reusable ACL profiles for settings shared by several domains. +- **DNS/DDNS:** an ACL `allow` rule may use a hostname, such as `allow office.example.ddns.net`; `deny` with DNS is not supported. Records are refreshed periodically; unknown, failed or expired resolutions authorize nobody under the allow rule. This is appropriate for a trusted, controlled DDNS record, not a replacement for login. +- **Country access:** allow-only or blocking based on the configured GeoIP database. Allow-only mode fails closed when a country cannot be determined. Keep the GeoIP database updated in **Settings → GeoIP updates**. +- **Cloudflare client IP:** enable the integration only when traffic really arrives through a controlled Cloudflare/trusted-proxy boundary. Never trust arbitrary client-supplied forwarding headers as an identity proof. + +Default deny plus explicit allow exceptions is usually safer for private admin applications. Apply rules to a staging client before enforcing them on a live business-critical service. + +## HTTP methods and rate limits + +Method policies allow, block or leave methods unrestricted. A denied method returns `405`, while typical ACL/country denials return `403`. Per-client rate limits can configure request count, window and burst with optional country overrides; exceeding the limit returns `429` with `Retry-After`. Limits are application controls, not a replacement for upstream DDoS protection. + +## Basic Auth and Tinyauth + +- **Basic Auth:** protect selected Vhosts with configured credentials. Use HTTPS so credentials are not sent over plaintext. +- **Tinyauth:** add a verification URL and an HTTPS login URL. RustProxy makes a bounded, body-free authorization request and can pass specifically allowed identity headers upstream. An origin URL can use the service's conventional `/api/auth/nginx` path. Tinyauth has its own timeout and private-backend policy. +- The two authentication integrations should not be enabled simultaneously for the same Vhost. Protected responses bypass unsafe shared caching. Confirm that login redirections and WebSockets behave as expected. + +Do not use a User-Agent blocklist as the primary access control: HTTP clients can forge their User-Agent freely. + +## Headers, sessions and internal services + +Custom request/response headers support set/append/remove within allowed names. Security headers can be configured at Vhost scope; check interactions with HSTS and CSP in browsers. Hostnames, forwarding identity, request IDs, hop-by-hop framing and WebSocket negotiation are protected by validation. Never expose the administration HTTP listener through a public Vhost just to bypass its local-access default. + +**Streams are separate.** TCP/UDP Stream listeners do not run HTTP auth, GeoIP Vhost rules, headers, cache or TLS termination. Restrict them with firewall rules and protections on the actual protocol/backend. + +## Practical verification + +Test allowed/denied IPs, private application login, denied HTTP methods, country policies, websocket handshakes and rate-limit recovery. Use [Logs](LOGS.md) to identify `403/405/429` responses. For origin-side client IP settings, see [Configuration](CONFIGURATION.md) and [Security](SECURITY.md). diff --git a/docs/ACME_PROVIDERS.md b/docs/ACME_PROVIDERS.md index 4bb4ec9..dd6449c 100644 --- a/docs/ACME_PROVIDERS.md +++ b/docs/ACME_PROVIDERS.md @@ -1,139 +1,64 @@ -> 0.7 update: configured managed certificates now have automatic renewal and -> validated hot deployment. Initial issuance/import remains explicit. Read -> [RENEWAL.md](RENEWAL.md) for defaults, retained profiles and opt-out. +# Certificates, ACME providers and renewal -# Certificate providers +Certificates are managed under **Certificates**. You can import a PEM pair, reference accessible external certificate files or request a managed ACME certificate. Issuing a certificate does **not** automatically assign it to a Vhost; choose the certificate in the intended frontend/Vhost afterwards. -Source implementation; real provider/DNS/CA issuance and Rust runtime have not been -verified in the packaging environment. Offline tests substitute Certbot, so they -cannot establish that a DNS token, zone policy or CA deployment works. +## Supported certificate methods -## Workflow - -Open Certificates -> Add certificate. Choose one provider or import mode, enter -the domains and metadata, then request issuance. Production is selected by default -in the panel and still requires the separate explicit production confirmation. Staging -remains available for testing and its certificates are not browser-trusted. -After a successful job, import its validated pair into the library, then assign the -certificate to a vhost. No CA operation occurs on startup or merely opening a form. - -| Selection | Mechanism and required configuration | +| Mode | Requirements | |---|---| -| HTTP-01 / domain validation | Certbot webroot; domain names and a publicly reachable plaintext HTTP listener | -| Cloudflare | DNS-01; scoped API token; `certbot-dns-cloudflare` installed | -| OVH | DNS-01; `ovh-eu` or `ovh-ca`, application key/secret, consumer key; `certbot-dns-ovh` installed | -| RFC2136 | DNS-01; server/port, TSIG name/secret/algorithm; `certbot-dns-rfc2136` installed | -| PEM import | Upload fullchain/certificate and corresponding private key, then validate | -| Existing server files | Advanced: reference a PEM pair already in the certificate directory | +| **HTTP-01 (Let's Encrypt)** | DNS for every name and a public TCP port **80** reaching a plaintext RustProxy listener; HTTP challenge endpoint is handled by RustProxy. | +| **Cloudflare DNS-01** | A zone-limited `Zone:DNS:Edit` API token and functioning Certbot Cloudflare plugin. | +| **OVH DNS-01** | Correct regional API endpoint plus application key/secret and consumer key scoped to the DNS zone. | +| **RFC2136 DNS-01** | DNS server, TSIG name/secret/algorithm and an update policy restricted to the requested records. | +| **PEM import** | A matching certificate chain and private key, validated before activation. | +| **Existing local files** | Trusted absolute certificate/key paths accessible to the RustProxy service account. Suitable for a separately managed Certbot lineage. | -RustProxy now owns its preferred Certbot environment under `/acme-python`. -When it is missing, RustProxy creates a virtual environment and installs pinned Certbot -5.8.0 plus `certbot-dns-cloudflare`, `certbot-dns-ovh` and -`certbot-dns-rfc2136` 5.8.0 with pip. No distribution Python files are modified and -RustProxy never executes sudo. If the stdlib `venv` module is unavailable but system -pip works, `virtualenv` is bootstrapped only inside the RustProxy data directory. +HTTP-01 cannot create wildcard certificates; use DNS-01. A staging certificate is for testing and is not generally trusted by browsers. Production issuance requires explicit confirmation of the production operation and provider terms. -If local setup fails, a runnable system `certbot` from `PATH` is used as fallback. The -Certificates panel reports whether the managed environment, the system fallback, or no -ACME runtime is active. Systems without Python (for example some appliance layouts) -retain PEM import and existing-file certificate modes even though ACME issuance/renewal -is unavailable. `bash dev.sh acme --doctor` and `certbot plugins` inspect only the -current shell/system fallback; they do not validate provider credentials or DNS reachability. +## First issuance and assignment -The shipped OCI image contains Python, venv/pip and distribution Certbot packages as an -offline fallback. Its preferred managed environment is still created in the persisted -`/var/lib/rustproxy` data volume. +1. Open **Certificates → Add certificate** and choose the provider/import method. +2. Enter DNS names, a unique certificate ID, contact email where requested and provider-specific credentials or files. +3. For HTTP-01, confirm public DNS and HTTP port 80 reach RustProxy even when a separate public HTTPS port is active. For DNS-01, validate authoritative DNS provider and propagation permissions. +4. Run staging first when testing new credentials/DNS, then confirm production issuance deliberately. +5. Wait for the job result; inspect errors under Certificates and the service logs. +6. Assign the issued/imported certificate to the intended Vhost/listener and verify a real TLS handshake with the public hostname. -## HTTP-01 requirements and scope +The reserved HTTP-01 challenge path is available only during eligible issuance/renewal work. It is not a generic public file server and does not bypass authentication for unrelated URLs. -Public TCP port **80** for every requested name must reach an active plaintext -RustProxy listener. NAT from public 80 to a private/custom HTTP port is allowed; -setting only port 8080 in public DNS is not sufficient. Check all published A/AAAA -addresses and multi-node routing. TLS-only listeners are not enough. There is no -TLS-ALPN-01 mode. This implementation accepts DNS names, not IP certificates. -Wildcard certificates require one of the DNS-01 providers, not HTTP-01. +## Managed ACME runtime -During a running HTTP-01 job, RustProxy serves only -`/.well-known/acme-challenge/` for that job's exact normalized hostnames. -Only GET/HEAD and bounded regular files from `certs/acme-http01` are accepted; -queries, traversal, unrelated names, HTTPS requests and inactive jobs cannot read -these files. The reserved path precedes application auth/cache/static pages so a -CA can validate ownership without logging in. This exception does not bypass -application authentication for any other path. Responses are no-store. +RustProxy prefers a **private Certbot/Python environment** under the application data directory (typically `/var/lib/rustproxy/acme-python`), with support for the Cloudflare, OVH and RFC2136 plugins. A working system Certbot may be used as fallback; systems without a usable runtime can still import or reference PEM files. The application does not run system-wide `sudo` to install plugins or modify distribution-owned Python packages. -A CDN or upstream proxy must forward this path and the correct Host without -rewriting it. There is no automatic forwarding configuration at your DNS/CDN vendor. -Expired challenge jobs do not leave an open generic file server. +Provider tokens and TSIG secrets are security credentials: use least privilege, protect files and the data directory, rotate compromised values and restrict backups. ACME DNS credentials may be stored in private provider/Certbot files; do not assume they are stored in the same way as RustProxy administration Secret Store records. -## DNS credentials and least privilege +## Automatic renewal -Cloudflare: use an API token with `Zone:DNS:Edit` restricted to the zones used for -issuance. The adapter intentionally does not accept the account-wide Global API Key. +Managed certificates have configurable automatic renewal. RustProxy runs the scheduler while the service is active; this is **not** a separate cron service. Certificate status and next checks are visible in Certificates. The scheduler uses spaced checks and retry/backoff on failure, without automatically forcing a CA reissue on every check. -OVH: use a consumer key allowing `GET /domain/zone/` and GET/PUT/POST/DELETE for -`/domain/zone//*`. Repeat only for required zones, not the whole account. -Choose the region matching the account. Consult the official plugin docs if API -policies change. A domain registered at OVH but DNS hosted elsewhere must use the -actual authoritative DNS provider; registration alone is not sufficient. +After successful renewal, RustProxy validates the new pair and publishes it to new TLS handshakes without interrupting existing connections. Invalid/rejected material should leave the previous active pair in place. Keep room for archived ACME generations and monitor expiry; this is not an independent external expiry-alert service. **Do not run an overlapping system Certbot timer** against the RustProxy-managed ACME profiles. -RFC2136: retain the TSIG-limited update policy described in ACME_RFC2136.md. DNS -provider propagation delay is configurable. The helper rejects malformed tokens, -unknown/cross-provider fields and INI injection. It does not test your DNS policy -in advance. Wildcard DNS-01 issuance does not add wildcard HOST routing: vhost -hostnames remain exact matches. +## Externally managed Certbot / PEM files -## API input +When another service owns the certificate, add a manual certificate with the actual certificate and key paths, for example: -Common fields: `provider`, `certificate_id`, `domains` (one or more DNS names), `email`, -`environment` (`staging`/`production`), `agree_tos`, `confirm_production`. RustProxy -does not impose its own domain-count ceiling; the selected CA/provider may still -apply its external certificate/SAN policy. -DNS providers accept `propagation_seconds`. +```text +/etc/letsencrypt/live/example.com/fullchain.pem +/etc/letsencrypt/live/example.com/privkey.pem +``` -Provider-specific fields: +Keep the original Certbot renewal system and use the RustProxy certificate reload/deploy API or local CLI after rotation. RustProxy resolves relevant symlinks again during reload. The service account must have directory traverse and narrow read permissions to the **current** key file; `ReadOnlyPaths` alone does not grant filesystem permission. Renewal hooks may need to reapply ACLs to replaced target files. -- `http01`: none; wildcard domains are rejected. -- `cloudflare`: `api_token`. -- `ovh`: `endpoint`, `application_key`, `application_secret`, `consumer_key`. -- `rfc2136`: `dns_server`, `dns_port`, `key_name`, `key_secret`, `key_algorithm`, `sign_query`. +### Restricted certificate API -`POST /api/acme/issue` is authenticated and CSRF-protected; one job runs at a time. -`GET /api/acme` is safe status metadata only. Job status is process-local; account -and credential files persist. A completed certificate has optional issuer metadata -with provider/environment, not a copy of request credentials. +Create an appropriately scoped credential in **Settings → Certificate API**. Its allowed certificate IDs and action permissions control **reload** or **deploy**; it is not a general administration token. A caller supplies both a Bearer credential and its configured `X-RustProxy-Certificate-Key` ID. Use HTTPS outside local/trusted transport and never store the clear token in the repository. See [Administration API](API.md). -## Secret storage, renewal and limitations +## Problems to check -Provider secrets are sent to the private worker on stdin, not command arguments. -They are not stored in exported JSON, SQLite, localStorage or audit records. -Files are private (0600 credentials, 0700 directories); these permissions do NOT -encrypt them. The service user/root can read them. Certbot account/lineage state -and provider credential files persist for managed renewal under -`certs/acme/--`; RFC2136 keeps its old path without a -provider suffix. Issued PEM outputs use new immutable names before library import. +- **HTTP-01 validation fails:** public TCP 80, all A/AAAA DNS answers, Host forwarding and challenge path handling through any CDN/router. +- **DNS-01 fails:** authoritative DNS provider, correct zone, TSIG/API scope and propagation interval. +- **Certificate available but wrong site certificate displayed:** Vhost assignment, listener binding, hostname and restart/reload state. +- **Renewal stops:** Certbot runtime, private profile files, DNS secrets, clock skew, permissions and CA limits. +- **External file reload fails:** rotated symlink targets, certificate/key match, SAN coverage and read permissions. -Back up these private profiles separately while issuance is idle: the standard -SQLite/configured-PEM backup does not include every account/credential profile. -Do not put real credentials into shell history or source-control examples. -Restrict the admin interface and service environment. Rotate exposed API credentials. - -Configured managed certificates in 0.7 renew through the shared RustProxy worker; -validated changed material is published automatically while failed checks preserve -the active generation. Use the certificate's toggle to disable scheduled renewal. -PEM-only imports remain manual. A separate system Certbot timer may own a **different, -manual RustProxy certificate id** and publish it with `tls deploy --id ...`; it must not -also own a RustProxy-managed ACME profile. There is no external expiry alert, revocation -or certificate garbage collector. Cancellation -kills the worker group; interrupted DNS cleanup can leave TXT records an operator -must remove. See [renewal and recovery](RENEWAL.md) and test CA staging first. - -## Primary references - -- HTTP-01/DNS-01 constraints: https://letsencrypt.org/docs/challenge-types/ -- Certbot webroot and operational use: https://eff-certbot.readthedocs.io/en/latest/using.html -- Cloudflare plugin/API scope: https://certbot-dns-cloudflare.readthedocs.io/en/stable/ -- OVH plugin/API scopes: https://certbot-dns-ovh.readthedocs.io/en/latest/ -- RFC2136 plugin: https://certbot-dns-rfc2136.readthedocs.io/en/stable/ - -These document upstream requirements, not evidence that this RustProxy build has -passed live provider or CA acceptance. +Keep a private encrypted application backup and a separate copy of any external certificate/key material. See [Backup and recovery](BACKUP.md) and [Security](SECURITY.md). diff --git a/docs/ACME_RFC2136.md b/docs/ACME_RFC2136.md deleted file mode 100644 index 29c1873..0000000 --- a/docs/ACME_RFC2136.md +++ /dev/null @@ -1,160 +0,0 @@ -# Basic Let's Encrypt / RFC2136 integration - historical 0.5.0 record - -> Historical implementation record. For current certificate-provider behavior, use [ACME_PROVIDERS.md](ACME_PROVIDERS.md) and [RENEWAL.md](RENEWAL.md). - -For the multi-provider UI, HTTP-01, Cloudflare and OVH, see ACME_PROVIDERS.md. -Managed automatic renewal added in 0.7 is documented in RENEWAL.md. -The RFC2136 fields and private account paths below remain supported. - -## Scope and prerequisites - -This is an integrated control-plane adapter for **Certbot + certbot-dns-rfc2136**, -not a newly written ACME or DNS-update protocol client. The plugin creates and -cleans up DNS-01 TXT records through TSIG-signed RFC2136 updates. It must be -installed separately. No real CA issuance or authoritative-DNS update has been -performed in the packaging environment. -Rust compilation, process integration and actual certificate deployment remain -acceptance work (TEST_REPORT.md). - -Use Linux/Unix with Python 3.10+, a current compatible Certbot/plugin installation, -and `python3` and `certbot` available in the RustProxy service's PATH. Check: - -```bash -bash dev.sh acme --doctor -certbot --version -certbot plugins --authenticators -``` - -The doctor command reports executable discovery and the required plugin's name; -it does NOT verify the plugin, DNS permissions or network connectivity. Nothing -is installed automatically, and RustProxy never invokes sudo. A service hardening -policy must allow child processes, the cert directory, DNS updates/queries to the -chosen server and outbound HTTPS to the CA. Do not run the proxy as root just to -make Certbot available. Use a private, service-owned cert directory (0700). - -The operator must control publicly valid DNS names and an authoritative DNS -server that permits the configured TSIG identity to change **only the necessary -_acme-challenge TXT names**. Do not grant zone-wide update authority merely for -certificates. A private update-server IP is acceptable when the resulting TXT -records are visible to the public validation resolvers. Split DNS/views, CAA, -replication delays and delegation must be checked on the operator's DNS setup. -DNS-01 does not require opening the application's HTTP listener for a challenge. - -## Panel workflow - -Open **Certificates > Let's Encrypt / RFC2136**. Enter a certificate library ID, -contact email, comma-separated DNS names (including optional `*.example.org`), -DNS update server IP/port, TSIG name, base64 secret, algorithm and propagation -wait. ASCII/punycode DNS names only; up to 16 names. SHA256 is the default TSIG -algorithm; SHA384/SHA512 are also allowed. SHA1/MD5 are not exposed. A TSIG key -name may be a single label such as `keyname.`. Optional signed SOA queries support -DNS servers/views that require them. Port defaults to 53; propagation defaults -to 60 seconds (allowed 10..900). - -Start with **staging**. Its certificates are not publicly trusted. Production -requires a separate explicit confirmation, in addition to agreement with the -Let's Encrypt subscriber agreement. The CA directory is fixed for each mode; -arbitrary directory URLs, shell hooks and arbitrary extra Certbot options are -not accepted by this interface. - -Requesting starts one bounded asynchronous job, not a proxy-path operation. Refresh -shows running, failed, or ready. At ready, the Rust control plane has checked PEM -parsing, matching key and every requested DNS name before exposing only metadata. -Click **Add issued certificate to library**, then select its ID in the vhost SSL -tab and save. Replacing an existing ID requires confirmation and retains its -legacy SNI host assignments; the whole current configuration is revalidated with -revision CAS. Failed issuance/validation/import leaves active configuration alone. -Certificates are loaded for new handshakes; changing listener protocols still -requires restart. Existing connections retain their negotiated certificate. - -## Renewal and operator responsibilities - -Configured RFC2136 managed profiles participate in the 0.7 renewal scheduler. -Validated changed certificate material is published automatically; there is a -per-certificate opt-out, explicit Check now and Dry run. Initial issuance/import -remains explicit. The old 0.5 helper alone was issuance-only; do not confuse -retained files or an unrelated OS Certbot timer with RustProxy publication. -See RENEWAL.md for prerequisites, timing, rollback boundaries and legacy profiles. -No external expiry notification, revocation UI or account manager is included. - -Account state and TSIG credentials are kept privately under: - -```text -data/certs/acme/-/ - rfc2136.ini # SECRET, mode 0600 - profile.json # non-secret parameters, mode 0600 - config/ # Certbot account, renewal, live and archive data - work/ - logs/ # private Certbot diagnostics -``` - -The location follows the configured certificate directory, not always `data/certs`. -There is one job per RustProxy process and a filesystem lock per profile. Bounds: -16 KiB request, 128 profiles, 128 staged ACME pairs and 128 KiB per PEM file. The -provider timeout is 1200 seconds plus propagation wait; the Rust worker has a -2160-second outer deadline and stops its process group on cancellation/shutdown. -Normal TXT cleanup belongs to the plugin. A hard kill, machine crash or DNS error -can leave challenge TXT records: inspect DNS and clean only the failed challenge -values, not unrelated records. Only the latest job status lives in memory; restart -loses that status, not the private Certbot files. - -TSIG/API credentials are supplied through stdin to the helper, never argv, exports -or job responses. RustProxy encrypts DNS-provider credentials in the Secret Store; -Certbot-compatible `.ini` files are recreated with private permissions only for the -external operation and removed afterwards. The browser clears submitted secret -fields and does not put them in local/session storage. Protect the Certbot account -state and private logs as secrets. - -Use `rust-proxy backup create` for current installations. The encrypted full backup -includes the durable ACME profile/account state, certificates and Secret Store key -material needed for renewal. The historical `dev.sh backup` helper is not sufficient -after Secret Store migration. See SECRET_STORE_BACKUP.md. - -## API and local helper - -`GET /api/acme` returns the latest sanitized job, provider name and -`automatic_renewal: false`. `POST /api/acme/issue` requires the normal authenticated -session, Origin/CSRF protections and an explicit bounded JSON request: - -```json -{ - "certificate_id": "example", - "email": "operator@example.org", - "domains": ["example.org", "*.example.org"], - "environment": "staging", - "agree_tos": true, - "confirm_production": false, - "dns_server": "192.0.2.53", - "dns_port": 53, - "key_name": "rustproxy.example.org.", - "key_secret": "REPLACE_WITH_YOUR_PRIVATE_BASE64_TSIG_SECRET", - "key_algorithm": "HMAC-SHA256", - "sign_query": false, - "propagation_seconds": 60 -} -``` - -This documentation example is intentionally not an executable credential. A -successful job submission returns 202; busy returns 429. Invalid input, consent -and provider failures use fixed `acme.*` diagnostics rather than returning -provider stderr or credentials. Look in the private profile logs for DNS/plugin -errors. A 202 response means accepted, not issued. - -Advanced local staging without the running API: - -```bash -# request.json must be private (0600), with real operator-owned parameters. -bash dev.sh acme --cert-dir /absolute/private/cert-directory < request.json -``` - -This CLI only stages files and prints metadata. Unlike the integrated API, it -cannot run Rust's key/SAN validation on its own and never activates a certificate. -Import through the normal validated library/config workflow. Do not post secret -JSON into shell arguments, source examples, tickets or logs. - -## Primary references - -- Plugin: https://certbot-dns-rfc2136.readthedocs.io/en/stable/ -- Certbot CLI: https://eff-certbot.readthedocs.io/en/stable/man/certbot.html -- Staging: https://letsencrypt.org/docs/staging-environment/ -- Challenge background: https://letsencrypt.org/docs/challenge-types/ diff --git a/docs/API.md b/docs/API.md index c244ddc..fd4a755 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,686 +1,64 @@ -# Administration API +# Administration API and configuration automation -The API is served on the separate administration listener, normally -`http://127.0.0.1:9090`. It is never mounted on reverse-proxy HTTP/TLS listeners. -Loopback HTTP is the default. Settings can enable administration HTTPS, choose a -certificate and persist a non-loopback bind; plain remote HTTP requires the explicit -startup `--allow-remote-admin` gate. Responses carry no-store, CSP and other browser -protection headers. +RustProxy exposes an authenticated administration API on the **separate administration listener** (normally `127.0.0.1:9090`). Open **`/docs → API`** after signing in to view the bundled OpenAPI/Swagger schema, including request bodies and available actions. That schema is the endpoint reference; server validation remains authoritative. -## Public-vhost HTTP cache invalidation (opt-in) +## Selected API endpoints -`PURGE /path?query` and `BAN /prefix` operate on the normal public vhost -listener and **do not** use an admin session. They are independently denied by -default unless the administrator explicitly populates the cache's -`http_invalidation_allowed_cidrs` allowlist in **Settings → Cache**. Authorization -uses the existing trusted client-IP forwarding logic, vhost/host ACL and rate -limits. BAN is a lazy prefix rule; PURGE evicts an exact URL. Both are restricted -to the request scheme (`http` vs `https`), Host and matched vhost. A shared -two-worker maintenance guard also bounds administrator-driven invalidation; -additional requests receive 429. Ambiguous duplicate control headers and -request bodies are rejected. See -[CACHE](CACHE.md#http-ban--purge-from-authorized-client-ips) for request examples, -scoping, confirmation and reverse-proxy requirements, and -[security review](SECURITY_AUDIT.md) for audit findings. The authenticated -`/api/cache/invalidate` endpoint remains unchanged. +The current bundled `src/admin/openapi.json` declares these operations (among others): -## Browser documentation and anonymous surface - -Open `/docs` **on the administration listener** after signing in. The default **Reader** -shows the bundled project Markdown documentation; the separate **API** tab renders the -OpenAPI/Swagger interface. The documentation bundle, OpenAPI JSON, JavaScript, styles and -Swagger vendor assets all require the same session. API writes are opt-in and CSRF-protected. -See [API_ACCESS.md](API_ACCESS.md). - -Only GET/HEAD `/`, the panel routes `/overview`, `/vhosts`, `/streams`, -`/acl-profiles`, `/web-server`, `/configuration`, `/tls`, `/cache`, `/logging`, `/settings`, -`/assets/login.js`, `/assets/login-langs.json`, `/assets/style.css`, `/favicon.svg`, -`/healthz`, `/api/auth/status`, and POST `/api/auth/setup`, `/api/auth/login` are -anonymous. Panel routes serve the same guarded shell as `/`, so a browser refresh -preserves the selected subpage instead of falling back to the landing page. Root -optionally authenticates and selects login or operational HTML. `/healthz` is exactly `{"status":"ok"}`. -Unknown/private paths and method mismatches return a generic 401 without a session. -Only top-level `/docs` and `/docs/` navigations redirect to `/?next=docs`; API/schema -fetches never redirect to login HTML. Other invalid Host/Origin checks can return 403. - -## Authentication - -| Method / path | Input or result | +| Method and path | Purpose | |---|---| -| `GET /api/auth/status` | Public `setup_required` boolean only; no version or operational state | -| `POST /api/auth/setup` | One-time `{token, username, password}`; creates no session | -| `POST /api/auth/login` | `{username, password}`; sets session cookie and returns session data | -| `GET /api/auth/session` | Username, CSRF token, absolute expiry and idle timeout | -| `POST /api/auth/logout` | Revoke the current session | -| `POST /api/auth/logout-all` | Revoke all administrator sessions | -| `POST /api/auth/password` | `{current_password, new_password}`; changes password and revokes sessions | - -Mutations, including setup/login, require `Origin` equal to the active administration scheme plus `{Host}` and -`X-RustProxy-Request: 1`. Authenticated mutations also need `X-CSRF-Token` from -session data. Send JSON as `application/json`. A permanent bearer access token -is not supported; the old setup capability does not authorize API reads/writes. - -The `rustproxy_session` cookie is HttpOnly, SameSite=Strict and Path=/ with the configured -7–30 day absolute lifetime (default 30 days); server-side revocation still applies. HTTPS -administration adds the Secure attribute; behind a trusted HTTPS-terminating reverse proxy -the external forwarded scheme controls that flag. A protected SSH/VPN tunnel remains valid, -while direct remote administration should use HTTPS plus firewall/private-network policy. -Direct IP/localhost Host values must use the actual administration port. A DNS name explicitly listed in `administration.allowed_hosts`, or automatically -derived from the validated `administration.public_origin`, represents the -browser-facing authority and may use the trusted reverse proxy's external port. Foreign Origin/cross-site requests are rejected. -There is no permissive CORS mode. -Auth request bodies are limited to 4096 bytes. - -Typical errors include 401 for missing/invalid credentials, 403 for origin/CSRF -rejection, 409 for already-completed setup, 422 for password policy and 429 for -throttling/busy workers. Error objects usually contain an `error` code/string; -framework extraction failures are not guaranteed to have that JSON shape. - -## Administrator Overview layout presets (0.33.7) - -The dashboard preset endpoints are private control-plane routes and use the -existing administrator session cookie. The same Origin, request-marker and -CSRF rules as other settings writes apply. They do **not** change the proxy -configuration revision and cannot be called from public vhosts. - -| Method / path | Request or result | -|---|---| -| `GET /api/dashboard-presets` | Array of named layouts for the authenticated administrator | -| `PUT /api/dashboard-presets/{name}` | Create/overwrite a named layout; JSON body described below; returns 204 | -| `PATCH /api/dashboard-presets/{name}` | Rename using `{ "name": "new name" }`; returns 204 | -| `DELETE /api/dashboard-presets/{name}` | Delete the named layout; returns 204 | - -A layout is `{ "dashboard_widgets": ["..."], "dashboard_hidden_widgets": ["..."], -"dashboard_widget_sizes": null, "dashboard_density": "compact", -"overview_samples": 600 }`, using registered widget IDs and the same -width/density validation as administrator view preferences. History accepts -60, 120, 300 or 600 samples. For sizes, `null` preserves legacy grouped -placement; an object maps widget IDs to valid 12-column spans. The account -may store at most 16 named layouts; names are 1-64 UTF-8 bytes with no -control characters, surrounding whitespace or slash. Invalid values receive -422; full capacity or a conflicting rename receives 409. Unknown delete -returns 404. All write endpoints require a session-bound CSRF token. - -The data resides in SQLite schema 10 (`dashboard_presets`), separate from -`admin_preferences`; the factory preset is not stored as a mutable row. -The browser saves the chosen history sample count using its existing local -preference when loading a preset. The client does not use JSON configuration -files to store named presets. - -## Configuration and observations - -| Method / path | Result | -|---|---| -| `GET /healthz` | Public process health/version; not a backend health probe | -| `GET /api/config` | `{revision, config}` | -| `GET /api/config/history` | Retained revision metadata; `?revision=N` returns that document plus the previous retained document | -| `POST /api/config/history/restore` | Input `{revision, expected_revision}`; validates the older retained document and publishes it as a new configuration revision | -| `PUT /api/config` | Input `{expected_revision, config}`; returns the applied envelope; retained for atomic multi-section changes | -| `PUT /api/config/{section}` | Input `{expected_revision, value}`; replaces one validated section and returns the applied envelope | -| `GET /api/backends` | Authenticated per-vhost backend observations and active/desired frontend plans | -| `GET /api/status` | Uptime, active revision, configured counts, listener addresses, feature flags, aggregate metrics, active/desired TCP/UDP streams with per-stream L4 counters, active/desired administration state, TLS policy/cipher inventory and structured-log sink counters | - -`GET /api/status?view=live` returns only volatile telemetry suitable for SSE fallback, without the heavy full configuration/OS/runtime objects. `GET /api/system-info` returns cached system diagnostics, host/runtime metadata and memory accounting under `system`, `runtime` and `memory_accounting`. Both are protected by the same cookie/origin admin guard as full status. - -`status.streams` contains `active`, `desired`, `restart_required` and a `metrics` map keyed by startup-active stream ID. Stream counters include active/total/completed sessions, rejects, errors, RX/TX bytes and UDP packet counts; the dashboard derives rates from byte-counter deltas. -| `GET /api/logs` | Recent bounded access entries, ring capacity and contention-drop count | -| `GET /api/audit` | Last 100 audit entries; database retention is 10,000 | -| `GET /api/events` | SSE `snapshot` events containing status and logs; the compact status includes `live_system` for CPU/RSS/network charts | -| `GET /metrics` | Prometheus text exposition | -| `GET /api/metrics` | Alias of `/metrics` | - -Except for health and authentication/bootstrap routes, these endpoints require a -session. These administrative previews are not anonymous. The separate configurable -exporter uses its own CIDRs and optional bearer token; see the scrape section below. - -SSE has eight viewer slots, generates a snapshot every 1/2/3/5/10 seconds per authenticated client (via `?interval=`, default 3) -and rechecks session validity. It does not queue an unlimited per-viewer event log -or guarantee replay. The browser falls back to compact status/log polling only after SSE fails and stops polling after its recovery. Invalid SSE intervals are rejected with 422. An active dashboard renews -idle activity, not the absolute session deadline. - -Configuration history, introduced in SQLite schema 8 and retained in current schema 10, keeps the newest 512 successful manual/configuration revisions. `GET /api/config/history` is session-protected like the rest of the administration API; `limit` is bounded to 512 and `revision` selects one retained revision for diff/inspection. `POST /api/config/history/restore` also requires the normal CSRF/request-marker protections, rejects stale `expected_revision`, and never rewinds the revision counter: restoring revision 85 while current revision is 120 creates revision 121. - -Configuration updates return 409 for a stale revision, 422 for validation failure -and 429 when the eight-operation update admission bound is reached. Persistence and -snapshot publication finish before a successful response. Disconnecting the caller -after the detached writer starts does not cancel an accepted write. -The document limit is 256 KiB, with an additional 1024-byte request-envelope allowance. -For ordinary Settings saves prefer `PUT /api/config/{section}`. Supported sections are -`listeners`, `response`, `cache`, `logging`, `forwarding`, `headers`, `prometheus`, `geoip`, -`administration`, `tls`, `vhosts`, `web-services`, `redirects`, `not_found_hosts`, `streams`, `acl-profiles` and `host-policies`. -Each section write still uses revision CAS, full validation and atomic persistence. -Only a change to the derived cache-compatibility identity purges hot/cold cache; -unrelated section saves do not. - -## Configuration fields - -The canonical document uses `vhosts`, `web_services`, `redirects`, `listeners`, `administration`, `tls`, `logging`, -`prometheus`, `geoip_updates`, `cache`, `compression`, `server_header`, `headers`, `host_policies` and other defaulted -settings. Unknown fields are rejected. `backends`/`routes` at the top level are -input-only legacy migration fields, omitted from saved canonical documents. - -```json -{ - "vhosts": [{ - "id": "application", - "frontend": { - "hosts": ["app.localhost", "alias.localhost"], - "listeners": [{"address":"127.0.0.1:8080", "tls":false, "http2":false}] - }, - "backends": [{"id":"application", "url":"http://127.0.0.1:9000", "allow_private":true}], - "routes": [{"id":"root", "path_prefix":"/"}] - }] -} -``` - -The backend service must already exist. Routes without `backend_id` select from -the vhost pool; an explicit ID pins that path to a member of the same pool. -`vhosts[].force_ssl=true` redirects matching cleartext requests with HTTP 308 to the -same hostname/path/query. Force SSL requires both a selected plain HTTP listener and a -selected TLS listener. Port 443 is preferred when several TLS ports are selected; -otherwise multiple non-standard TLS ports are rejected as an ambiguous redirect target. -`vhosts[].forward_client_ip` defaults to true. Set it to false only when an application -must see RustProxy's transport peer instead of the visitor: backend requests then omit -`Forwarded`, `X-Forwarded-For` and `X-Real-IP`, while `X-Forwarded-Host/Proto/Port/URI` -remain and RustProxy/Tinyauth still use the real client address. See examples and -VHOSTS.md. HTTPS upstream certificate verification is disabled when `tls_verify` is omitted. Set `tls_verify: true` to validate the origin certificate chain and identity. For verified IP-based origins, use a DNS `tls_server_name` present in the -certificate and provide `tls_ca_pem` when the issuer is private. Disabling verification -retains TLS encryption and all backend address/DNS policy checks. Certificate metadata references -files under `/certs`; it does not create a vhost or expose a listener. -First-class redirect hosts live under `redirects`; see `REDIRECTS.md` for matching, target, status, path/query and cache behavior. - -ACL rules have `network` CIDR and `action` (`allow`/`deny`). Omitted ACL means no -restriction in that scope; an explicit empty ACL defaults to deny. Matching deny -wins. Host and route decisions are ANDed. Identity is the socket peer, not an -untrusted forwarded header. There are up to 128 rules per ACL. - -Headers run global, then vhost (where applicable), then route rules. `set` replaces, `append` adds a -separate value and `remove` accepts no value. Transport, WebSocket handshake and -client-identity headers are protected. Up to 32 rules per direction/scope, 128-byte -names and 8192-byte values are allowed. Response rules and matching vhost security policy also cover locally generated -proxy errors/denials. Multiple Set-Cookie fields remain separate. - -Routing matches exact normalized hostnames, then Nginx-style location precedence: exact path, -longest priority prefix (`^~`), first matching regex in declaration order, then longest ordinary -prefix at a segment boundary (`/api` matches `/api/x`, not `/apix`). Case-insensitive regex is -also supported. Repeated literal slashes are merged for route/ACL selection, while an unrevised -backend request retains the original path/query. Locations can explicitly strip/replace/regex- -rewrite the upstream path or return a local response. Ambiguous encoded separators and traversal -forms remain rejected. Vhost pools support load balancing as described in VHOSTS.md; locations -may pin traffic to one member. Wildcard hostname matching is supported only in the documented -leading-label form; hostname regex remains unsupported. - -## Metrics and logs - -Metric names use `rust_proxy_` and bounded aggregate labels. Transport counters cover -HTTP/2 requests/connections, TLS handshakes/errors and WebSocket tunnel totals, -active tunnels, errors, frame bytes and closed-tunnel duration sum/count. Ordinary -HTTP body bytes and WebSocket frame bytes are separate measurements. - -Access entries are emitted at exchange completion or tunnel closure, not only when -headers arrive. WebSocket exchanges retain status 101 and a `websocket_closed` -outcome when cleanly released. Errors/cancellation affect the outcome. No query -values, request bodies, cookies, password material or WebSocket payloads are logged -by the access logger. Warnings and errors use the configured error file, or stderr when that file is disabled/unavailable. The in-memory access ring retains up to 4096 -completed entries; live SSE/UI snapshots use the newest 256 by default. The dashboard can -temporarily expand an open Live Logs window to the retained 4096-entry buffer and keep -merging new live snapshots until that window closes. This is not a durable lossless log archive. - - -## 0.1.0 cache, listener and diagnostics additions - -| Method / path | Contract | -|---|---| -| GET /assets/langs.json | Public compiled language bundle: default ID, discovered language metadata/translations | -| GET /api/cache | Authenticated cache settings, metrics and bounded object metadata; never returns cached bodies | -| POST /api/cache/purge | Full/global or vhost-scoped purge with the normal mutation protections and audit | -| POST /api/cache/invalidate | Selective authenticated PURGE/BAN by exact URL, prefix, tag, safe regex or cache key; optional host/vhost scope | -| GET /api/cache/bans | List active in-memory BAN rules | -| DELETE /api/cache/bans/{id} | Remove one BAN rule with audit | -| POST /api/cache/cold/cleanup | Start an authenticated/CSRF-protected asynchronous selective Cold Cache cleanup job | -| GET /api/cache/cold/cleanup/{id} | Read status/result of an authenticated selective cleanup job | - -Configuration includes defaulted `listeners`, `cache`, `transform`, `compression`, `server_header` -objects and optional per-route `cache` (`false` bypasses, null/omitted inherits). -The complete documented field contracts are in SETTINGS.md and CACHE.md. Listener -changes persist immediately but require restart; all other settings here are hot -published. Settings forms use revisioned section writes where one settings area can -be changed independently; full-document PUT remains for changes that must publish -multiple sections atomically. - -Status and SSE snapshots include `cache`, `system`, and `listeners` with active, -desired, startup-default addresses and `restart_required`. Metrics include four -compression counters; log entries include `cache` (HIT/MISS/BYPASS, or `-` before -cache classification). OS measurements are optional/null and sampled off the -request path. Unsupported is not represented as zero. Prometheus adds fixed-name -cache/resource series; absent OS measurements are omitted. No arbitrary URL/cache-key -labels are introduced and the metrics endpoint remains authenticated. - -The language bundle is generated by build.rs from langs/*.json and embedded into the -binary; uploading a new file requires recompilation/restart. It exposes public -translations only, not filesystem paths, administrator data or credentials. - - -### Web Services - -`web_services` contains reusable static-file service definitions. Use `PUT /api/config/web-services` to replace that section with revision CAS. A vhost or location selects `{ "type": "web_server", "web_service_id": "..." }`; an omitted `source` remains `{ "type": "reverse_proxy" }` for compatibility. Filesystem settings remain on the Web Service and are not duplicated inside vhosts. See [WEB_SERVER.md](WEB_SERVER.md). - -## 0.2.0 vhosts and backend state - -`vhosts` defaults to an empty array. All models, enum values, defaults and bounds -are documented in VHOSTS.md, with complete examples under `examples/vhosts-*.json`. -Use the same revisioned PUT endpoint; there is no unauthenticated health-control -endpoint or separate unsynchronized frontend/backend write API. - -`GET /api/backends` returns `{pools, active_frontends, desired_frontends}`. Pool state -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, 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. - -Status contains `backend_pools` and `vhosts`; listener state additionally includes -`active_endpoints`, `desired_endpoints`. Each endpoint reports its address, TLS and -H2/H3 transport capabilities. `restart_required` covers socket and listener-capability -changes. Listener capabilities are configured only under `listeners.endpoints`. A vhost -then narrows them with `http2_enabled` and `http3_enabled`; both default true. For TLS, -SNI selects either H2+HTTP/1.1 or HTTP/1.1-only ALPN for the matched vhost, so disabling -H2 on one hostname does not disable it for another hostname sharing the listener. - -Metrics add `rust_proxy_backend_*` per bounded vhost/member labels. Access entries -add nullable `backend` (`vhost/member` for a forwarded pool exchange). Cache hits -have no actual upstream member request and do not increase its request counters. -The `/assets/vhosts.js` frontend uses the same API/CSRF/revision -boundaries; the static script itself, like other frontend assets, is publicly readable. - -## 0.3.0 certificate upload and scoped operations - -`POST /api/certificates/upload` accepts JSON -`{"cert_pem":"PEM chain","key_pem":"PEM private key","hosts":["app.example"]}`. -It requires the same session, Origin and CSRF guards as configuration updates. -The request limit is 300 KiB; each PEM value is at most 128 KiB. At most two -blocking upload workers run, and the directory holds at most 128 upload-prefixed -pairs. This endpoint is staging only, not a configuration mutation. - -Success: `{"certificate":{"id":"upload-...","covered_hosts":["app.example"], -"hosts":[],"cert_file":"upload-....pem","key_file":"upload-....key"},"staged":true}`. -`covered_hosts` contains the normalized DNS names extracted from the leaf certificate -SubjectAltName extension; `hosts` remains the configured active SNI bindings. No key or -certificate PEM is returned. Add the metadata to `config.tls.certificates` and its ID -to a vhost's `certificate_id`, then save `tls`/`vhosts` as needed. The panel keeps a -combined full-document Save only when staged certificate metadata and vhost assignment -must be published atomically. Unknown certificate references, mismatched keys, -uncovered DNS names and conflicting same-name selections fail validation. -There is no ACME enrollment, expiry/chain-trust guarantee or remote PEM retrieval. - -`GET /api/cache` adds `vhosts: [{id, usage: {entries, resident_bytes}, settings}]`. -`POST /api/cache/purge?vhost=app` purges only that configured vhost and invalidates -its pending fills. Missing/unknown IDs return 404, not an accidental global purge. -Omitting the query purges all entries. Both actions are audited and require CSRF; -an empty body is accepted. Configuration updates purge only when cache compatibility -changes; unrelated section saves retain both hot and cold objects. - -`POST /api/cache/cold/cleanup` accepts a JSON selection with optional -`unused_for_seconds`, optional `oldest` (maximum count) and `hits_zero`. At least one -criterion is required. It returns HTTP 202 with a `ColdCleanupJob`; poll -`GET /api/cache/cold/cleanup/{id}` until `status` is `done`. The result contains -`removed_entries` and `freed_bytes`. This is intentionally separate from full/scope purge -but reuses the same internal Cold Cache candidate/removal machinery as automatic GC. - -### 0.22.1 selective cache invalidation - -`POST /api/cache/invalidate` accepts an `InvalidationRequest`: - -```json -{ - "operation": "purge", - "selector": "exact", - "value": "/assets/app.js?v=42", - "host": "app.example", - "vhost": "application", - "ttl_seconds": null, - "confirm_wide": false -} -``` - -`operation` is `purge` or `ban`. `selector` is `exact`, `prefix`, `tag`, `regex` or -`cache_key`. `host` and `vhost` are optional independent scope restrictions. BAN accepts -`ttl_seconds` (1..604800, default 3600) and the runtime keeps at most 1024 active rules. -Every regex operation and prefix `/` requires `confirm_wide=true`; the browser panel asks -for a danger confirmation before sending it. Regex input is length-bounded and compiled -with Rust's non-backtracking `regex` engine plus a compiled-size limit. - -The response includes `result` with operation/selector/value/host/vhost, -`affected_entries`, optional `ban_id`, `elapsed_ms` and `status`, plus current cache metrics -and active BAN rules. Selective PURGE removes RAM and Cold Cache matches, removes the full -`Vary` family under a matched primary key and prevents fills that started before the PURGE -from publishing afterward. BAN creation does not synchronously scan the store: matching -objects created before the rule are rejected on lookup and removed incrementally by cache -maintenance; fresh objects created after the rule may be stored. BAN rules are runtime -state and are not persisted across process restart. - -`GET /api/cache/bans` returns the active rule views. `DELETE /api/cache/bans/{id}` removes -one rule. All invalidation mutations use the standard administrator session, same-origin -request marker and CSRF protections; requested operator/scope information is written to the -existing audit log. - -The `transform` configuration section is replaceable through the normal revision-CAS -`PUT /api/config/transform` endpoint. Vhost objects carry a `transform` override and -Docker assignments carry `transform_enabled`; both default to disabled per vhost. -Global transform settings also expose resource controls: `memory_budget_bytes` (1 MiB..2 GiB, -default 128 MiB), `max_body_bytes` (1 KiB..32 MiB, default 2 MiB), and `max_inflight` -(1..1024, default 32). `preset` is optional UI intent metadata with values -`conservative`, `recommended`, `performance`, or `uber`; numeric limits remain authoritative. -Resource pressure is fail-open: work that cannot reserve the configured memory/concurrency -capacity is left unchanged rather than queued behind full-body transformation work. - -`GET /api/transform` returns in-memory transformation observability for the administration -Transform workspace: totals and per-vhost counters for examined/applied/skipped/error -responses, transformed cache HITs, original/transformed/saved bytes, accumulated transform -time, applied counts by content kind and bounded skip/fail-open reasons. These counters are -runtime diagnostics and reset when the RustProxy process restarts. - -`GET /api/logs` accepts optional `vhost`, `q`, exact numeric `status`, `cache` and -`limit` parameters. Limit clamps to 1..4096 and defaults to 256; search length is at most -256 bytes. Filters run over the retained ring before the limit is applied. `cache` accepts -HIT, MISS, BYPASS or `-`. Entries now include nullable `vhost` independently of `backend`: -cached and static responses can be attributed without claiming that an origin was contacted. -SSE still sends the newest 256 entries per global snapshot. The Live Logs UI can seed a -temporary 4096-entry buffer on demand and then merge subsequent live snapshots into it until -the window closes. This is not a durable log stream or a per-tenant authorization boundary. - -The status endpoint keeps existing field names, adds feature flags for new -capabilities, and assembles small values rather than one deeply expanded macro. -The management script `/assets/management.js` is a public static asset; APIs remain -protected. Pages and per-vhost cache fields use the standard configuration endpoint. - -## 0.4.0 certificate inventory, forwarding, auth and listeners - -`GET /api/certificates` requires the existing admin session and returns revision, -metadata-only certificates including `covered_hosts`, active `hosts`, `assigned_vhosts`, -file names and the per-entry default flag, plus `acme:false`. Inventory reads only the -public certificate PEM on a blocking worker and derives current DNS SubjectAltName values, -so older/manual entries and in-place renewals do not depend on stale persisted coverage -metadata. Persisted `covered_hosts` is only a fallback when the public PEM cannot be read. -It never reads a private key for this enrichment and never returns PEM contents or private-key bytes. Existing upload remains a -CSRF-protected staging operation. The inventory UI saves its returned metadata in -`config.tls.certificates`; vhost assignment is a later independent configuration -save. Every configuration mutation retains revision CAS and validation. - -Vhost additions: `tinyauth`, `server_header`, `diagnostic_headers`, -`backend_header`, `backend_timing_header`, and -`frontend.listener_ids`. Global additions: `forwarding`, -`diagnostic_headers`, and `listeners.endpoints`. Each effective -TLS binding can carry `http3` with the five fields described in HTTP3.md. These -are full-config fields, not independent unaudited mutation endpoints. Unknown or -invalid references, trust ranges, URLs, limits and protected header names fail -validation before publication. - -Status contains named_endpoints and active/desired endpoint transport metadata, -H3 connection counters and feature flags. Feature flags describe included source -capabilities, not evidence of successful build/conformance testing. Existing -Prometheus metrics add rust_proxy_http3_connections_total, -rust_proxy_http3_active_connections and rust_proxy_http3_rejected_connections_total -and rust_proxy_http3_requests_total. See source `metrics.rs` for exact HELP/type -contracts and INTEGRATIONS_040.md for configuration examples. - - -## 0.32.4 backend Host and HTTP/2 authority - -Existing backend `preserve_host` is now valid for all `protocol` values: -`http1`, `auto`, `http2` and `h2`. HTTP/2 :authority follows the request Host, -while socket/DNS use the configured origin and TLS SNI uses -`tls_server_name` if present. No new JSON fields, endpoints, database migration -or extra retries. HTTP health checks maintain their own Host override. - -## 0.32.3 Overview registry and compatible upstream modes - -The backend `protocol` wire values are `http1` (default), `auto`, `http2` -(legacy HTTPS H2/H1 ALPN fallback or cleartext H2C) and new `h2` (strict -HTTPS H2 or cleartext H2C). Old `http2` configurations are not rewritten. -See [Vhosts](VHOSTS.md). - -The authenticated `/api/preferences` Dashboard card ID lists are backed by a -shared server/frontend registry with **49** IDs: the original 16, fourteen -optional HTTP backend cards, twelve optional performance charts and seven existing -full-width panels (live traffic, system, connections, cache, process, network, -backend diagnostics). OpenAPI `dashboard_widgets` and -`dashboard_hidden_widgets` enum, `maxItems` and defaults reflect that registry. -Existing saved card order is retained. New performance charts are opt-in, -while previously displayed full-width sections remain visible by default. -Unknown saved IDs are ignored rather than failing a whole profile. -The visual editor offers a default-layout preview and a confirmed Reset layout, -both using the existing per-account preference endpoint. -No new routes, tokens or permissions are introduced. - -## 0.22.3 certificate hot reload and external deployment - -`POST /api/certificates/reload` is an administrator-session-only, Origin/CSRF-protected control-plane -operation. Certificate API bearer tokens are intentionally not accepted by this global endpoint. It re-reads every certificate/key file referenced by the current desired -configuration, validates a fresh immutable routing/TLS snapshot and publishes it at the -**same configuration revision**. Invalid or mismatched material returns an error and the -previous snapshot stays active. The response contains `reloaded`, `revision`, `public_tls`, -`http3_new_handshakes` and `administration_restart_required`. New public H1/H2 TLS and HTTP/3 -handshakes use the refreshed certificates; established connections are not renegotiated. -Administration TLS is startup-fixed and therefore reports when a process restart is needed. - -`POST /api/certificates/{certificate_id}/reload` is the targeted variant for a manual/file-backed certificate (`issuer == null`). It validates the requested configured ID and atomically republishes the immutable TLS snapshot at the same configuration revision. It is intended for external renewers that replace the files already referenced by RustProxy. A manual entry may use simple filenames from `/certs` or two absolute external paths such as `/etc/letsencrypt/live/example/fullchain.pem` and `/etc/letsencrypt/live/example/privkey.pem`. Absolute paths are resolved again on every reload, so Certbot's `live/` symlinks follow the newly renewed generation. Application-managed ACME certificate IDs are rejected with `409` because their renewal pipeline owns those files. The endpoint accepts either the normal administrator session + CSRF flow or a Certificate API token whose grant explicitly contains that certificate ID. - -`POST /api/certificates/deploy` accepts -`{"certificate_id":"shared-cert","cert_pem":"...","key_pem":"..."}` under the same -authenticated Origin/CSRF boundary. It is a convergent upsert for externally owned certificate -ids: a missing id creates a manual certificate entry, an existing manual id is rotated in place, -and an identical RustProxy-owned generation is a no-op. The pair is validated **inside the running -RustProxy process** and changed material is staged under immutable generation-specific files before -a revision-CAS configuration publish. This is important for root-run Certbot hooks when RustProxy -itself runs as an unprivileged service account: final generations are created with the service's -filesystem identity rather than `root:root`. Managed ACME ids are rejected, and failed -configuration publication removes any newly staged generation. - -For a certificate owned by a local external issuer such as system Certbot, prefer the local -CLI deploy hook, which calls that loopback API after bounded local file checks: +| `GET /healthz` | Minimal health response, without privileged state. | +| `GET /api/auth/status` | Whether first-time administrator setup is required. | +| `POST /api/auth/login` · `POST /api/auth/logout` | Start/end a browser admin session. | +| `GET /api/auth/session` | Validate current admin session. | +| `GET /api/config` · `PUT /api/config` | Read or replace validated, revisioned configuration. | +| `PUT /api/config/{section}` | Update one supported configuration section. | +| `GET /api/config/history` · `POST /api/config/history/restore` | Review/restore retained configuration snapshots. | +| `GET /api/status` · `GET /api/backends` | Operational status and backend observations. | +| `GET /api/cache` · `POST /api/cache/purge` · `POST /api/cache/invalidate` | Cache observations and authenticated invalidation. | +| `GET /api/logs` · `GET /api/live-logs` | Recent and streaming request logs. | +| `GET /api/file-logs` · `GET /api/file-logs/query` | Stored-log exploration. | +| `GET /api/traffic` · `GET /api/system-info` | Traffic and system observations. | +| `GET /api/docker` · `POST /api/docker/refresh` | Docker-agent observation and refresh. | +| `GET /api/certificates` · `POST /api/certificates/upload` | Certificate inventory/import. | +| `GET /api/acme` · `POST /api/acme/issue` · `POST /api/acme/renew` | Managed certificate jobs. | +| `GET/PUT /api/preferences` | Personal UI settings. | +| `GET /api/dashboard-presets` and named preset operations | Saved Overview presets. | +| `GET /docs/openapi.json` | Protected machine-readable API specification. | + +Some paths have additional DELETE/PATCH operations, job-status resources, query parameters or restricted actions. Use OpenAPI for the **actual fields/methods**, not guesses based on a table. `GET /metrics` here is not a replacement for the separate Prometheus exporter. + +## Authentication and safe writes + +- Ordinary API calls require an administrator browser/session cookie on the admin listener. Logging in also provides CSRF context. Writes require correct Host/Origin, `X-CSRF-Token` and `X-RustProxy-Request: 1` as enforced by the server. +- The Swagger **API** tab uses the signed-in session. Read calls can be executed; mutations require explicit confirmation. It cannot bypass server-side authorization. +- Configuration writes are revisioned (`expected_revision`) and rejected if based on a stale save. Prefer section updates over replacing the entire document when practical. +- `/docs`, API schemas, configuration, logs, certificates and most observations are **not anonymous**. Keep the listener restricted and use HTTPS or a tunnel for remote access. + +## Local CLI alternatives + +The included `rust-proxy` program supports configuration and backup operations without turning browser sessions into automation tokens: ```bash -/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \ - --cert /etc/letsencrypt/live/example/fullchain.pem \ - --key /etc/letsencrypt/live/example/privkey.pem +rust-proxy --data-dir /var/lib/rustproxy config get +rust-proxy --data-dir /var/lib/rustproxy config validate config.json +rust-proxy --data-dir /var/lib/rustproxy config apply config.json +rust-proxy --data-dir /var/lib/rustproxy config reload +rust-proxy --data-dir /var/lib/rustproxy backup create --output /private/site.rpb ``` -The command resolves Certbot's local `live/` symlinks, checks bounded regular files/private-key permissions, then sends the pair to `POST /api/certificates/deploy`. The running service validates the certificate/key and DNS SANs, stages generation-specific files inside RustProxy's private certificate directory, and creates or switches the **manual** certificate id through the normal revision-CAS configuration path. A single id may already be selected by multiple vhosts, so those vhosts switch together -without individual edits. RustProxy-managed ACME entries (`issuer != null`) are rejected to -avoid two renewal owners for the same lineage. The existing `/api/certificates/upload` remains -the generic PEM staging endpoint; deploy/upload request bodies never accept host filesystem -paths. Absolute paths may only be saved explicitly in administrator TLS configuration as a -manual certificate source, where they are validated and never returned as PEM contents. +Run CLI commands as an authorized local operator with the correct private data directory. The `admin` subcommands provide first setup, password recovery, session revocation and status; `tls` supports certificate generate/import/deploy/reload. Run `rust-proxy --help` for exact options. -## 0.28.2 least-privilege Certificate API credentials +## Restricted Certificate API credentials -External automation does not need a full administrator browser session. From **Settings -> Certificate API**, an administrator can create multiple dedicated credentials (up to 32), each consisting of a Key ID, Bearer token, optional non-secret comment and an explicit allowlist of 1-32 certificate IDs. New credentials are **reload-only by default**. Deploy is an independent opt-in permission. +**Settings → Certificate API** can create credentials containing a **Key ID + Bearer token** with explicit certificate-ID grants. New keys default to reload-only; deploy permission is separate. The clear token is displayed at creation/rotation and is not a general admin API token. -The clear token is returned only by the create/rotation response; SQLite stores only its SHA-256 hash plus metadata and the grant. Credentials created before 0.28.2 migrate to a reload-only compatibility grant for all certificate IDs (`*`). Rotate those credentials after upgrading to replace the wildcard with explicit IDs. - -API clients send both headers: +An authorized client sends: ```text -Authorization: Bearer +Authorization: Bearer X-RustProxy-Certificate-Key: ``` -A reload-only token may call only the targeted endpoint for an allowlisted certificate: - -```bash -curl --fail --show-error -X POST \ - -H 'Authorization: Bearer TOKEN' \ - -H 'X-RustProxy-Certificate-Key: certbot-main' \ - -H 'Content-Type: application/json' \ - --data '{}' \ - https://proxy-admin.example/api/certificates/shared-cert/reload -``` - -Certificate API tokens are **not** accepted by the global `POST /api/certificates/reload` endpoint. This prevents a compromised renewal token from forcing reads/reloads of unrelated certificate material. `POST /api/certificates/deploy` is available only when the credential was explicitly created with deploy permission, and the `certificate_id` in the request body must also be present in that credential's allowlist. - -A Certificate API credential cannot read configuration, logs, metrics, users or any other administration endpoint. Non-loopback use requires the configured HTTPS administration surface. The normal administrator session + Origin/request-marker + CSRF path remains valid for certificate operations. Credential management remains administrator-only: `GET /api/certificate-api` lists metadata/grants; `GET /api/certificate-api/{key_id}` returns a single credential's **non-secret** settings (Key ID, comment, created/last-used timestamps, allowed certificate IDs and deploy grant); `PATCH /api/certificate-api/{key_id}` edits the comment/grants without changing or returning the bearer token; and `POST /api/certificate-api/{key_id}/regenerate` creates a fresh token for the same Key ID, immediately invalidates the old token, retains metadata/grants and returns the new token exactly once. The original `PUT /api/certificate-api` still creates/rotates credentials, `DELETE /api/certificate-api/{key_id}` revokes one and `DELETE /api/certificate-api` revokes all. **Only administrator sessions** may use these management endpoints. Writes require the existing CSRF, origin and request-marker checks, including regeneration. Legacy wildcard grants cannot be saved in `PATCH`; replace them with explicit IDs first. - -Example session-authenticated metadata edit (existing token stays valid; the `token` field is deliberately forbidden): - -```json -{ - "comment": "renewal hook on edge-02", - "certificate_ids": ["shared-cert", "backup-cert"], - "allow_deploy": false -} -``` - -Submit this JSON to `PATCH /api/certificate-api/certbot-main` with the normal administrator cookie + CSRF/Origin headers. Regenerate through `POST /api/certificate-api/certbot-main/regenerate` with the same session controls and securely record the one-time `token` response. - -Example external deploy for a token that has **Also allow deploy** enabled and contains `shared-cert` in its allowlist: - -```bash -curl --fail --show-error \ - -H 'Authorization: Bearer TOKEN' \ - -H 'X-RustProxy-Certificate-Key: deploy-hook' \ - -H 'Content-Type: application/json' \ - --data-binary @deploy.json \ - https://proxy-admin.example/api/certificates/deploy -``` - -For an on-host Certbot hook, a token is optional: invoke the installed binary directly instead: - -```bash -/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \ - --cert /etc/letsencrypt/live/example/fullchain.pem \ - --key /etc/letsencrypt/live/example/privkey.pem -``` - -`dev.sh` is development tooling and is not required for production certificate rotation. - -## 0.5.0 cache inspector and certificate jobs - -`GET /api/cache` additionally returns `dashboard: {domains, objects, -matching_objects, offset, limit, statistics_revision}`. Domain rows flatten -hits/misses/bypasses/stored/saved_body_bytes/reasons beside vhost/domain and -resident counts. Object rows contain vhost/domain/path/body_bytes/resident_bytes/ -hits/age_seconds/remaining_ttl_seconds, never bodies, request headers or queries. -`vhost=ID` filters, `vhost=` selects legacy only; omitted selects all. Limit 1..200 -(default 50), offset <=16384. Unknown vhost ->404. The same scope semantics apply -to POST /api/cache/purge. Existing global metrics remain process-lifetime values. - -The historical 0.5 `GET /api/acme` response did not support renewal; see the -current 0.7 response below. -`POST /api/acme/issue` accepts at most 16 KiB of JSON with certificate_id, email, -domains, environment, agree_tos, confirm_production, dns_server, dns_port, key_name, -key_secret, key_algorithm, sign_query and propagation_seconds. See -ACME_RFC2136.md for exact constraints. It returns 202 `{job}` only after admission; -one process-wide job, 429 when busy. Job fields: id, certificate_id, status, -environment, message, certificate. The ready certificate is immutable PEM metadata, -not private bytes. Import is a separate normal config CAS write. Both endpoints -are behind the administrator session; POST retains Origin/CSRF and audit. - -LogEntry adds `cache_reason`, a fixed string token. Debug response headers add -X-Cache-Reason when X-Cache is enabled, and standardized RFC 9211 `Cache-Status` detail -when enabled. Transformed cached representations add the `transformed` Cache-Status -extension parameter. `X-Cache-Status` is stripped if supplied by an origin but is not -emitted by RustProxy. -`diagnostic_headers.hide_mock_origin` is removed and old documents containing it are rejected on -export; `request_id` is the only diagnostic-header switch. The generated request ID is a cryptographically unpredictable 64-hex-character value and the same protected value is forwarded to the origin as `X-Request-ID`. (The OPT5 DEV implementation replaces the extra SHA-256 digest with CSPRNG output mixed with the per-process sequence, retaining the length and unpredictability.) No revision response-header behavior remains. TLS listener -http3 omission is default-on; explicit null is persisted off. Legacy listeners use -https_http3 with identical default/null behavior. - -## 0.6 provider and scrape additions - -`POST /api/acme/issue` accepts `provider`: `http01`, `cloudflare`, `ovh` or -`rfc2136` (omitted retains RFC2136 compatibility). Common fields include -`certificate_id`, `domains`, `email`, `environment`, `agree_tos` and -`confirm_production`. DNS providers also accept `propagation_seconds`. -Cross-provider/unknown fields are rejected. Provider-specific fields, secret -handling and request examples are in ACME_PROVIDERS.md. `GET /api/acme` returns -only public job status and validated certificate metadata, not input credentials. -Issuer metadata on certificates is optional `{ "provider": "cloudflare", -"environment": "staging" }`. Do not confuse metadata with CA trust validation. - -`config.prometheus` controls the new dedicated HTTP scrape endpoint, which uses -actual-peer CIDRs and optional bearer authentication, not administrator cookies. -The exact configured GET path only is accepted (query strings rejected). Disabled -or changed/restart-pending settings return 404; denied peers 403; invalid tokens -401; non-GET 405; exhausted scrape capacity 429. No administrative actions are -available there. Admin `/api/metrics` and `/metrics` remain session-protected -previews on the admin listener, even when the dedicated endpoint is disabled. -See PROMETHEUS.md for the full settings and metric contract. Traffic analytics is an independent collector group (`include_traffic`) that exports bounded country, fixed User-Agent/method/source, status/cache and configured-route aggregates without raw IP/path/full-UA labels. Docker discovery is an independent collector group (`include_docker_vhosts`) and exports only dedicated `rust_proxy_docker_vhost_*` / `rust_proxy_docker_backend_*` families; ordinary vhost/backend families exclude dynamic Docker routes. - -## 0.7 renewal and host access - -`GET /api/acme` now returns `{job, providers, automatic_renewal:true, -interval_seconds:43200, renewals}`. `renewals` is keyed by certificate ID and -contains last_checked, next_check, last_renewed, not_after (Unix seconds), failures, -status and a public message token. No credential/account data is returned. -`GET /api/certificates` also includes renewal observations. Statuses are observations, -not a promise of a successful future CA operation. - -`POST /api/acme/renew` accepts only `{certificate_id, dry_run:false}`. It uses an -existing managed lineage and returns 202 `{job}` after admission, or 404 unknown ID, -409 unmanaged/invalid profile or disabled prerequisites, 429 worker busy. Actual -worker/CA failures arrive in job state. No force-renew flag is offered. Dry runs -never deploy their test certificate and never postpone a due real renewal. -The usual session, Origin/CSRF protections and audit apply. - -A certificate's `issuer` supports `auto_renew` (default true for saved managed -metadata, explicit false opts out). Toggle through ordinary revisioned config; -no provider credentials need resubmitting. Old immutable generations are retained. - -`POST /api/basic-auth/hash` accepts `{username, password}` with at most 4096 bytes -of request JSON. It requires an administrator session and the same mutation/CSRF -guards, returns `{username, password_hash}`, and never saves the plaintext password. -Passwords are 12..128 UTF-8 bytes with no control characters. Usernames are 1..128 -bytes, no colon/control characters. Hashing has two shared blocking-worker slots; -no available slot returns 429. Invalid input returns 400. This is NOT a public -login endpoint and does not create a browser session for a vhost. - -A vhost accepts `websockets` and `basic_auth` with enabled, realm, require_tls, -forward_credentials and users[{username,password_hash}]. Basic and Tinyauth cannot -be enabled simultaneously. Hash import accepts only the bounded Argon2id policy -created by the hash endpoint, not arbitrary-cost PHC strings or htpasswd formats. -Missing `websockets` retains the previous true behavior; the new-host UI selects -false. Basic TLS defaults true and forwarding defaults false. - -Backend pool members have `pool_eligible` (default true). False excludes that -member from default load balancing and sticky selection without blocking an -explicit location `backend_id`. Visual custom locations edit existing vhost -`routes`: ID, path_prefix, backend_id, enabled, cache, ACL and advanced headers. -This is prefix routing, not regex, rewrite or nginx configuration evaluation. -See [host access](VHOST_ACCESS.md) for cumulative ACL and authentication semantics. - - -## 0.18 Docker discovery additions - -| Method / path | Contract | -|---|---| -| `GET /api/docker` | Authenticated cached in-memory Docker-agent observations; no agent poll and no configuration write | -| `POST /api/docker/refresh` | Authenticated + Origin/CSRF immediate poll of enabled agents followed by ephemeral route reconciliation; no configuration revision write | -| `PUT /api/config/docker-agents` | Revision-CAS replacement of desired Docker-agent settings, per-agent domains and service hostname/alias/IP assignments | - -`config.docker_agents` stores desired agent endpoint/token/base-domain/IP override/listener references, a per-agent 5..3600 second inventory poll interval, and assignments. Inventory data returned by agents is not persisted in the configuration document. The normal configuration API remains authoritative for desired state; the Docker observation API is operational state only. Docker assignments may also carry an ACL profile, inline ACL, health-check settings and per-vhost proxy timeouts. Publishing/unpublishing is therefore an ordinary CAS configuration update and does not require polling the agent. - -## HTTP/2 lifecycle settings - -As of 0.24.3 the preferred configuration is per listener: `listeners.endpoints[].binding.http2_settings` contains `idle_timeout_seconds`, `max_connection_lifetime_seconds` and `drain_timeout_seconds` for that TLS H2 or cleartext H2C endpoint. Update these values by revision-CAS replacement of the `listeners` section (`PUT /api/config/listeners`), the same ownership model used by per-listener HTTP/3 limits. `max_connection_lifetime_seconds: 0` disables the absolute lifetime limit. Idle and lifetime expiry both use graceful GOAWAY/drain shutdown. - -The top-level `http2` object introduced in 0.24.0 and `PUT /api/config/http2` remain accepted for backward compatibility. They are a fallback only for listeners that do not yet have explicit `binding.http2_settings`; the structured 0.24.3 UI writes listener-scoped values. - -The runtime system diagnostics object includes `platform`, supplied by the shared system-statistics backend (`Linux`, `FreeBSD`, `OpenBSD`, `NetBSD`, or the generic/unsupported fallback). The browser only renders that value; it does not detect the operating system itself. - -## Per-vhost User-Agent rate limiting (0.31.7) - -The revisioned `/api/config` vhost rate-limit object accepts optional `user_agent_limits`, up to 64 entries with `pattern` (1..256 bytes, case-insensitive substring or `*` wildcard), `requests` (1..1000000), `window_seconds` (1..3600) and `burst` (0..1000000). The first matching rule replaces the standard and country rate for that request and uses a separate fixed-window counter for each (vhost, rule index, client IP). Requests with no matching User-Agent use the existing limiter. A User-Agent is trivially spoofable; these overrides are operational shaping, not access control. Global and vhost User-Agent blocklists continue to deny matching requests before rate limiting. - -## Stream pools and dashboard widgets (0.32.0) - -`GET /api/config` and `PUT /api/config/streams` preserve the existing revisioned JSON and session/CSRF gate. A Stream host may include `backends[]` (ID, address, weight, enabled, drain, max_sessions, health, metrics_enabled), `balance` and nullable `metrics_enabled`. With no `backends`, `upstream` retains its legacy single-backend behavior. Backend edits remain desired state until controlled restart. See [STREAMS.md](STREAMS.md). - -`GET /api/status` retains `streams.active`, `streams.desired`, `streams.restart_required` and `streams.metrics`. Each active Stream metric view includes `backends[]` for health/active information and optional detailed counters. `/api/status?view=live` and the existing authenticated SSE snapshots carry the same backend observations inside `streams.metrics` without a separate polling source. Backend status is `unverified`, `checking`, `healthy`, `unhealthy` or `disabled`. - -In the 0.32.0 Stream addition, `GET`/`PUT /api/preferences` own `dashboard_widgets` and `dashboard_hidden_widgets` (at that stage 16 supported IDs, adding `stream_overview`, `stream_traffic`, `stream_sessions`, `stream_backend_health`). Legacy accounts have their missing Stream widget IDs appended **hidden**. Preferences affect only Dashboard presentation, not proxy configuration or metrics export. No new endpoint or permission was introduced. - -Detailed backend metrics default to off in v0.32.0; per-Stream `metrics_enabled` sets the inherited value for its backends, and explicit per-backend `metrics_enabled` overrides it. Basic per-Stream counters remain available regardless of these flags. +The credential can be used only for allowed certificate reload/deploy operations, not configuration, users, metrics or secret management. Non-loopback use requires the appropriate administration HTTPS security boundary. Rotate broad or legacy credentials. See [API access](API_ACCESS.md) and [Certificates](ACME_PROVIDERS.md). diff --git a/docs/API_ACCESS.md b/docs/API_ACCESS.md index c67d88f..46a8ff8 100644 --- a/docs/API_ACCESS.md +++ b/docs/API_ACCESS.md @@ -1,243 +1,29 @@ -# Administration API access - 0.29.1 +# Administration authentication and API access -## Sign-in boundary +## Default exposure and setup -The administration listener remains separate from reverse-proxy listeners and uses -loopback HTTP by default. Settings -> Administration can enable HTTPS, select either -the application-managed default certificate or an inventory certificate, persist a -new bind address and allow explicit DNS hostnames. A persisted non-loopback bind is -accepted when HTTPS is enabled. The legacy startup flags remain only for deliberately -plain remote HTTP. Public vhost `/docs` paths are not captured by the admin listener. +The administration listener uses **HTTP loopback (`127.0.0.1:9090`) by default** and is separate from normal Vhost listeners. No administrator password is bundled. Use the local CLI `admin setup-token` or `admin init` to complete first-time setup. For access from another workstation, prefer an SSH tunnel; for direct remote operation, enable **administration HTTPS**, configure a trusted certificate and narrow network access before binding outside loopback. -When the dashboard is published through a trusted reverse proxy, the hostname of -the optional `administration.public_origin` is automatically allowed for panel -Host validation. Any extra `administration.allowed_hosts` DNS entry is also -matched against the external authority rather than the private admin socket port. `X-Forwarded-Host` is trusted only from loopback or a -configured trusted proxy; the external `X-Forwarded-Proto` additionally requires the -forwarded-proto trust setting except for loopback. Origin/CSRF checks and Secure cookies use -that external origin. Direct IP/localhost access still requires the real administration port. +## Session security -### Publishing the panel through RustProxy itself +Administrator sessions use an HttpOnly, SameSite=Strict cookie, with Secure on HTTPS. Expiry is bounded by the configured absolute lifetime (7–30 days, default 30). Logout, password changes and revoke-all terminate sessions. API mutations are validated for Host/Origin, CSRF token and request marker; a cookie copied into automation is not an approved general-purpose API token. -0.16.0 supports the explicitly opted-in same-process topology where a public HTTPS vhost proxies to the local administration listener. The reserved `rustproxy_session` cookie is passed through only when all of these conditions are true: the browser-facing request is TLS, its DNS hostname either matches the pinned `administration.public_origin` or is explicitly listed in `administration.allowed_hosts`, the selected backend directly addresses the configured administration socket by IP, and that backend has `accept_local_listener_risk=true`. The control-plane route is forced out of shared cache. +Unauthenticated endpoints intentionally expose only minimal setup/sign-in and health behavior. Configuration, operational status, dashboard assets, file logs, the API specification and the documentation Reader require authentication. Admin responses use no-store and security headers, and the browser does not expose the full application state before sign-in. -This is a narrow exception to the normal isolation rule. For every ordinary backend RustProxy still removes `rustproxy_session` before forwarding requests and strips an upstream attempt to set that cookie in responses. DNS backend names are not accepted for the special self-admin cookie path. This prevents a normal application backend from reading or replacing the administration session. +## `/docs` Reader and Swagger -The anonymous page contains only the sign-in/setup form, logo, shared stylesheet, -a dedicated login script, and filtered language strings. It does not fetch the -configuration, status, metrics, logs, certificate inventory, dashboard modules or -the full translation bundle. Adding a new language JSON still discovers that -language at build time; login strings use the same fallback rules. +The authenticated documentation page has two tabs: **Reader**, embedding the registered Markdown guides at compile time, and **API**, displaying the bundled OpenAPI schema. Both are local assets; no runtime CDN access is required for viewing the reference. A document edit affects the shipped reader **only after rebuilding** RustProxy. -Exact anonymous methods/paths: +Swagger uses the current signed-in session, not a new broad credential. It requires explicit confirmation for write operations. Treat the OpenAPI schema as a request guide and RustProxy's field/authorization validation as the final authority. -| Method | Paths | Content | -|---|---|---| -| GET/HEAD | `/`, `/overview`, `/vhosts`, `/redirects`, `/streams`, `/acl-profiles`, `/configuration`, `/tls`, `/cache`, `/logging`, `/settings` | Minimal login when no valid cookie is present; the same URL returns the authenticated application shell when the supplied session is valid. `/configuration` is a compatibility alias for Settings -> Manual config. | -| GET/HEAD | `/assets/login.js`, `/assets/login-langs.json`, `/assets/style.css`, `/favicon.svg` | Login assets; no runtime state | -| GET/HEAD | `/healthz` | `{"status":"ok"}` only | -| GET/HEAD | `/api/auth/status` | `{"setup_required":false}` or `true` only | -| POST | `/api/auth/login`, `/api/auth/setup` | Credential/capability validation, exact Origin and request marker required | +## Publishing the panel behind a reverse proxy -The document allowlist above is intentionally session-aware: a browser refresh on a protected -panel path first validates the cookie server-side instead of rendering a false logged-out state. -The login document also rechecks `/api/auth/session` when restored from browser history, preventing -a stale back/forward-cache copy from contradicting a still-valid session. The authenticated shell -also treats `auth.invalid_origin` during startup/session revalidation as a stale restored-page signal -and performs at most one reload of the current same-origin URL. A repeated failure is shown normally; -server Host/Origin, request-marker and CSRF validation is unchanged. Every other admin -path/method, including full assets, `/api/metrics`, `/metrics`, unknown endpoints and the OpenAPI -schema, requires an active database session. -Missing/invalid/expired cookies return `401 {"error":"auth.required"}` before -query/body extraction. Invalid Host/Origin may return 403 first. Only GET/HEAD -`/docs` and `/docs/` navigate to login through a fixed `303 /?next=docs` redirect; -API/spec/assets never return login HTML instead of a status error. +This is a **special-risk topology**, not the default. Configure a fixed HTTPS public origin/allowed hostname and a trusted forwarding boundary. RustProxy has an explicit narrowly constrained local-listener exception, but a normal public Vhost must not pass administration cookies to an ordinary upstream. Forwarded Host/Proto values are trusted only when the direct peer satisfies the configured trust rules. Test CSRF/Origin validation and cookie security after a topology change. +## Certificate-only credentials -Administrator browser sessions have a configurable absolute lifetime of **7–30 days** -(default 30) through `administration.session_days`. The server stores only a hash of the -session secret, keeps the cookie HttpOnly/SameSite=Strict (and Secure on HTTPS), and -never extends a session past its creation-time absolute expiry. The cookie is refreshed -with only the remaining lifetime so an upgraded still-valid short session can adopt the -new configured bound. Logout, password change and revoke-all still invalidate server-side -sessions immediately. +Certificate API Key ID + Bearer credentials are limited by certificate ID and by allowed action (reload by default; deploy opt-in). They do not grant login or administrative configuration access. Revocation/rotation are administrator-only actions. Keep tokens out of logs and pass them over HTTPS outside loopback. -Responses are no-store. Headers include CSP, nosniff, frame denial, no-referrer and -restricted camera/microphone/geolocation permissions. Error responses do not expose -request-body excerpts for malformed anonymous login/setup input. Anonymous responses contain no application version; fingerprint/revision -response headers are stripped on the admin surface. Authenticated status and the -protected schema intentionally retain the current version. The application name/logo and the single setup-needed bit are intentional -public information, not claims of anonymity or software fingerprint prevention. +## Offline build assets -Authenticated `GET /api/status` also returns a small `runtime` object with the platform, -statistics adapter name, resolved data directory and SQLite database path. This is used by -the Settings UI so Linux, BSD/OPNsense and custom installations show their actual runtime -paths instead of browser-side guesses. The object is not available anonymously and does -not expose credentials, environment values, certificate contents or arbitrary filesystem -listings. - -## Docs Reader and API view - -`/docs` opens an authenticated documentation workspace. The default **Reader** tab loads -`/docs/content.json`, a build-time bundle of the project Markdown documentation listed in -`docs/README.md`. The bundle contains documentation source text only; it does not contain -live configuration, credentials or runtime state. Reader links between bundled Markdown -documents stay inside the same authenticated documentation surface. - -The separate **API** tab uses Swagger UI. `/docs/openapi.json` describes the local endpoints and common request schemas; it -contains no live configuration, actual credentials, generated keys, user accounts, -cache entries or listener addresses. Extensible schemas summarize configuration; -the Rust validator remains authoritative for the full configuration model. - -Documentation uses the same HttpOnly `rustproxy_session` cookie as the panel. -Do not paste session cookies into the schema or store them in browser storage. -GET execution is enabled by default; write execution requires a checkbox and an -explicit confirmation for each request. The script validates the current session, -allows only documented same-origin operations, and sends the current session's -`X-CSRF-Token` plus `X-RustProxy-Request: 1`. The browser supplies Origin. The server -performs the real authorization checks regardless of any browser options. - -Login/setup operations and indefinite SSE streams are documented but not executed -by Swagger UI. Native logout/password writes can invalidate the browser session; reload or -sign in again afterward. Ordinary API mutations still require the administrator session, -Origin/request marker and CSRF token. - -0.28.2 keeps the deliberately narrow Certificate API exception for automation and makes it least-privilege. An operator can create one or more **Certificate API Key ID + Bearer token** credentials in Settings -> Certificate API. Each new credential requires an explicit allowlist of 1-32 certificate IDs and is reload-only by default. Deploy access must be enabled separately. - -A reload-only credential authorizes only: - -- `POST /api/certificates/{certificate_id}/reload` when that ID is allowlisted. - -Certificate API credential inspection (`GET /api/certificate-api` or `GET /api/certificate-api/{key_id}`) is likewise administrator-session only and returns no hashes/secrets. `PATCH /api/certificate-api/{key_id}` changes metadata/grants without touching the secret; `POST /api/certificate-api/{key_id}/regenerate` replaces the hash and invalidates the old token. Both writes require session + CSRF/Origin/request-marker protection. The replacement secret is revealed only once in the regeneration response. External Key ID + Bearer clients are not authorized to call any credential-management endpoint. - -`POST /api/certificates/reload` is administrator-session only and never accepts a Certificate API token. `POST /api/certificates/deploy` accepts a Certificate API token only when the credential has explicit deploy permission and the request's `certificate_id` is also allowlisted. - -Both `Authorization: Bearer ` and `X-RustProxy-Certificate-Key: ` are required. The clear token is returned only on creation/rotation; the database stores its SHA-256 hash. Up to 32 Key IDs can coexist, and each can be revoked independently or all at once. Credentials created before 0.28.2 migrate to reload-only compatibility access for all certificate IDs (`*`); rotate them to narrow the allowlist. The credential does not authorize configuration, logs, metrics, users or any other endpoint. Non-loopback use is accepted only on the configured HTTPS administration surface. Revocation from Settings invalidates that credential immediately. - -Local host automation does not need this token. The installed binary can run -`rust-proxy tls deploy ...` or `rust-proxy tls reload` directly; it obtains short-lived local -CLI authority from the local database and calls the guarded loopback administration API. -`dev.sh` is a development helper and is not required in production. - -The external validator, query-supplied schema/server configuration and persisted -Swagger authorization are disabled. Runtime JS/CSS are served from the same admin -origin behind its guard. Only the two necessary Swagger vendor asset filenames, the documentation bundle and a plain-text license notice -(`/docs/licenses.txt`) are routed; all require an administrator session. -Swagger's inline layout styles have a document-scoped CSP exception; scripts stay -self-only with no unsafe-eval allowance. The ordinary dashboard CSP is unchanged. - -Logout broadcasts an in-memory notification between tabs. The panel drops its -operational document on logout/revocation; documentation clears its schema, -responses and write opt-in. Docs recheck sessions on actions, visibility and every -30 seconds while visible. Browser history restoration revalidates; this is not a -promise to erase browser/OS screenshots or data intentionally downloaded by an admin. - -## First build / offline assets (fixed in 0.7.2) - -No `SWAGGER_UI_DOWNLOAD_URL` environment variable or `.cargo/config.toml` is required. -The default is Swagger UI **5.33.1**, selected by `scripts/build/swagger_assets.py` and -called from this application's `build.rs`. The former `utoipa-swagger-ui` adapter -is removed: there is no separate dependency build silently choosing its older -historical browser default. Direct Cargo builds using `--manifest-path` use the -project's absolute manifest directory, not the caller's working directory. - -Python **3.10+ is now also required for direct Cargo builds**, as it already was -for `dev.sh`. `build.rs` tries `python3`, then `python` if that executable is absent. -Use `RUST_PROXY_PYTHON=/absolute/path/to/python3` to select an interpreter explicitly. -The helper uses Python's verified HTTPS transport and standard-library ZIP reader; -`curl` is no longer a Swagger build requirement. Missing tools/network/assets give -an actionable build error rather than an absent-environment-variable panic. - -Normal build (no Swagger environment setup): - -```bash -cd RustProxy -bash dev.sh check -bash dev.sh test -bash dev.sh release -``` - -The first build still needs the normal Cargo registry and the pinned upstream -archive. Swagger JS/CSS and license notices are embedded in **both debug and -release binaries**, served behind the existing admin guard. No runtime download, -CDN, public asset endpoint or alternate authentication mechanism was introduced. - -An offline or GitHub-restricted build can use a reviewed local copy of the same -GitHub **tag archive**, with its original `swagger-ui-5.33.1/` directory structure: - -```bash -export SWAGGER_UI_DOWNLOAD_URL=file:///srv/build-assets/swagger-ui-5.33.1.zip -# Optional: require the archive digest independently verified by your build team. -# export SWAGGER_UI_ARCHIVE_SHA256=<64-hex-digit-verified-digest> -bash dev.sh check -bash dev.sh test -bash dev.sh release -``` - -Unset/empty source values use the default pin. Different remote URLs and old tag -archives are rejected. Local file URLs support escaped spaces and must be absolute. -`SWAGGER_UI_ARCHIVE_SHA256`, when set, checks the entire archive before publication. -No upstream checksum is fabricated: HTTPS provenance, the archive's declared tag -and its layout are not themselves a cryptographic authenticity guarantee for an -operator-supplied ZIP. Independently review/pin local inputs. - -The helper only reads exact, bounded members; it does not extract arbitrary ZIP -paths. It retains licenses/notices, validates package name/version, rejects duplicate -required members, symlinks and oversized assets, caches only validated downloads in -Cargo's output directory, and writes outputs atomically. Failed preparation blocks -compilation instead of embedding stale assets. A `source.json` receipt records -archive and output SHA-256 values in `OUT_DIR/swagger-ui/`. - -Cargo tracks changes to the helper, source/digest/interpreter environment variables, -local or cached ZIP and generated outputs. Repeated identical preparation preserves -output timestamps. `CARGO_NET_OFFLINE=true` forbids a new helper download; ordinary -Cargo dependencies must also be cached for a fully offline build. A local archive -is the recommended explicit setup for `cargo --offline`; Cargo CLI offline flags -are not assumed to propagate automatically into subprocess environments. - -0.24.1 adds cross-platform statistics dependencies. This packaging workspace has no Cargo toolchain, so it does not fabricate or hand-edit a resolver result. Generate and preserve `Cargo.lock` with Cargo on the compiler-enabled release host, then run `cargo check --locked` / the normal `dev.sh` gates before deployment. - -Build-helper tests use synthetic ZIPs; they do not claim to render Swagger. This -environment lacks Rust/Cargo and cannot fetch the upstream assets. Compilation, -real embedding/rendering and live access-control acceptance remain to be run on a -build server. Browser tests still label their simulated API/Swagger renderer. - -## Focused live regression probe - -After running the built server: - -```bash -python3 tools/audit/admin_audit.py --url http://127.0.0.1:9090 -``` - -The script sends anonymous read-only loopback probes, follows no redirects, ignores -proxy environment variables, prints no response bodies, and fails on unexpected -public content/statuses. It does not send credentials or mutate configuration. It -is also called by the real-binary security integration suite. Rust router tests -add write/method/duplicate-cookie/CSRF checks. A passing probe is a regression gate, -not a complete pentest, dependency scan or guarantee against all vulnerabilities. - -## Custom location headers - -Vhosts -> edit -> Custom locations -> Location headers. Add rules independently -for proxy requests (to backend) and responses (to client). `set` replaces, `append` -adds another field value, `remove` has no value. Maximum 32 rules per direction. -The existing JSON editor is synchronized and remains optional. Invalid JSON, -newlines, unknown operations and protected names cannot silently save an old draft. - -Rules apply global -> vhost -> matching location. Proxy transport, forwarding, -verified authentication identity, configured Server/security/diagnostic policies -remain system-managed and can override or forbid a conflicting custom rule. -Values are literal, not Nginx variables; changing Host/forwarded identity still uses -the designated backend/forwarding controls. Headers for sibling locations remain -unchanged. No configuration schema migration is needed for existing header rules. - -## References - -- OWASP REST Security Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html -- OWASP Session Management Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html -- Swagger cookie authentication: https://swagger.io/docs/specification/v3_0/authentication/cookie-authentication/ -- Swagger UI configuration: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/ -- Cargo configuration discovery: https://doc.rust-lang.org/cargo/reference/config.html -- Cargo build scripts: https://doc.rust-lang.org/cargo/reference/build-scripts.html -- Pinned browser release: https://github.com/swagger-api/swagger-ui/tree/v5.33.1 +Swagger JavaScript/stylesheets are prepared by the build and embedded behind authentication. If compilation fails during Swagger asset preparation, inspect `build.rs` and `scripts/build/swagger_assets.py` and ensure the required checked/downloaded asset inputs are available. Do not replace these authenticated assets with an unaudited public CDN workaround. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index 6cd7585..0000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,233 +0,0 @@ -# Architecture - -## Control plane and forwarding plane - -SQLite stores administrator intent, including vhosts/paths, policies, backend definitions -and certificate metadata. `config::compile` validates this document and builds an -immutable Snapshot with routing tables, compiled ACL/header policies, per-backend -clients and an optional rustls ServerConfig. ArcSwap publishes the active snapshot. -No public request consults SQLite or a provider to choose its route. - -A configuration mutation obtains a bounded admission slot and builds a candidate on -a blocking worker. The slot remains held if the HTTP caller is cancelled while the -worker is running. A serialized detached writer checks the expected revision, -commits the desired document in SQLite and swaps the active snapshot with no await -between commit completion and publication. Invalid candidates leave the old state -active. A crash between persistence and publication is recovered by loading the -committed document at startup; this is not one cross-memory/SQLite transaction. - -## Module responsibilities - -| Module | Responsibility | -|---|---| -| `server` | Bounded listeners, TLS acceptance, HTTP/1.1/HTTP/2 selection, connection leases and shutdown | -| `proxy` | Host/authority routing, ACL sequencing, forwarding and streaming instrumentation | -| `proxy::websocket` | Handshake validation, upgrade tasks and bounded bidirectional byte transport | -| `web_server` | Reusable static-file services, safe document-root traversal, validators/ranges and streamed local file bodies | -| `upstream` | Per-backend pools, guarded resolver, connection deadlines and verified HTTPS clients | -| `tls` | Local PEM files, generation/import, validation, SNI and loaded certificate snapshots | -| `config` | Desired models, normalization, validation and compiled snapshots | -| `vhost` | Host-owned frontend/member models, actual-address selection and listener planning | -| `balance` | Full-exchange leases, health/recovery, weighted scheduling, connection/affinity settings | -| `security` | Compiled per-vhost response protection, TLS-only HSTS | -| `acl`, `headers` | Generic access and header policies, no application-specific exceptions | -| `auth`, `admin` | Administrator lifecycle, local API, browser sessions, CSRF, SSE and static UI | -| `db` | SQLite schema, migrations, desired-state persistence and revision checks | -| `metrics`, `logging` | Aggregate instrumentation, bounded access ring and trace output | -| `transform` | Bounded decode/transform stage for eligible static identity representations before cache/compression, with live process-wide memory and concurrency ceilings | -| `docker` | Desired Docker-agent settings, in-memory observations and ephemeral vhost reconciliation | -| `runtime` | Shared process state, snapshot updates, admission limits and tunnel tracking | -| `demo` | A separate opt-in demonstration backend with its own listener | - -### Rust source organization - -Production Rust modules are kept as small responsibility-oriented files, with a -**500-line engineering limit** for production modules. Unit and -integration test files are explicitly exempt: related tests may live together in -large `tests.rs` files rather than arbitrary numbered fragments. Large logical modules use a thin facade (`config.rs`, `docker.rs`, -`server.rs`, `runtime.rs`, `balance.rs`, `prometheus.rs`, and similar) plus files in a -matching directory. `web_server` uses the standard `web_server/mod.rs` facade with -submodules for configuration, filesystem confinement, responses, routing, FastCGI and -`.htaccess`. - -Unit-test implementations are not embedded in production source files. They live in -`tests.rs` or `tests/` files and are included or declared only behind `#[cfg(test)]`. -Keep `include!` targets, split fragments and attributes/doc comments alongside -the items they describe. After removal of the old source-check scripts, review -these invariants in code review and verify compilability with `./dev.sh check`. - -Docker discovery is an optional provider boundary implemented through the standalone -`docker-agent/`; the RustProxy process never opens the Docker socket. Cloudflare Tunnel -remains a future module. Certbot-based HTTP-01/Cloudflare DNS/OVH DNS/RFC2136 issuance and managed renewal are optional -control-plane adapters. Provider work stays outside proxy request paths and -publishes certificates only through validated configuration snapshots. - -## Request lifetime - -A listener owns a shared TCP connection lease. Each admitted request owns an -in-flight permit and an Exchange, which remains alive through streamed request and -response bodies. HTTP/2 connections share the public TCP cap with HTTP/1.1 and TLS. -Upgraded WebSocket tasks retain both the Exchange and TCP lease after Hyper stops -managing the HTTP connection, preventing upgrades from escaping limits or counters. - -Routing captures one snapshot. Socket peer identity, host and route ACLs plus configured -authentication are checked before source dispatch, DNS or upstream connection reuse. A selected -Web Server source opens only its compiled Web Service root and returns through the shared response -pipeline; a Reverse Proxy source continues to a stored backend origin. The public client never -supplies an arbitrary upstream destination or filesystem root. Header -rules cannot change the identity already used by ACL. Ordinary bodies pass through -without full aggregation. For an explicitly transform-enabled vhost, eligible bounded -static responses use the logical pipeline `origin -> decode supported Content-Encoding --> transform -> cache identity representation -> downstream compression`. Origin -`no-transform` is authoritative; changed bytes receive a RustProxy representation ETag -while origin validators are retained separately for revalidation. WebSocket data uses -fixed-size copy buffers. - -New requests on existing keep-alive/HTTP/2 connections load the current snapshot. -Existing bodies and tunnels retain the old snapshot/backend. Each accepted TLS -connection captures the current certificate configuration before its handshake. -Certificate validation and file reads are never performed inside the SNI resolver. - -## Startup and shutdown - -Startup opens/migrates SQLite, loads existing intent, prepares required certificates, -compiles the snapshot and binds the requested listeners. The demo only seeds a new -database. A corrupt configuration is not automatically reset. After the runtime is published, -the optional Docker poll loop contacts enabled agents; failures remain observations and do not reset desired configuration or block ordinary proxy service. - -Shutdown cancels listeners and tracked tunnels, drains admitted HTTP connections -within the configured grace period, drains accepted configuration writers and -closes SQLite. Bounded runtime shutdown avoids waiting forever for cancelled OS DNS -workers. This does not turn a blocking getaddrinfo call into a killable operation. - -## Deliberate development tradeoffs - -The panel remains loopback HTTP. All active configuration changes currently rebuild -backend pools and certificate state, even if unrelated fields changed. Compatible health/counter/affinity observations are retained independently; idle -connection and certificate reuse optimization still requires measurements. Backend loop protection checks local listener addresses by default while allowing remote origins on any valid port. A per-backend `accept_local_listener_risk` opt-in can deliberately allow a local RustProxy listener (for example port 9090) without bypassing private/special-address policy. DNS special-range restrictions -are basic safeguards rather than an independently audited network policy engine. -No performance target has been established or measured for this package. - - -## 0.1.0 response pipeline and resources - -Observe pins one immutable snapshot for the exchange. Host/route ACLs precede the -cache and all upstream I/O. Eligible GET lookup uses effective request headers -and a revisioned representation key. On a miss the origin response is validated -before and after global/route header rules; a bounded tee records complete bodies -while still yielding frames immediately. No request-path SQLite/provider lookup is -introduced. Server policy and optional streaming compression apply after cache -lookup/fill wrapping. The outer MeteredBody counts delivered encoded payload frames. - -Cache publication uses a short mutex section and LRU/byte/entry bounds; fill buffers -have eight semaphore slots. Purge increments a generation, preventing older in-flight -fills from publishing. Config commit invalidates cache before snapshot publication, -without awaiting between those operations. New listener intent is persisted but -active sockets remain unchanged until restart; original CLI defaults are retained -separately so clearing an override has accurate desired-state reporting. - -`cache`, `transform`, `compression`, `settings`, `diagnostics`, and `system_stats` are dedicated modules. `diagnostics` owns periodic sampling/export while `system_stats` adapts the shared `systemstat` host API, `sysinfo::Networks` network API, and one `metrics-process` process API. Network inventory/counters use `sysinfo` as the primary source and merge `systemstat` as a secondary library source, so missing/partial primary inventory is supplemented without shell commands; no BSD/Linux shell-command network parser is maintained. `metrics-process` uses its documented dummy fallback where no native process collector exists, so unsupported fields remain absent without adding another platform parser. Linux adds its compatibility-only `/proc`/cgroup fields after the common sample. The -sampler runs blocking OS/library reads off the async request path roughly every two -seconds and stops through the process cancellation token. Language discovery runs -in build.rs, not in browser hardcoded PL/EN logic. The result is an embedded public -JSON asset; existing logo resources remain intact. - -## Vhost pool publication and observations - -The immutable routing snapshot contains actual-address-constrained routes, compiled -frontends, pools and both TLS ALPN configurations. The process separately retains -its actually bound listener plan. New routing uses desired bindings immediately; -new sockets/ALPN take effect only at restart. This can temporarily deny moved hosts, -but never silently broadens them to still-active old bindings. - -Each pool owns immutable member definitions and clients. Small synchronized health, -scheduling and traffic observations live separately. Compatible publication reuses -observations/tokens by vhost/member/origin/health identity; changed or re-enabled -members are quarantined. Request leases retain the old member/client until both -body directions or the upgraded tunnel finish. Changes do not replay requests. - -A weak-App, cancellation-aware supervisor schedules a rotating, bounded window of -probes outside the data path. DNS and configured-origin policies are shared with -normal forwarding; HTTP checks use a dedicated connection and discard the body. -Traffic sampling and status snapshots operate on these observations, not SQLite. - -Request order is actual frontend selection -> host/vhost/path ACL/auth -> location/source selection. Web Server then performs confined local filesystem handling; Reverse Proxy continues through eligible member selection -> member-isolated cache lookup -> guarded upstream request. -Cookie affinity bypasses cache. Cache hits release the selection without recording -an actual upstream request. Response protection is applied after cache selection, -and per-vhost HSTS is removed on plaintext, including synthetic responses. - -## Integrations in 0.4.0 - -`forwarding` compiles the explicit trust boundary and reconstructs canonical -metadata. `tinyauth` owns immutable verifier configuration and an outbound client; -it authorizes before the existing origin/cache path. `http3` adapts bounded Quinn -streams to the same Axum router and response Body without a separate cache/auth -implementation. Named listener IDs resolve to actual bindings while compiling -vhosts; accepted destination addresses still control route visibility. - -TLS and QUIC use the same validated certificate resolver. Active endpoint limits -remain fixed until restart; new QUIC handshakes can use the latest certificate -snapshot without changing Retry token keys. Graceful shutdown signals H3 GOAWAY -and bounds drain before endpoint close. Runtime conformance remains to be tested. - - -## 0.5.0 cache inspection and certificate jobs - -Cache decision counters and entry metadata live in the bounded cache store. A -separate authenticated paginated read API returns metadata, not bodies, queries or -request headers. Expiry cleanup also runs during inspection; generation barriers -still prevent old in-flight fills from republishing after purge/config changes. - -The ACME manager is outside proxy request paths: one job semaphore, a tracked -child-process group, JSON over stdin, sanitized status and private Certbot/RFC2136 -profiles. Only validated immutable certificate metadata can enter the existing -library through explicit revision CAS publication. See ACME_RFC2136.md for the -required external tools and unverified integration boundary; RENEWAL.md documents -the 0.7 scheduler and automated publication. - -## 0.6 boundaries - -`legacy` is an input-only migration module, not a traffic dispatcher. It emits the -same canonical vhost model used by the UI. Validated startup migration uses one -CAS write; failure before validation completion leaves the stored JSON untouched. - -`acme` manages one private worker job; `scripts/runtime/acme_provider.py` adapts Certbot -webroot/Cloudflare/OVH/RFC2136. Only the running HTTP-01 job's names and token files -are exposed by the reserved challenge middleware. Provider files remain private, -not part of SQLite or generic configuration exports. Issuance/import are separate. - -`prometheus` owns a dedicated read-only HTTP router, actual-peer CIDRs, environment -bearer token and bounded scrape work. It does not share administrator cookies. -Instrumentation is process-local; per-vhost counters reuse stable-ID observations -across configuration changes. Domain cache gauges intentionally reset with revision. -Admin request timing measures response headers, while proxy/vhost timing measures -complete body/tunnel lifetime. Neither renderer queries a provider or SQLite to -handle public proxy requests. - -## 0.7 renewal, access and deployment boundaries - -A shutdown-tracked weak-reference scheduler selects due configured managed -certificates. It shares the issuance semaphore and per-profile advisory lock. -Provider work remains outside request paths. Publication validates PEM/key/names -and the complete current configuration, then uses the same SQLite CAS/snapshot -path. Only changed generations publish; failures do not touch the active snapshot. -TCP and QUIC handshakes consult current TLS state. Certificate publication also -uses the existing cache-generation invalidation. See RENEWAL.md for timing/limits. - -Basic Auth is a separate vhost gate after cumulative IP ACLs and before static -responses, cache and upstream selection. Argon2id verification is bounded by a -shared semaphore held by blocking workers and an IP failure table. Hashes, not -passwords, are persisted. A verified Authorization header is optional and reapplied -after custom request headers; unverified identity headers are removed. Protected -responses bypass the shared cache and authentication failures cannot become custom -200 responses. WebSocket admission uses the same gates before opening a tunnel. - -`pool_eligible=false` distinguishes a location-pinned backend from ordinary pool -membership. The GUI edits the same prefix route model as the configuration API; -there is no second nginx-style parser or unvalidated location execution path. - -Server deployment uses root-owned immutable release directories, an atomic current -symlink, a non-root systemd service and private service-owned data. A stopped-state -backup precedes update publication. Failed health/version checks attempt to restore -both old code and data; backups and failed-generation state remain for recovery. -Live systemd/database recovery acceptance is not replaced by helper mocks. diff --git a/docs/BACKUP.md b/docs/BACKUP.md new file mode 100644 index 0000000..3d55023 --- /dev/null +++ b/docs/BACKUP.md @@ -0,0 +1,39 @@ +# Encrypted backup and recovery + +RustProxy can create a password-protected **full application backup** while the proxy is running. Restore is a separate, CLI-only operation that requires the service to be stopped. A configuration-history entry is **not** a substitute for a full backup. + +## Create a backup + +```bash +rust-proxy --data-dir /var/lib/rustproxy backup create \ + --output /private/rustproxy-backup.rpb +``` + +The tool asks for a password in interactive operation or may provide a generated password according to CLI mode; preserve it independently of the archive. Store the encrypted `.rpb` file **off-host** with access controls. Never place backup archives in an exposed Web Service document root. + +Backups include database state, RustProxy's private Secret Store material, managed certificates and other durable files under the data directory. They **exclude** rebuildable COLD/HOT cache data, most logs/temporary files and the managed Python environment. Certificates/keys referenced through **external absolute paths**, such as `/etc/letsencrypt/live`, must be backed up and restored separately with their permissions. + +## Restore + +**Stop RustProxy first** and perform restore using an interactive terminal: + +```bash +sudo systemctl stop rustproxy +rust-proxy --data-dir /var/lib/rustproxy backup restore \ + /private/rustproxy-backup.rpb +sudo systemctl start rustproxy +``` + +Use an appropriate service user/elevation and keep ownership/permissions intact. RustProxy validates the password, encrypted package, manifest/checksums, SQLite data, Secret Store consistency and compatibility before activating staged restored data. A failed validation should not replace the running state, but an external off-host copy is still essential. + +After restore, reattach any external certificate mounts/files and service secrets, check network listeners, then verify the panel login, Vhosts, certificates, Docker agents, cache policy and API credentials. Test a backup restore periodically on an **isolated** environment; do not first test recovery on the live server. + +## Secret Store + +The application stores sensitive management records using a private local encryption key. Copying only `config.sqlite3` is not enough for disaster recovery: without the associated key, encrypted records may be unreadable. Do not commit the key, database, backup password or unredacted provider credentials to source control. A lost backup password cannot be recovered by RustProxy. + +External systems (system Certbot, PHP-FPM, reverse proxies, Docker host agents, DNS credentials) may need their own backups because their complete state is not owned by RustProxy. + +## Before an update + +Create and verify an off-host backup, record release version and current listener state, plan a maintenance window, update, then run application-level checks. Managed installation rollback is useful but should not be relied on as the sole recovery plan. See [Installation](INSTALL.md) and [Migration](MIGRATION.md). diff --git a/docs/CACHE.md b/docs/CACHE.md index b4c5513..c52612c 100644 --- a/docs/CACHE.md +++ b/docs/CACHE.md @@ -1,381 +1,65 @@ -# Shared hot/cold cache +# HTTP cache — HOT, COLD and invalidation -See [CACHE_DIAGNOSTICS.md](CACHE_DIAGNOSTICS.md) for practical HIT/MISS/BYPASS diagnosis. +RustProxy provides an optional shared HTTP response cache with a bounded in-memory **HOT** tier and optional persistent-disk **COLD** tier. Both the global cache and a Vhost's effective policy must permit caching. **COLD storage is disabled by default**, and newly created Vhosts do not automatically cache dynamic content. -## Scope +## Enable caching safely -RustProxy implements a bounded reverse-proxy cache with a hot RAM tier and an optional -persistent cold disk tier. Version 0.11.5 aligned request-method handling and most logical -hash behavior with Varnish built-in semantics. Since 0.19.1 the logical primary identity -includes the frontend scheme as well as Host + exact request URL, so `http://host/path` -and `https://host/path` cannot reuse the same stored response. RustProxy still isolates -different vhost/config scopes internally and uses response `Vary` to select representation -variants. +1. Open **Settings → Cache**, enable the global cache and choose memory limits. Enable COLD only if you want durable disk storage and have enough private disk capacity. +2. On the appropriate Vhost, enable cache for public, repeatable responses. Restrict or disable it for login, personalized pages, uploads, APIs and responses carrying secrets. +3. Ensure the origin supplies a valid public freshness policy, or configure a safe default TTL where supported. `Cache-Control: private` and `no-store` must prevent shared storage. +4. Optionally enable HIT-only diagnostics. Send repeated identical requests using `curl`, then inspect **Cache → Domains / Stored objects** for `MISS → HIT`. +5. Check TTL, revalidation and invalidation after updating the origin's content or configuration. -This is not a VCL implementation. Generic Varnish grace, ESI, public unauthenticated -unrestricted public BAN/PURGE and distributed cache state are not implemented. RustProxy 0.22.1 adds bounded -foreground request collapsing, authenticated selective PURGE/BAN and optional proactive -refresh while keeping directive-controlled stale delivery through RFC 5861 and request -`max-stale`; it is not an unconditional availability/grace layer. RustProxy also keeps its -validator-based handling of response `no-cache` instead of reproducing Varnish hit-for-miss -behavior exactly. +## Stored object rules -## Web Server origins (0.29.4) +- Reuse is scoped by frontend scheme, Host, exact client URL, Vhost/content policy and eligible `Vary` dimensions. HTTP and HTTPS objects do not cross-share. +- GET and suitable HEAD requests may reuse a full object; changing request methods do not silently invalidate all cached GET results. +- Authorization, dynamic cookies, `Set-Cookie`, sticky-affinity cookies, Tinyauth and other private signals normally cause **BYPASS**. A limited static-file cookie exception exists for configured static extensions and does not override `private`, `no-store`, `Set-Cookie` or `Vary: Cookie`. +- Origin status, response size, TTL, unsupported metadata, range/conditional semantics and request/client cache directives can prevent fill or require validation. +- A Web Service selected as a Vhost source uses the same cache policy for eligible local files. File changes can be checked with `ETag` / `Last-Modified`; a 304 validation may refresh a stored body without downloading it again. -A Web Service selected as the vhost source now uses the normal RustProxy shared -object-cache pipeline when both global and vhost cache policy permit it. -`Cache-Control: public, max-age=...` (or another eligible freshness policy) -can produce `MISS` followed by `HIT` instead of a browser-only cache header. -Vhost response rules run before cache fill, and transformed output is what is -stored. Private/`Set-Cookie`/`no-store` origin responses remain ineligible, -even if a later response-header rule replaces their visible cache headers. +## Freshness and diagnostics -For Web Server sources (0.32.2), requests with `Cache-Control: no-cache`, -`max-age=0`, or an expired/inadequately fresh representation check the local -origin using the stored origin `ETag` / `Last-Modified`. When the Web Server -returns `304 Not Modified`, RustProxy refreshes the same shared-cache object, -keeps its body and increments `X-Cache-Hits`. The original browser conditional -headers determine whether the client receives 304 or the cached 200 body. -Changed static files cause a new response/cache fill; `no-store`/`private` -origin policy continues to prevent shared storage. HEAD and single-range -requests can reuse a successfully revalidated full object. A Web Server -source does not run the remote-backend background revalidation worker or its -stale-if-error fallback. A PHP handler that ignores validators may still -return 200, which must be handled as a real origin response rather than an -assumed unchanged object. +| State | Interpretation | +|---|---| +| `MISS` | No suitable fresh cached representation was found; the origin was consulted. The response may still be ineligible for storage. | +| `HIT` | A matching stored object was reused. | +| `BYPASS` | Cache was disabled or a policy intentionally skipped it (for example authorization, cookies or a path exclusion). | +| Revalidated | An existing representation was checked against the origin; a `304` can retain the body. | -## HTTP BAN / PURGE from authorized client IPs +`Age` is total HTTP age, not only the time spent in RustProxy. The optional `X-Cache-Resident-Age` reports the cache residency interval. Debug headers such as `X-Cache`, `X-Cache-Hits` and reason labels are optional and should be enabled deliberately. Browser hard reloads commonly force revalidation, so test with `curl` when investigating. -The **Settings → Cache → HTTP BAN / PURGE IP allowlist** control sets -`cache.http_invalidation_allowed_cidrs`. The default is **empty (disabled)**. -Allow individual client IPs or small CIDR ranges, one per line. Saves validate -IPv4/IPv6 CIDR syntax and reject `/0` networks. An allowed request must also -pass the vhost listener binding, global/vhost ACL, and rate limits. The -operation is restricted to the matched external HTTP/HTTPS scheme, `Host` and -vhost; it cannot choose other tenants or the opposite scheme through headers. Commands use the existing -cache invalidation engine, are audit-logged before execution and share a -two-worker maintenance limit with authenticated admin invalidation (429 when -busy). They are never forwarded upstream. +Example, replacing the listener and Host values with yours: -- `PURGE /path?query` immediately invalidates exact cached representations of - that URL. Returns `200` with JSON result/count. Other query variants are not - matched. -- `BAN /prefix` records a lazy prefix ban of older cached representations - for that host/vhost. Returns `202` and ban ID, normally expires after one hour. - Optional `X-RustProxy-Ban-Ttl: 30` sets 1..604800 seconds. Newly filled - representations are allowed (Varnish-like lazy BAN). -- `BAN /` requires `X-RustProxy-Confirm-Wide: 1` to prevent accidental - whole-host invalidation. Request bodies, upgrades, arbitrary selectors and - wildcard cross-vhost operations are rejected. - -Examples from an allowlisted machine (replace the domain and path): - -```sh -curl -i -X PURGE 'https://site.example/assets/app.js?v=3' -curl -i -X BAN 'https://site.example/assets/' -curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1' +```bash +for n in 1 2 3; do + curl -sS -D - -o /dev/null -H 'Host: app.example.com' \ + http://127.0.0.1:8080/public.css + sleep 2 +done ``` -**Behind a reverse proxy:** forward nonstandard `BAN`/`PURGE` methods to the -RustProxy public listener; do not let an upstream cache intercept them. Configure -RustProxy's existing `forwarding.trusted_proxies` accurately and ensure the -edge sanitizes/replaces untrusted `Forwarded`/`X-Forwarded-For` headers. When a -trusted proxy does not provide a usable real-client chain, BAN/PURGE fails -closed. Do **not** allowlist an entire proxy NAT network unless all its users -should have destructive cache access. Unlike full Varnish VCL, this interface -intentionally provides only exact PURGE and bounded URL-prefix BAN, not an -arbitrary untrusted regex/VCL expression. +Look for cache reason labels including `request_cookie`, `request_authorization`, `response_cache_control`, `route_disabled` and `cache_hit` when diagnostic headers are enabled. A `MISS` alone does not prove the object was admitted to HOT/COLD. -## Settings +## Advanced behavior -| Field | Default | Bounds / meaning | -|---|---|---| -| enabled | true | Global master switch; vhosts may disable cache but cannot re-enable a globally disabled cache | -| max_bytes | 268435456 | Hard HOT allocation budget (body backing allocation + charged entry metadata); minimum 64 KiB, no artificial high ceiling | -| max_object_bytes | 16777216 | Maximum logical body accepted by one fill; minimum 1 KiB and at most `max_bytes` | -| max_entries | 4096 | 1..16384 hot objects | -| transient_max_bytes | 67108864 | Hard RAM budget for fill buffers and complete bodies moving between HOT/COLD | -| cold_queue_max_bytes | 33554432 | Byte ceiling for complete demoted objects waiting for the COLD writer; at most `transient_max_bytes` | -| hot_eviction_max_objects | 64 | 1..4096 LRU removals attempted for one HOT admission before the new object passes uncached | -| hot_auto_trim_enabled | true | Periodically trim sufficiently idle HOT objects only while HOT usage is above the target waterline | -| hot_auto_trim_after_seconds | 300 | 5..86400; minimum idle age before an object can be removed by automatic HOT trim | -| hot_auto_trim_target_percent | 80 | 10..95; automatic trim stops once HOT usage reaches this percentage of `max_bytes` | -| cold_enabled | false | Enable persistent disk tier; never implied by hot-cache enable | -| cold_path | cache/cold | Relative path under the private RustProxy data directory | -| cold_max_bytes | 4294967296 | 64 KiB..1 TiB disk budget | -| cold_max_entries | 50000 | 1..1000000 cold objects | -| cold_gc_interval_seconds | 60 | 5..86400; interval between asynchronous pressure-GC checks | -| cold_gc_max_delete | 256 | 1..10000; maximum objects removed by one automatic GC pass | -| cold_after_seconds | 300 | 5..604800; idle hot objects are eligible after this delay | -| cold_on_idle | true | Move idle eligible hot objects to cold storage | -| cold_max_hot_seconds | 3600 | 5..2592000; total RAM-residency age used when `cold_on_age=true` | -| cold_on_age | false | Demote objects after the maximum hot-tier age even if they remain frequently accessed | -| cold_on_lru | true | Move objects displaced by hot-tier LRU pressure to cold storage | -| cold_on_shutdown | true | Best-effort direct hot-to-cold flush during ordinary graceful shutdown/restart | -| default_ttl_seconds | 0 | Only explicit public responses without origin freshness | -| max_ttl_seconds | 86400 | 1..604800; maximum configured retention/freshness cap | -| bypass_query | false | Bypass requests containing a query when enabled | -| bypass_prefixes | [] | Up to 64 normalized absolute path prefixes | -| http_invalidation_allowed_cidrs | [] | Opt-in IP/CIDR allowlist (max 32) for HTTP BAN/PURGE. Empty blocks all HTTP methods; `/0` is invalid. | -| static_ignore_cookies | true | For configured static extensions only, ignore request Cookie for eligibility/keying | -| expose_status | false | Emit `X-Cache: HIT`, `MISS` or `BYPASS` | -| expose_hits | false | Emit per-object `X-Cache-Hits` | -| expose_cache_status | false | Emit standardized `Cache-Status` (RFC 9211) | -| expose_resident_age | false | Emit `X-Cache-Resident-Age` on reused stored responses | -| via_header | `RustProxy` | Append this `Via` response value; `null` disables it | -| collapse_enabled | true | Collapse concurrent foreground fills for the same primary cache key | -| collapse_wait_timeout_ms | 1500 | 10..60000 ms follower wait before fallback | -| collapse_timeout_fallback | origin | `origin` or `stale_if_available` after follower timeout | -| stale_if_error_timeout | true | Allow RFC 5861 stale fallback for timeout failures | -| stale_if_error_connection | true | Allow stale fallback for connection failures | -| stale_if_error_dns | true | Allow stale fallback for DNS failures | -| stale_if_error_tls | true | Allow stale fallback for TLS failures | -| stale_if_error_statuses | [500,502,503,504] | Up to 32 unique eligible HTTP 5xx statuses | -| proactive_refresh_enabled | false | Schedule sampled cached objects for validator refresh before expiry | -| proactive_refresh_before_expiry_seconds | 15 | 1..604800 seconds before expiry | -| proactive_refresh_percent | 10 | 0..100 deterministic percentage of primary keys | -| proactive_refresh_max_inflight | 4 | 1..64 concurrent proactive refreshes | +- **Budget limits:** HOT RAM capacity, object count/size, transient fill memory and optional COLD byte/object limits. Configure based on real workload and monitor disk/RSS. +- **HOT/COLD movement:** entries may be demoted, recovered or expired; a periodic/explicit COLD cleanup is available. Cache storage is not a permanent archive. +- **Request collapsing:** eligible concurrent MISS requests can share a bounded fill, limiting origin bursts. +- **Stale-if-error and refresh:** conditional options can help on eligible backend failures, but are not an unconditional stale-serving guarantee. Web Server local-file validation has a distinct policy. +- **Compression/transformation:** cached variants and validators must remain compatible with client encoding and content-modification settings. -The hot tier enforces `max_bytes` and `max_entries` before publication. HOT accounting uses the retained body allocation (the fill buffer capacity transferred into `Bytes`) plus directly owned entry metadata and a conservative per-entry charge for the HOT hash/LRU/variant indexes, while `hot_logical_bytes` reports only visible body lengths. Rust container/allocator APIs do not expose every bucket/node allocation exactly, so `hot_allocated_bytes` is intentionally a conservative charged estimate rather than a promise that it equals process RSS. In-progress fills reserve a separate bounded transient budget before growing their buffers. When HOT pressure would exceed the budget plus a small allocator-rounding headroom (roughly 1/64 of HOT, capped at 1 MiB), RustProxy removes LRU objects first; if admission still cannot be made safe within `hot_eviction_max_objects`, the response continues normally without being cached. `max_object_bytes` is checked incrementally before every body-buffer growth, so an oversized response is not fully accumulated before rejection. +## Clear or invalidate content -Automatic HOT trim is a separate low-pressure maintenance path. Every maintenance pass first checks whether HOT allocation is above `hot_auto_trim_target_percent` of `max_bytes`; if so, only least-recently-used objects that have been idle for at least `hot_auto_trim_after_seconds` are eligible. One pass is capped by `hot_eviction_max_objects`. Active/recent objects are never removed by this mechanism. Eligible objects follow the existing COLD-on-LRU policy when enabled; otherwise they are evicted. This is intended to release stale working-set memory after traffic bursts without periodically purging the cache or reloading vhosts. +Authenticated Cache page/API operations can purge selected entries, domains or larger scopes. The optional public HTTP `PURGE`/`BAN` interface is **disabled by default**. Enable it only by listing trusted client IPs/CIDRs under **Settings → Cache → HTTP BAN / PURGE IP allowlist**; operations are additionally subject to Vhost binding and normal access controls. `PURGE` addresses an exact URL; `BAN` applies the supported bounded matching rules. Never allow untrusted Internet clients to purge all tenants' objects. -When cold storage is enabled, -`cold_on_lru`, `cold_on_idle` and `cold_on_age` independently choose which hot objects are demoted. -`cold_after_seconds` measures time since the last access. `cold_max_hot_seconds` measures total -hot-tier residency, so it can move a frequently accessed object to disk after a configured age. -The administration UI exposes these as separate policies to avoid conflating "old" with "unused". -Demotions use an asynchronous queue bounded by both object count and `cold_queue_max_bytes`. A demoted `Entry` is removed from HOT accounting and moved, not copied, into a `ColdDraft`; its `Bytes` payload remains charged to `transient_max_bytes` until persistence completes and the draft is dropped. The persisted `ColdEntry` contains compact lookup/invalidation/LRU metadata rather than the body allocation. Since 0.27.3 it also does not retain the full response `HeaderMap` or origin validator values per COLD object; those are validated and loaded from the existing metadata JSON only for an actual COLD read. The in-memory body digest is 32 raw bytes and the map-owned cache key is not duplicated inside the entry. This reduces long-lived COLD index RAM without changing Cold format 3 on disk. This prevents the COLD writer queue from becoming a second unbounded RAM cache. If an HTTP response is already using a clone of the same `Bytes`, that last transport-owned reference can keep the backing allocation alive until the response finishes; the cache-owned HOT/COLD reference itself is released after persistence. Process RSS is therefore monitored separately from cache accounting. Supported -glibc/Linux builds ask the allocator to release unused pages after bulk demotion. During -ordinary graceful shutdown, `cold_on_shutdown` performs a best-effort direct flush of remaining hot -objects before the store is closed. A **managed software update** (`update.sh` / `scripts/deploy/deploy.py`) -uses a private one-shot marker and, on RustProxy 0.13.2+, forces this hot-to-cold handoff whenever -the cold tier is enabled even if `cold_on_shutdown=false`. The updater gives the process a larger -stop budget and removes the marker before backing up state. If the cold tier is disabled there is -nowhere persistent to hand the hot objects, so RAM-only cache is still lost on process exit. The -body is stored as a raw `.body` file and metadata as JSON with a SHA-256 body digest. A fresh cold hit is integrity-checked and promoted to -RAM. Cold format 3, introduced in 0.21.0, stores objects below the first two hexadecimal -characters of the SHA-256 cache key, for example `cache/cold/7f/.body` and -`cache/cold/7f/.json`. Shard directories are created lazily. The previous flat -Cold Cache layout is deliberately not migrated; legacy flat objects are removed and can -be refilled from origin. Format 3 metadata also carries RFC 5861 windows, revalidation -flags, origin validators, last access and hit count. +In 0.34.0, changing operational Vhost settings is intended to preserve compatible HOT/COLD objects; content-affecting changes invalidate affected scopes and COLD carries an additional fingerprint against stale resurrection. **This new behavior still needs release-level verification**, so validate cache after configuration edits and during upgrades. Earlier persistent cache entries may need refilling following format/compatibility transitions. -Automatic Cold Cache GC is asynchronous. When either byte or entry usage reaches about -90% of its configured limit, it selects old/rarely used candidates and cleans toward -about 80%, removing at most `cold_gc_max_delete` objects in one pass. Metadata is -snapshotted in small batches and disk deletion is queued instead of holding request-path -state across a full-cache sort/delete loop. +## Troubleshooting -## Varnish-style identity and variants +- **Always BYPASS:** check both cache enable switches, login/cookies, exclusion rules, methods, Tinyauth and sticky sessions. +- **MISS never becomes HIT:** check response `Cache-Control`, status, object size, `Vary`, `Set-Cookie`, incomplete origin streams and whether the same scheme/Host/query is used. +- **Content unexpectedly old:** inspect Age, TTL, validators, stale rules and previous invalidation; purge only affected content after confirming its identity. +- **Memory/disk pressure:** review HOT/COLD budgets and cleanup, rather than increasing limits without measurements. -The logical primary cache identity is: - -- a stable cache-compatibility revision derived only from settings that can change - cached representation/origin semantics, plus vhost scope (internal isolation), -- normalized frontend scheme (`http`/`https`), -- normalized original Host authority, including an explicit port, -- exact request path and query. - -Selected backend-pool member, route/backend IDs and unrelated request headers are not -part of the representation hash. This means two healthy members of the same pool can -serve/fill the same public cache object, while HTTP and HTTPS remain isolated. Headers -such as User-Agent, X-Forwarded-For or browser client hints no longer create unnecessary -variants by themselves. - -The response `Vary` header provides the secondary representation identity. For example, -`Vary: Accept-Language` stores independent language variants under the same scheme + Host + URL -primary hash. `Vary: *`, malformed Vary fields or an excessive Vary field set are not -stored. If an origin changes the Vary schema for a URL, incompatible old variants are -removed so an obsolete no-Vary object cannot shadow a newer Vary representation (or -vice versa). - -Cookie and Authorization requests still pass by default, matching Varnish built-in -safety behavior. RustProxy's static-cookie optimization can ignore Cookie only for the -configured static file extensions; response `Set-Cookie`, `private`, `no-store` and -`Vary: Cookie` still prevent unsafe shared reuse. - -### Per-vhost forced-cache targets - -A vhost can opt into an explicitly unsafe `cache.unsafe_force` list for narrowly selected public request targets. It is intentionally not available as a global cache setting. Each configured target starts with `/`; a target without `?` matches by normalized path prefix, while a target containing `?` matches the original path+query prefix. - -For matching GET/HEAD requests, forced mode may ignore request-side bypasses caused by a query string, Cookie, Authorization, `Cache-Control: no-cache`/`no-store`/`max-age=0`, and `Pragma: no-cache`. Protocol-safety exclusions such as Upgrade, unsupported Range/preconditions and request bodies remain in force. Cookie and Authorization values are added to the cached representation's `Vary` identity in forced mode so different credentials/cookies cannot reuse each other's object. - -Forced mode does **not** override response-side privacy policy. `Set-Cookie`, `Cache-Control: private`, `no-store`, `Vary: *`, partial/trailer/SSE/gRPC responses and the normal size/lifetime validation still prevent storage. Use this only for URLs whose origin semantics are understood; ordinary cache defaults remain safer. - -## Request methods and invalidation - -GET and HEAD participate in lookup. They share one representation identity. On a -cacheable HEAD miss RustProxy fetches the backend representation as GET, may populate -the shared object, and strips the response body before delivering the original HEAD -request. - -POST, PUT, PATCH, DELETE, OPTIONS and TRACE pass to the backend and are not stored. -They do **not** implicitly purge the whole cache. This fixes the pre-0.11.5 behavior -where any successful unsafe request could erase unrelated static objects and destroy -the hit ratio. - -Cache invalidation remains explicit: - -- authenticated full purge can clear all objects or one vhost scope, -- selective PURGE supports exact URL, URL prefix, `Cache-Tag`, safe regex and primary cache key, with optional host and vhost restrictions, -- exact/prefix matching uses the stored request target including query; a match removes the whole `Vary` family under that primary cache key, -- selective PURGE operates on RAM and Cold Cache and advances a publication generation so no fill that started before the operation can publish afterward, even if the selector depended on response metadata such as `Cache-Tag`, -- BAN records a bounded TTL rule without synchronously scanning the entire store; objects created before a matching active BAN are rejected during lookup/insertion and are removed later by bounded GC, -- new fills created after a BAN rule are allowed, so BAN invalidates the old representation set rather than permanently blocking a URL, -- regex rules and prefix `/` are treated as broad operations and require explicit confirmation, -- only cache-compatibility changes advance the normal cache generation/revision; unrelated logging/TLS/admin/metrics/response-presentation saves retain objects, and cache request-bypass edits plus response-only `X-Cache`/`Cache-Status`/resident-age/`Via` diagnostics also apply without purging stored objects. - -Unrestricted anonymous cache-maintenance access is not exposed: the public -HTTP BAN/PURGE interface is explicitly IP/CIDR-authorized (empty by default), -separate from the administrator-only selective invalidation API. If an -application changes a GET resource after POST/PUT/PATCH/DELETE, use origin -freshness/validators or one of the explicit invalidation paths; RustProxy -does not guess the affected URLs. For exposure review and test scenarios, see -[security audit](SECURITY_AUDIT.md). - -## Eligibility and freshness - -Hard request bypasses include Cookie (outside the static exception), Authorization, -Upgrade, `If-Match`, `If-Unmodified-Since`, nonempty request bodies, configured path/query -exclusions and request `Cache-Control: no-store`. - -A single `Range: bytes=...` request is special rather than a normal bypass. When a complete -fresh cached representation is available, RustProxy evaluates `If-Range` and can return -`206 Partial Content` directly with `Content-Range`, `Accept-Ranges`, the correct sliced -`Content-Length`, validators and normal cache `Age`; an unsatisfied range returns `416` -with `Content-Range: bytes */`. A mismatching `If-Range` returns the complete cached -representation. If origin access is needed, RustProxy forwards the Range request as an -identity, non-transformed stream. Multiple/multipart, duplicate or otherwise unsupported -Range forms are passed through to origin and are never stored as separate range objects. - -A fresh cached representation can satisfy `If-None-Match`/`If-Modified-Since` locally. -Request `no-cache`, `max-age=0` and legacy `Pragma: no-cache` require validation when a -safe stored validator exists. `max-age=N` and `min-fresh=N` are freshness constraints: -they reuse the cached response when its current Age/remaining freshness satisfies the -client limit and otherwise drive normal revalidation/fetch behavior. Request `max-stale` -can explicitly allow a retained stale response: bare `max-stale` accepts any retained -staleness, while `max-stale=N` bounds it. Freshness expiry does not itself physically -delete an object; normal RAM LRU/Cold Cache GC may still evict it. `must-revalidate` and -`proxy-revalidate` override client stale permission. With a safe validator, an origin -304 refreshes metadata and reuses the stored body. - -Responses with `Set-Cookie`, `private`, `no-store`, `Content-Range`, trailers, -SSE/gRPC, `Vary: *`, invalid freshness metadata or unusable size/lifetime are not -stored. Response `Cache-Control: no-cache` may be retained only with ETag or -Last-Modified and must validate before every reuse. Both origin and transformed response -policy must remain eligible. - -`s-maxage` takes precedence over `max-age`, then Expires/Date. `Age` uses the RFC 9111 -current-age model and includes origin age; it is not a measure of how long the object has -been in RustProxy. Cache metadata keeps normal TTL, `stale-while-revalidate`, -`stale-if-error`, `must-revalidate` and `proxy-revalidate` separately. A representation -is therefore **fresh**, **stale-but-servable** only under an applicable directive, or -**expired/unusable**. - -Inside `stale-while-revalidate=N`, RustProxy may return stale immediately and starts one -deduplicated background refresh for that cache identity. Inside `stale-if-error=N`, stale -is used only after an eligible origin failure. Version 0.22 makes timeout, connection, DNS -and TLS classes independently configurable and uses an explicit configurable 5xx status -allowlist; defaults preserve the previous behavior (`500`, `502`, `503`, `504` plus all four -transport classes). After TTL plus the corresponding stale window, that mode is unavailable. -`must-revalidate` and `proxy-revalidate` still permit storage, but prohibit all stale delivery -after expiry, including RFC 5861 and request `max-stale`. - -Optional proactive refresh schedules a deterministic percentage of cache keys before normal -expiry. It reuses the same validator-based refresh logic and background-revalidation guard, -keeps only one schedule per key, and enforces a global concurrent-refresh bound. A newly -completed fill can schedule this work immediately, so sampled objects do not require another -client request to start the pre-expiry timer. The feature is disabled by default. - -## Resident age - -`X-Cache-Resident-Age` is the RustProxy-specific whole-second clock for how long the -stored representation has remained in this cache. In 0.11.5 that clock is independent -from the HTTP freshness `Age` clock and survives: - -- ordinary hot-cache HITs, -- successful backend 304 revalidation, -- hot-to-cold demotion, -- cold-to-hot promotion. - -A value of `0` is valid only during the first sub-second of residency. Revalidation or -tier movement must no longer reset a long-lived object to zero. - -## Streaming and capacity - -RustProxy tees response frames while delivering them; it does not wait for the complete -origin body before sending the first bytes. Only a complete successful body is -published. Oversize, body error, trailer and cancelled-fill paths discard the candidate. -Large buffers grow incrementally and consume multiple bounded fill permits. - -The configured resident byte limit accounts for body bytes, estimated header storage -and fixed per-entry overhead; it is not total process RSS. Active HIT references, -in-flight origin responses, compression buffers and runtime allocations also use memory. - -With `collapse_enabled=true`, the first foreground MISS for a primary cache key owns the -fill while concurrent followers wait for publication. The fill guard is held through the -actual response-body tee, so followers cannot wake merely because origin headers arrived. -Leader error/cancellation releases waiters. A follower that wakes to a different `Vary` -representation re-enters the single-flight decision instead of immediately stampeding the -origin. After `collapse_wait_timeout_ms`, the configured fallback is either an independent -origin request or an eligible stale response. SWR/background revalidation keeps its separate -existing deduplication guard. - -## Diagnostics and API - -`GET /api/cache` reports settings, HIT/MISS/BYPASS, hot/cold counts and bytes, -configured COLD byte/object limits, promotions/demotions, errors, fill pressure, object -views and per-domain reason counters. The `/cache` dashboard shows HOT and COLD -occupancy against the configured object-count and byte limits with progress bars and -explicit percentages. HOT byte occupancy uses the charged/allocated HOT value rather -than logical body bytes; COLD byte occupancy uses persisted storage bytes. Hit ratio is -`hits / (hits + misses)` and intentionally excludes BYPASS traffic. - -`POST /api/cache/purge` requires the existing administrator session, same-origin marker -and CSRF token. `?vhost=` purges only that vhost scope; omitting it purges all cache -objects. Purge is audited. - -`POST /api/cache/invalidate` performs authenticated selective `purge` or `ban` using -`exact`, `prefix`, `tag`, `regex` or `cache_key`. Optional `host` and `vhost` restrict scope; -BAN accepts a bounded TTL. Regex and prefix `/` require `confirm_wide=true`. The result -reports the selector/scope, affected entry count, elapsed time, status and BAN id when one -was created. `GET /api/cache/bans` lists active rules and `DELETE /api/cache/bans/{id}` -removes one. These operations use the normal administration audit path. The Cache panel -exposes the same controls plus per-object **Purge URL**. - -`POST /api/cache/cold/cleanup` starts a selective asynchronous Cold Cache cleanup using -the same candidate/removal machinery as automatic GC. Criteria can select entries unused -for a duration, cap removal to the oldest N objects, and/or require `hits == 0`. -`GET /api/cache/cold/cleanup/{id}` reports job status, removed object count and freed -bytes. Purge-all and purge-vhost remain the full-clear mechanisms; selective invalidation -is deliberately separate. - -`X-Cache`, `X-Cache-Hits`, `Cache-Status`, `X-Cache-Reason` and -`X-Cache-Resident-Age` are optional diagnostics. `Via` is a separate configurable response header, enabled by default with value `RustProxy`; RustProxy appends it instead of deleting an upstream `Via` chain. `Cache-Status` is the standardized -RFC 9211 field name; RustProxy does not rename it to the legacy/custom `X-Cache-Status`. -HTTP/2 and HTTP/3 serialize field names in lowercase, so `cache-status:` on the wire is -normal. When the cached representation was produced by the transform pipeline, -RustProxy appends the extension parameter `transformed`, for example: - -```http -Cache-Status: RustProxy; hit; ttl=556488; detail="cache_hit"; transformed -``` - -Origin lookalike values (`Cache-Status`, `X-Cache-Status` and the other RustProxy -cache diagnostics) are removed before RustProxy emits its own diagnostics. HTTP `Age` -is generated from cache freshness metadata on reused responses. For 0.26.6 configuration -compatibility an explicit response-header rule may set/append/remove `Age`, but that rule -runs only after cache lookup/revalidation/storage decisions and therefore cannot alter the -internal freshness clock. The Cache dashboard also marks transformed objects explicitly. - -## Pool behavior - -Backend selection and health policy still run before an origin fetch. The selected pool -member no longer fragments the cache identity in 0.11.5, so a fresh public object can be -reused regardless of which healthy member would have been selected for a MISS. A pool -with no eligible member still returns 503 before RustProxy can use the cache; this cache -is not a stale-availability layer. - -Cookie affinity and protected/authenticated traffic keep their existing privacy bypass -rules. Per-vhost response/security policy also applies to HIT responses. +See [Monitoring](MONITORING.md) for cache telemetry and [Security](SECURITY.md) for privacy boundaries. diff --git a/docs/CACHE_DIAGNOSTICS.md b/docs/CACHE_DIAGNOSTICS.md deleted file mode 100644 index b251329..0000000 --- a/docs/CACHE_DIAGNOSTICS.md +++ /dev/null @@ -1,223 +0,0 @@ -# Cache diagnostics and behavior - -This describes implemented source, not a verified Rust runtime. See -[TEST_REPORT.md](TEST_REPORT.md). - -## 0.29.7: show diagnostic headers only on cache HIT - -Set `cache.expose_only_hits: true` globally (Settings > Cache) to emit -`X-Cache`, `X-Cache-Hits`, `X-Cache-Reason`, `Cache-Status` and -`X-Cache-Resident-Age` only when the response is actually served from -RustProxy cache. MISS and BYPASS continue to be recorded in internal metrics -and access logs but these diagnostic headers are absent from those responses. -Cached stale/revalidated/range/conditional responses use the HIT outcome and -continue to show cache diagnostics. Individual vhosts can override the global -flag with `vhosts[].cache.expose_only_hits: true | false | null` (`null` -inherits). Changing the flag alone does not purge cached objects. - -The independent `Via` proxy-chain header can still appear on MISS/BYPASS when -`cache.via_header` is configured. Set it to `null` to turn off that header. -Existing `expose_status`, `expose_hits`, `expose_cache_status` and -`expose_resident_age` switches still select which individual HIT headers are -emitted. - -## 0.19.1 cache identity safety - -The frontend scheme is part of the primary cache identity. `http://host/path` and -`https://host/path` therefore cannot share an object, preventing responses that depend -on `X-Forwarded-Proto` or equivalent origin logic from crossing schemes. - -## 0.11.5 cache changes - -The cache follows the Varnish built-in model for the behaviors that most directly -affect hit ratio, with the 0.19.1 scheme-isolation difference noted above: - -- GET and HEAD use the cache; other ordinary methods pass without clearing unrelated objects. -- The logical primary identity is frontend scheme + normalized Host authority + exact URL inside the current vhost/config scope. -- Selected backend-pool member and unrelated request headers do not fragment the primary object. -- Response `Vary` chooses representation variants under that object. -- GET and HEAD share the same stored representation; a cacheable HEAD miss can fill it from a backend GET. -- `X-Cache-Resident-Age` survives 304 revalidation and hot/cold tier movement. - -RustProxy still deliberately bypasses Cookie and Authorization requests by default, -matching Varnish built-in safety behavior. The configured static-extension optimization -can ignore Cookie only for selected static paths, subject to response privacy guards. - -## Why objects used to disappear after POST/PUT/etc. - -Before 0.11.5 a successful non-safe request could call a global cache purge. A periodic -POST, API heartbeat or form submission could therefore erase every cached static object -even though the request was unrelated to those URLs. In 0.11.5 POST/PUT/PATCH/DELETE, -OPTIONS and TRACE pass through and do not implicitly purge the shared cache. - -Only configuration changes that alter cache/origin compatibility invalidate incompatible cache -state; unrelated control-plane saves keep reusable hot/cold objects. Explicit administrator purge -still invalidates the selected cache state. Use the purge action when the application really needs -explicit invalidation. - -## `Age` versus `X-Cache-Resident-Age` - -These are different clocks: - -- `Age` is HTTP freshness age. It can already be thousands of seconds when RustProxy - receives an object because the origin/CDN `Date`/`Age` contributes to it. On an ordinary - fresh HIT it advances from RustProxy's stored receive time even when the backend never - supplied an `Age` header. A successful validator revalidation (backend `304`) starts a - new freshness calculation, so the next `Age` can legitimately be `0`. Requests carrying - `Cache-Control: no-cache`, `max-age=0` or `Pragma: no-cache` can therefore make repeated - validated HITs appear with `Age: 0`. -- `X-Cache-Resident-Age` is RustProxy-specific and counts whole seconds that the stored - representation has remained resident in this cache. - -Resident age is preserved across a successful backend 304 and across hot-to-cold/cold-to-hot -movement. An immediate first HIT can legitimately show `0` because less than one full second -has elapsed; a long-lived resident object should then keep increasing rather than repeatedly -resetting to zero. Use this header when the diagnostic question is cache residency rather than -HTTP freshness. - -## Practical HIT test - -Enable cache plus `X-Cache`, `X-Cache-Hits` and `X-Cache-Resident-Age`, then make -repeated identical requests that read the complete body: - -```bash -for n in 1 2 3; do - curl -q -sS -D - -o /dev/null \ - -H 'Host: app.localhost' \ - http://127.0.0.2:8888/cache - sleep 2 -done -``` - -For an eligible response, expect MISS then HIT/HIT. `X-Cache-Resident-Age` should grow -between later HITs. A browser is a poorer diagnostic because its own cache may satisfy -a navigation without sending any request to RustProxy. - -HEAD is now eligible too. It shares the GET representation, but GET is still the best -basic test because it proves that the body was completely consumed and published. - -## Vary test - -A response with: - -```text -Vary: Accept-Language -``` - -stores language variants under one primary scheme + Host + URL object identity. Two English -requests can HIT each other; a Polish request must not receive the English body. If the -origin later changes between no `Vary` and `Vary: Accept-Language`, incompatible older -variants are removed rather than used as a fallback. - -`Vary: *` is not cacheable. - -## Cookie/static behavior - -`request_cookie` is a BYPASS by default. With `static_ignore_cookies=true`, configured -static extensions such as `.css`, `.js`, fonts and images ignore the request Cookie for -cache eligibility/keying. This is intentionally limited to static paths. A response -with `Set-Cookie`, `Cache-Control: private/no-store`, or `Vary: Cookie` still cannot be -shared through that optimization. - -This setting is useful for raising the asset hit ratio for logged-in users without -turning dynamic personalized pages into shared objects. - -## Conditional requests and reloads - -Fresh cached objects can satisfy `If-None-Match`/`If-Modified-Since` locally. Request -`Cache-Control: no-cache`, `max-age`, `min-fresh` and legacy `Pragma: no-cache` select -validation instead of creating a separate cache key. If a stored validator exists and -the backend returns 304, RustProxy refreshes metadata and reuses the body. - -Hard browser reloads commonly add revalidation directives, so they may produce -`cache_revalidated` instead of a plain `cache_hit`. This is different from a cache miss. - -## Reason tokens - -When cache diagnostics are enabled, these fixed reason tokens explain decisions: - -| Token | Meaning | -|---|---| -| `cache_disabled` | Effective cache is disabled. | -| `route_disabled` | Selected route explicitly disables cache. | -| `request_method` | Method is not GET/HEAD. It passes and does not purge the cache. | -| `request_cookie`, `request_authorization` | Private request state bypasses shared cache. Static Cookie exception may apply. | -| `request_no_store` | Client forbids use/storage of shared cache. | -| `request_no_cache`, `request_max_age`, `request_min_fresh`, `request_pragma_no_cache` | Client requests validation. | -| `request_body`, `request_range`, `request_precondition` | Request is outside reusable shared-object semantics. | -| `query_excluded`, `path_excluded` | Configured exclusion matched. | -| `tinyauth`, `sticky_cookie` | Authentication/affinity policy deliberately bypasses shared cache. | -| `response_set_cookie`, `response_cache_control` | Origin response is not safely shareable. | -| `response_vary`, `response_ttl_size_or_metadata` | Vary/freshness/size/metadata prevents storage. | -| `response_headers_policy` | Transformed client-response policy is not cacheable. | -| `cache_miss`, `cache_hit` | No reusable variant was found, or a variant was reused. | -| `cache_conditional_hit` | Fresh cache metadata satisfied the client validator locally. | -| `cache_revalidated` | Backend 304 validated the stored body and it was reused. | -| `cache_revalidated_uncached` | Validation succeeded but refreshed policy no longer permits storage. | - -A MISS only means lookup found no reusable object. The later origin response can still -fail storage because of privacy headers, size, incomplete streaming, trailers or a -changed policy. - - -## Memory accounting in 0.16.0 - -The process **RSS total** is an operating-system measurement and is intentionally shown separately from RustProxy's own cache accounting. Do not add the rows together: they overlap. The process block reports: - -- `RSS total` (`VmRSS`) - all resident pages currently attributed to the process; -- `RSS anonymous` (`RssAnon`) - anonymous resident pages, which typically include heap/stacks and other private allocations; -- `RSS file-backed / shared` (`RssFile` / `RssShmem`) - resident mapped file/shared-memory pages; -- `data address space / swap` (`VmData` / `VmSwap`) - address-space and swapped-page context, not extra resident bytes. - -The cache block separately reports memory RustProxy can attribute to the cache subsystem: - -- `hot logical` - visible cached body bytes (`body.len()`); -- `hot allocated` - the hard-budget charge for retained backing allocations plus entry metadata (`resident_bytes` remains a compatibility alias); -- `transient` - bounded cache allocations temporarily outside the hot store; -- `fill` - active response buffers being assembled before publication; -- `cold queue` - complete bodies waiting for cold-tier persistence; -- `cold I/O` - bodies currently being written to or read from the cold tier. - -This explains why, for example, a process RSS around 630 MiB can coexist with only about 100 MiB of reported hot cache: the old `Pamięć hot` card was never a whole-process memory counter. TLS/QUIC/Hyper/Tokio allocations, thread stacks, configuration/routing state, active request bodies and allocator-retained pages are also part of RSS but are not falsely assigned to a RustProxy subsystem when the source cannot measure them directly. - -The existing Linux/glibc allocator-release path remains in place: large cache demotion/purge paths call `malloc_trim(0)` through `release_allocator_memory()`. Since 0.24.5, HOT allocation, active fills and bodies moving to/from COLD are explicitly budgeted. COLD queue admission is byte-bounded as well as count-bounded. RSS is still a process-wide view containing the same cache memory plus TLS/QUIC/Hyper/Tokio, stacks and allocator retention, so cache gauges must not be added to RSS. Long-running acceptance should verify that HOT stays within `hot_limit_bytes`, transient stays within `transient_limit_bytes`, demotion drains transient bytes, and process RSS reaches a practical plateau under representative traffic. - -### Sustained traffic, vhost saves and RSS - -A vhost configuration save can make RSS fall abruptly for two independent reasons. First, the current cache compatibility identity includes vhost configuration; when that identity changes, publication performs a cache purge so representations created under the old origin/configuration contract cannot survive incorrectly. Second, publishing the newly compiled routing snapshot retires the previous snapshot and its upstream clients; once outstanding references finish, idle connection-pool state and related buffers can be released. A visible RSS drop after saving a busy vhost is therefore a useful diagnostic clue, but saving/reloading a vhost is not a memory-maintenance requirement. - -The 0.25.0 development runtime adds two maintenance mechanisms that do not reload configuration: - -- 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 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 - -HOT RAM is bounded by allocated/charged `max_bytes` and `max_entries`; `hot_logical_bytes` separately exposes payload length. Fill and COLD handoff/read memory are bounded by `transient_max_bytes`, and queued demotions also obey `cold_queue_max_bytes`. Optional cold storage demotes LRU -or sufficiently idle hot objects asynchronously. Cold hits verify the body digest and -promote the object back to RAM. Version 0.11.5 uses cold metadata format 2 to persist -Vary identity and resident age; old-format metadata is not reused. - -Cold is not required for normal hot-cache stability. With cold disabled, hot objects leave -RAM because of expiry, hot capacity/LRU pressure, explicit purge, configuration generation -changes, replacement of the same logical representation, or the optional HOT auto-trim -waterline. Auto-trim is not a blind TTL: it acts only when HOT is above its configured target -and only on entries idle longer than the configured threshold; it can be disabled independently. - -## Cache panel and API - -The Cache panel shows hot/cold object counts, RAM/disk usage, HIT/MISS/BYPASS, hit ratio, -fill pressure, evictions/expiry, per-domain decisions and stored objects. Live polling -refreshes the view; it does not itself purge cache objects. - -`GET /api/cache?offset=0&limit=50` returns aggregate and object data. Add `vhost=ID` to -filter. `POST /api/cache/purge?vhost=ID` invalidates one vhost; omitting the query purges -all cache objects. Mutations remain authenticated, CSRF-protected and audited. - -Hit ratio is `HIT / (HIT + MISS)`. BYPASS requests are excluded, so a large number of -cookie/authenticated requests can reduce backend offload without mathematically lowering -that displayed ratio. For the highest safe reusable-object ratio, give static assets -long explicit public TTLs, keep their URLs stable/immutable, leave the static-cookie -optimization enabled when appropriate, and avoid unnecessary `Vary` fields. diff --git a/docs/CACHE_HOTPATH_0.34.0.md b/docs/CACHE_HOTPATH_0.34.0.md deleted file mode 100644 index e19a504..0000000 --- a/docs/CACHE_HOTPATH_0.34.0.md +++ /dev/null @@ -1,78 +0,0 @@ -# RustProxy 0.34.0 HOT HIT follow-up (source candidate) - -## What changed - -The *existing* optimized HOT cache no longer removes/reinserts full entries at -HIT time. This follow-up further shortens the critical section without changing -cache policy or accepting an approximate eviction algorithm: - -1. Store each published response's immutable headers in `Arc`. - Snapshot the Arc and the shared `Bytes` body while locked, release the HOT - mutex, then clone the headers and build the per-request response. Header - cloning is **not eliminated**, because per-request `Age`, `Cache-Status` and - transformation annotations must not mutate the stored representation. -2. Preserve exact per-HIT sequence, hit count and LRU ordering. Bypass the Vary - index traversal for the common direct-key/no-`Vary` representation; continue - selecting keyed variants for other representations. -3. Replace the per-domain flat pair-key map with a nested ordered map so a - known `(scope, domain)` is updated using borrowed keys. Preserve the 4096 - distinct-domain limit and the dashboard wire format. Update cached-byte - accounting without allocating `String` keys. -4. When there are no active BAN rules, a cached response does not acquire the - BAN mutex or clone any BAN rules. Active rules still perform expiration, - matching and removal. Publication/removal of BAN rules updates an atomic - count; BAN and PURGE security checks remain enabled. -5. Remove repeated full HOT-entry `sweep()` scans. The existing - `retention_deadline()` explicitly returns `u64::MAX`: entries beyond their - freshness TTL are retained for conditional revalidation and `max-stale`. - Capacity/idle eviction and cold-tier GC remain responsible for memory/disk - retention. HTTP freshness checks and `Age` are unchanged. -6. Avoid allocating the static cache-reason text on each repeat occurrence: - previously registered reasons update by borrowed lookup. Recorded dashboard - fields, counters and reason labels are unchanged. - -No new configuration, cache key, COLD on-disk format, migration, panel toggle or -feature flag was added. Cache query bypass, transformed representations, origin -validators, TTL, hot-to-cold demotion, backend selection and `sendfile` behavior -are intentionally unchanged. - -## Tests and verification - -Native Rust test file: `src/cache/tests/hot_hit_034_tests.rs`. - -- Multi-threaded HOT access checks monotonic, unique hit counters and bounded - LRU/index growth. -- Transformed response HIT retains its ETag/extension; changes to the response - header map never mutate cached headers. Memory accounting and per-domain - saved-byte counters remain exact. -- Exact LRU eviction, selective BAN, stale revalidation retention and - conditional client `max-stale` are covered. -- The manual **ignored** in-process lookup microbenchmark measures the whole - `get_hot()` response construction, not network RPS. - -```bash -./dev.sh check -CARGO_INCREMENTAL=0 ./dev.sh test -CARGO_INCREMENTAL=0 cargo build --release --locked -cargo test --release --locked --lib manual_hot_lookup_microbenchmark -- --ignored --nocapture -``` - -For real HTTP RPS, compare two compiled RustProxy 0.34.0 binaries -with the same cache-enabled, cache-warmed vhost and fixed no-query URL. -A cache-disabled nginx comparison cannot measure this HOT-specific improvement. -Check `X-Cache: HIT`, body SHA-256, exact `Cache-Status`, transform marker, Age, -ETag, p95/p99, CPU/request, memory and warm-up/eviction counts while measuring. - -## Remaining performance bottlenecks - -The HOT store still uses a shared mutex to provide exact LRU, per-key -generation checks and publication guarantees. Per-response `HeaderMap` cloning -is still required and now occurs outside this lock. Sharding the store or -making LRU eventually consistent would require a separate, extensively tested -cache architecture revision: doing that without compilation and live soak -would risk invalidation, accounting, BAN and tier-demotion regressions. - -**Acceptance gate open:** the changed Rust source was not compiled in this -environment, new Rust tests were not run and no HOT HIT RPS improvement has been -measured. Source-only guards, SQL and upgrade tests do not establish a safe -production build. diff --git a/docs/CHANGELOG_TECHNICAL_ARCHIVE.md b/docs/CHANGELOG_TECHNICAL_ARCHIVE.md deleted file mode 100644 index 8bb4325..0000000 --- a/docs/CHANGELOG_TECHNICAL_ARCHIVE.md +++ /dev/null @@ -1,876 +0,0 @@ -# Historical developer-level changelog archive - -> Archived during the 0.25 documentation cleanup. This file preserves the pre-cleanup -> detailed release record, including source refactors, regression notes and -> packaging details. It is historical technical evidence, not the operator-facing changelog. -> Use [`../CHANGELOG.md`](../CHANGELOG.md) for user/operator-visible release changes. - -# Changelog - -## 0.26.7 - -### Backend client lifecycle and cache diagnostics - -- Preserve compiled upstream clients when an active-snapshot rebuild leaves the backend origin, address policy, keepalive policy and connect timeout unchanged. This retains Hyper connection pools together with generation age, per-generation request counters and cumulative rotation totals instead of resetting those diagnostics on unrelated configuration rebuilds. -- Keep incompatible backend changes isolated: changing the origin or any client-construction input still creates a fresh upstream client and fresh generation state. -- Add a pool regression proving compatible snapshot rebuilds reuse the existing backend/client object; existing upstream-client tests continue to cover age-triggered recycling. -- Extend `/cache` with explicit HOT and COLD occupancy meters for both configured object limits and byte/disk budgets. Percentages are clamped for progress rendering while the raw counters remain visible. - -### Container certificate tooling - -- Build the OCI runtime image with a dedicated Certbot virtual environment containing the Cloudflare, OVH and RFC2136 DNS plugins supported by RustProxy, all pinned with the same Certbot release. -- Refresh container documentation so it no longer tells operators that those provider plugins must be added separately. - -## 0.25.1 - -### Source modularization and release guards - -- Enforce a 500-line ceiling for Rust implementation/test source files and bounded dashboard JavaScript, Docker Agent Go and operational deployment/migration modules. -- Keep Rust tests in dedicated `tests.rs` / `tests/` sources and Docker Agent tests in `_test.go`; split `config`, `docker`, Web Server, dashboard bundles, Docker Agent, deployment and NPM migration source by responsibility. -- Preserve public browser asset URLs by assembling the bounded app/vhost source fragments once inside the admin router. -- Add compiler-independent regressions for dangling Rust attributes, broken `include!` targets, missing Web Server `htaccess`/`server_protocol`/`anyhow::Context` wiring and browser split-boundary syntax. -- The package workspace still has no Rust toolchain, so `cargo fmt/check/test --locked --all-targets` remains a release-host gate. - - -## 0.25.0 - -- Add reusable Web Services with document root, index-file list and optional directory listing, managed from the new **Web Server** dashboard section. -- Add `Reverse Proxy` / `Web Server` source selection for vhosts and custom locations, including mixed static/proxy routing in one vhost and reuse of one Web Service by multiple vhosts. -- Add static `GET`/`HEAD`, streaming responses, MIME/length headers, directory index/redirect/listing behavior, HTTP validators and single-range requests (`206`/`416`). -- Enforce document-root confinement for Web Server paths, reject ambiguous/encoded traversal forms and prevent symlink escape by not following symlinks during Unix filesystem traversal. -- Reuse the existing vhost ACL/authentication/header/error/TLS/HTTP pipeline for local files without adding a separate router, error-page system or object cache. -- Add an authenticated **Docs Reader** for bundled project documentation alongside the existing API/OpenAPI view, and separate the current documentation index, release audit and open TODO material. -- Split the 0.25 Web Server implementation from the former monolithic `src/web_server.rs` into the responsibility-based `src/web_server/` module (`config`, filesystem, security, response, routing, FastCGI, dispatcher, `.htaccess` and tests), keeping `mod.rs` as the stable public facade and updating source/package gates for the new layout. -- Extend the source-layout cleanup across the Rust tree: every checked-in `.rs` file is capped at 500 lines, large `config`/`docker`/`server`/`runtime`/`balance`/`prometheus`/`proxy`/cache/admin/etc. modules are split by responsibility, unit-test bodies live in separate test files, and compiler-independent contracts reject dangling attributes/doc comments, broken `include!` targets and unbalanced split fragments. - -## 0.24.10 - -- Keep Error File inside the existing revisioned `logging` configuration section while retaining its own Settings form; both access-log and error-file forms save atomically through `/api/config/logging`, so enabled/path/format persist without introducing a special endpoint. -- Preserve backward compatibility for historical `/api/config/logging` clients that send only the access-logging object. -- Keep the Error File panel free of the redundant ERROR/logrotate help sentence. -- Remove collapsing from Response time history and add an accessible information control explaining the cumulative mean metric. -- Update package/source/UI/OpenAPI/release identity to 0.24.10. - -## 0.24.9 - -- Added composite Settings > Logging support for `error_log.enabled`, path and format while retaining legacy `/api/config/logging` payload compatibility. -- Removed the Error Log help sentence from the panel while keeping the existing controls and behavior unchanged. - -- Harden the built-in release checker for FreeBSD/OPNsense and other Unix platforms: supplement bundled Mozilla roots with common system CA bundles/`SSL_CERT_FILE`/`CURL_CA_BUNDLE`, follow bounded non-downgrading HTTP(S) redirects, and report failed checks as normal application `ERROR` events without changing dashboard behavior. -- Add a central AES-256-GCM encrypted Secret Store with private local master-key storage and migration of existing ACME DNS-provider credentials. -- Add encrypted full backup/restore with Argon2id, authenticated encryption, explicit manifests/checksums, staging validation and rollback-safe activation. -- Add an optional ERROR-only JSON/RAW file sink with bounded asynchronous writes, central secret redaction and system `logrotate` integration. -- Extend source/release regressions and update release identity to 0.24.9. - -## 0.24.8 - -- Expand the overview Cache RAM diagnostics with HOT usage against limit, transient RAM history for total/fill/COLD queue/COLD I/O, and COLD storage occupancy against its disk budget. -- Hide the long RSS/cache accounting explanation behind an accessible information control in the Process heading. -- Keep COLD disk occupancy explicitly separate from transient process RAM accounting. -- Update release identity to 0.24.8. - -## 0.24.7 - -- Make the transform temporary-memory ceiling configurable from Settings and preserve a 128 MiB default for existing configurations. -- Add a configurable process-wide transform concurrency ceiling; excess work fails open unchanged rather than waiting behind transformation work. -- Add persisted RAM saver, Recommended, Performance and Uber performance transform resource presets without changing enabled transform formats or extension allow-lists. -- Show transform-resource warnings when the selected budget cannot fit the worst-case buffers for one maximum compressed body or exceeds half of detected host/cgroup RAM. -- Add live dashboard resource accounting: current transform RAM reservation/budget and active slots, HOT/transient cache RAM, non-cache process RSS, plus COLD disk occupancy and object limits. -- Include global transform representation/eligibility settings in cache compatibility identity while excluding resource-only preset/budget/concurrency controls. -- Extend SQLite/OpenAPI/offline memory/release regressions and update release identity to 0.24.7. - -## 0.24.6 - -- Persist the selected memory and COLD cache preset identities in the SQLite-backed cache configuration and restore their selected/custom state in Settings. -- Add a Recommended COLD policy preset with bounded queue sizing and conservative demotion/GC defaults while preserving operator-selected disk path, disk budget and object limit. -- Add a 128 MiB process-wide transform temporary-memory budget, bounded body aggregation independent of claimed Content-Length, and bounded decompression growth. -- Validate/compact JSON without a full DOM tree, remove routine HTML raw-tag string allocations and avoid repeated CSS output scans. -- Cap stale-while-revalidate background work at 8 keys and proactive sleeping refresh tasks at 256; keep COLD cleanup single-flight and candidate memory proportional to the requested deletion limit. -- Bound COLD delete fallback tasks and deletion metadata, and add Rust-independent regression checks for memory/task bounds across background workers. -- Update release identity to 0.24.6. - -## 0.24.5 - -- Normalize actually empty, length-unknown HTTP/3 request bodies before HTTP/1.1 upstream forwarding so Hyper emits a zero-length request instead of chunked framing for methods such as DELETE. -- Keep non-empty HTTP/3 bodies streaming by replaying the inspected first frame, leave declared `Content-Length` requests untouched, and do not alter HTTP/2 upstream framing. -- Add focused regression coverage for empty/non-empty H3 DELETE and generic empty H3 methods forwarded to HTTP/1.1. -- Keep the source package consistent with its existing release contracts by dropping the stale unreferenced `src/system_stats/bsd.rs` file and updating the offline container check to the already-shipped Debian 13/Trixie base images. -- Harden cache RAM admission without changing the HOT -> COLD storage model: HOT now has a 256 MiB allocation-aware default budget, fills reserve memory before buffer growth, and a small bounded allocator headroom replaces max-object-sized overshoot. -- Add a separate 64 MiB transient RAM budget plus a 32 MiB byte ceiling for the COLD writer queue; demoted bodies stay charged until persistence drops the `ColdDraft`, while persisted `ColdEntry` values retain only disk paths/metadata. -- Preserve body backing-allocation accounting across fill, revalidation and COLD promotion, expose HOT logical vs allocated bytes and the HOT/transient/COLD-queue limits, and add memory-pressure/LRU-admission counters. -- Make global cache disable authoritative over per-vhost enablement, remove the artificial 1 GiB HOT/object configuration ceiling, and hot-apply reduced HOT capacity with bounded LRU demotion/eviction. -- Add Settings memory sizing based on effective host/cgroup RAM with conservative, recommended and performance presets, a live recommended profile, host/cgroup headroom warnings, and controls for transient RAM, COLD queue RAM and bounded LRU admission. Automatic presets are disabled below 256 MiB effective RAM. -- Update release identity to 0.24.5. - -## 0.24.4 - -- Disable Linux Transparent Huge Pages for the RustProxy process with `prctl(PR_SET_THP_DISABLE)` before the Tokio runtime is created, avoiding a host-wide THP policy change while preventing 2 MiB anonymous huge pages from amplifying allocator RSS retention. -- Treat the process-local THP operation as best-effort: unsupported/rejected kernels emit a startup warning and RustProxy continues to run. -- Update release identity to 0.24.4. - -## 0.24.3 - -- Add a per-certificate reload operation for manual/file-backed certificate IDs and authorize it through the scoped Certificate API as well as the normal administrator session. -- Allow multiple Certificate API tokens (bounded to 32), operator comments, individual revocation and revoke-all while continuing to persist token hashes only. -- Replace the overview Core State block with a live connection/activity chart, move uptime into the Process block below process start, add RX/TX-style upload/download detail under Payload transferred, and persist per-browser hidden network-interface charts. - -- Add `MALLOC_ARENA_MAX=2` to managed Linux/systemd service units to reduce glibc arena growth and long-running RSS retention while retaining the environment-file override. -- Remove an identity-body copy from the transformation pipeline, remove `Vec` plus final output-copy allocations from the JavaScript/CSS minifiers, release successful-transform temporary buffers earlier, and use a throttled `malloc_trim(0)` after large Linux/glibc transforms. -- Make HTTP/2 lifecycle tuning listener-scoped like HTTP/3: keep H2/H2C and H3 enablement in **Settings -> Listeners**, expose per-listener H2 GOAWAY/lifetime limits under **HTTP connections -> HTTP/2**, retain per-listener QUIC limits under **HTTP/3**, and link listener rows to the matching tuning page. Existing top-level `http2` values remain a compatibility fallback for listeners without explicit H2 settings. -- Rename **Proxy process** to **Process** and dynamically hide unsupported process diagnostics and history charts instead of showing unknown values. -- Remove the stale, unreferenced legacy `src/system_stats/bsd.rs` parser file from the source package; shared library collectors remain the only BSD statistics implementation. -- Update release identity to 0.24.3. - -## 0.24.2 - -- Version-only release bump from 0.24.1; no functional, configuration or protocol changes. - -## 0.24.1 - -- Restore system network interface discovery and RX/TX counters after the shared-statistics refactor: use the cross-platform `sysinfo::Networks` API as the primary network inventory/counter source, merge `systemstat` as a library-level supplement/fallback for missing or partial inventory, preserve per-interface rate calculation, and stop silently truncating the interface list. -- Remove RustProxy's artificial hostname-count ceilings from certificate SNI bindings, local certificate generation/import validation, ACME domain validation, vhost/redirect/404 hostnames, administration allowed hosts, host ACL policies and Docker aliases. Input validity and bounded control-plane/resource limits remain enforced. -- Replace per-BSD command/parsing statistics code with shared library collectors: `systemstat` for host metrics, `sysinfo::Networks` for network inventory/counters, and `metrics-process` for process metrics, using the documented dummy process fallback on targets without a native collector. -- Preserve Linux `/proc` and cgroup-specific details as additive compatibility metrics while using the common library API for portable host/process values. -- Add portable swap, system uptime, process virtual memory/limit, descriptor limit and process start-time diagnostics where the libraries support them. -- Add authenticated runtime metadata for platform, statistics source, resolved data directory and exact SQLite database path. -- Make local CLI data-directory discovery understand both `/var/lib/rustproxy` and the BSD/OPNsense `/var/db/rustproxy` convention. -- Move Logging into **Settings -> Logging** while retaining legacy `/logging` navigation compatibility. -- Update release identity to 0.24.1. - -## 0.24.0 - -- Replace abrupt downstream H2 idle termination with a GOAWAY-based graceful shutdown that rechecks current activity and waits for accepted streams. -- Add independent H2 idle timeout, maximum connection lifetime and drain timeout settings; lifetime may be disabled with `0`. -- Bound H2 draining so stalled streams cannot keep a retiring connection open indefinitely. -- Keep H2 Extended CONNECT/WebSocket streams under the same active-stream accounting and graceful drain path. -- Extract system statistics into platform backends while preserving Linux metric names, units and behavior; add native FreeBSD/OpenBSD/NetBSD sampling where available and explicit nulls otherwise. -- Add runtime platform to Proxy Process and Prometheus platform info. -- Add Settings UI/API/OpenAPI/language-pack support plus H2 lifecycle/configuration regressions. -- Reorganize **Settings -> HTTP connections** into HTTP/2 and HTTP/3 sub-tabs; move listener-scoped H3/QUIC enablement and limits out of the Listeners editor while preserving restart semantics and the existing listener configuration model. -- Update release identity to 0.24.0. - -## 0.23.3 - -- Make external certificate deployment convergent: create missing manual certificate ids, rotate existing manual ids, no-op identical RustProxy-owned generations, and keep managed ACME ids protected from external ownership. -- Make the OPNsense os-acme-client hook reconcile on every run so RustProxy remains the source of truth and deleted/drifted external certificate entries self-heal. -- Add per-vhost **Enable WebSocket legacy support** for older appliance/embedded WebSocket implementations. -- Force legacy-mode WebSocket upstream handshakes to HTTP/1.1 and force the WebSocket `Host` to the configured backend authority and align `Origin` to the matching backend scheme + authority. -- Tolerate legacy `101 Switching Protocols` response framing while keeping `Sec-WebSocket-Accept` and subprotocol validation strict. -- Add UI/OpenAPI/translations, documentation and regression contracts for the compatibility mode. -- Update package/source/UI identity and release verification contracts to 0.23.3. - -## 0.23.2 - -- Add selectable 60/120/300/600-sample history windows to the Cache and Transformation live charts, with bounded per-workspace browser persistence. -- Fix `rust-proxy --livelogs` on HTTPS administration listeners by detecting the HTTP 308 redirect and reconnecting over TLS instead of looping. -- Pin local CLI HTTPS to leaf certificates present in the private `data-dir/certs` directory and reuse the HTTP/HTTPS-aware client for config and certificate CLI API calls. -- Update package/source/UI identity and release verification contracts to 0.23.2. - -## 0.23.1 - -- Add default-collapsed, full-width live activity charts to the Cache and Transform workspaces. -- Plot Cache HIT/MISS/BYPASS rates for the selected scope using deltas from the existing cache runtime counters. -- Plot Transform applied/cache-HIT/skipped/error rates for the selected scope using the existing transform metrics endpoint. -- Reuse the administration dashboard SVG/grid/legend visual language, keep the existing 3-second live refresh, and add English/Polish language-pack labels. -- Update package/source/UI identity and release verification contracts to 0.23.1. - -## 0.23.0 - -- Bound inactive inbound HTTP/2 connections to five minutes so idle TLS/H2 sessions cannot accumulate indefinitely toward the process connection limit; export `rust_proxy_http2_active_connections` for direct diagnosis. -- Account hot-cache response-body backing allocation capacity rather than only visible body length, preventing `BytesMut` growth slack from bypassing the configured RAM budget and under-reporting resident cache memory. -- Add a scoped external Certificate API credential managed from a new **Settings → Certificate API** tab. RustProxy stores only the token hash plus a non-secret Key ID; the cleartext token is returned only on rotation. -- Allow the scoped Key ID + Bearer Token only on `POST /api/certificates/deploy` and `POST /api/certificates/reload`, require HTTPS for non-loopback token use, track last use, support immediate revoke, and retain normal administrator-session/CSRF access as an alternative. -- Keep production certificate operations available directly from the installed binary (`rust-proxy tls deploy` / `rust-proxy tls reload`) without requiring `dev.sh`. -- Add `rust-proxy --livelogs`: an interactive varnishlog-style all-vhost live access stream with structured filters (`host`, `vhost`, `backend`, `route`, `client`, `method`, `transport`, `cache`, status classes, path prefix, errors and slow requests), pause/resume, per-request JSON inspection and detailed error/WebSocket/H3/cache fields. -- Back live logs with a bounded non-blocking broadcast stream and authenticated SSE endpoint; slow consumers receive an explicit `gap` event with the dropped-entry count instead of silent loss. -- Split the large **Settings → Cache** form into **General**, **Resilience**, **Cold cache**, and **Static & diagnostics** sub-tabs while retaining one atomic cache-policy save. -- Add SQLite schema v5 for the scoped certificate credential, OpenAPI/security schemes, UI translations, audit events and 0.23.0 regression/release documentation. - -## 0.22.3 - -- Add downstream HTTP/1.1 `Upgrade: h2c` on listeners that explicitly enable cleartext HTTP/2, preserving the existing prior-knowledge path and rejecting malformed/body-bearing upgrade attempts before protocol switch. -- Reconstruct the accepted H1 upgrade request as HTTP/2 stream 1 and hand subsequent framing/streams to the existing Hyper H2 server path; include raw-TCP interoperability regression source. -- Add authenticated `POST /api/certificates/reload` and `tls reload` to re-read configured public certificate/key files and hot-publish a fresh TLS/H2/H3 snapshot without changing the desired configuration revision. -- Add `tls deploy --id ... --cert ... --key ...` and authenticated `POST /api/certificates/deploy` for externally issued local certificates such as Certbot. The CLI performs bounded local reads while the running service owns pair/SAN validation, immutable generation staging, revision-CAS publication and rollback on failure, avoiding root-owned staged PEMs when Certbot hooks run as root. -- Keep all vhosts assigned to one manual certificate id synchronized during external rotation; reject RustProxy-managed ACME ids to avoid dual renewal ownership and report the administration-TLS restart caveat explicitly. -- Add Certificates-panel reload UX, OpenAPI contracts, audit events, translations and 0.22.3 audit/test documentation. - -## 0.22.2 - -- Add bounded replay-safe backend failover retry for body-free GET/HEAD/OPTIONS requests, including retry policy controls, passive-health integration, logs and per-backend retry counters. -- Correct ordinary explicit-HTTP/2 upstream requests and add a real Hyper h2c prior-knowledge regression path. -- Add per-backend request-concurrency limits with bounded capacity waiting, `503` rejection and capacity/queue-timeout metrics. This limits full proxy exchanges, not TCP socket count. -- Add detailed upstream failure classes, last failure detail/timestamp, and backend health ejection/recovery timestamps to runtime diagnostics and Prometheus. -- Expand the backend administration table with concurrency capacity, last failure and last recovery columns. -- Parse public X.509 leaf metadata for issuer, SANs, validity, remaining days and expiry warning state, and surface ACME renewal status/last attempt/error in certificate inventory. -- Extend OpenAPI, language packs, source regressions, audit/TODO and release documentation for the expanded 0.22.2 scope. - -## 0.22.1 - -- Vhost UX: replaced separate WebSocket/H2 RFC 8441/H3 RFC 9220 checkboxes with one transport selector; H2/H3 modes keep HTTP/1.1 Upgrade as an explicit fallback and unavailable transports are disabled from listener capabilities. - -- UI polish: replaced the overview TLS listener tile with compact HTTP protocol and WebSocket protocol mini charts; Target and Cache key/tags cells remain copyable with full values. - -- Keep the overview metrics in one shared stats grid so the WebSocket/H2/H3/TLS row uses exactly the same card geometry as the primary metrics, without a transport-specific dashboard layout. Make Cache **Target** and **Cache key / tags** cells copy their full untruncated value on click/keyboard activation. -- Polish the 0.22.1 administration UI: move **Active tunnels** into the WebSocket dashboard card, flatten the H1/H2/H3 tunnel breakdown to match the standard transport-card styling, and align Cache modal close actions with the vhost modal header pattern. -- Fix Rust ownership in tunnel registration by releasing the client-IP mutex guard before moving the shared exchange into the active tunnel, and remove an unnecessary mutable H2 upstream reader binding. -- Move selective cache PURGE/BAN and the active BAN list into dedicated Cache modals, keep their launch actions beside **Purge selected scope**, and use null-safe inline operation feedback consistent with login errors. -- Add opt-in RFC 8441 WebSocket Extended CONNECT for HTTP/2, with per-vhost controls beside RFC 9220, listener-scoped SETTINGS advertisement, H2-to-H1 bridging, explicit H2 upstream forwarding, cancellation/idle timeout and transport-specific metrics. -- Add opt-in cleartext HTTP/2 prior-knowledge (`h2c`) listeners. Legacy HTTP/1.1 `Upgrade: h2c` remains deliberately unsupported because the current Hyper integration cannot safely reconstruct the required HTTP/2 stream 1 bootstrap. -- Add stable active-tunnel IDs, authenticated tunnel inventory/kill API, operator-supplied close reasons, audit records, RX/TX/duration metadata and an administration modal. -- Consolidate active WebSocket/tunnel visibility on the main dashboard as total plus H1 / H2 RFC 8441 / H3 RFC 9220 counters while retaining ordinary H2/H3 request statistics separately. -- Preserve `Accept-Ranges` from origins and synthesize it only for RustProxy-generated cached range responses; transformed/compressed representations continue to remove incompatible range advertising. -- Update source/package/UI/OpenAPI/Debian/Grafana/tooling identity to 0.22.1. - - -## 0.22.0 - -- Fix a Rust ownership regression in the upstream request path: retain a cloned origin URI for proactive refresh after the request URI is assigned. -- Add per-cache-key request collapsing for foreground MISS/fill traffic with bounded wait, configurable timeout fallback, cancellation-safe leader release and collapse metrics. -- Add single `Range: bytes=...` handling from complete cached objects, including `206`, `416`, `Content-Range`, `Accept-Ranges` and `If-Range`; multipart/unsupported ranges remain origin pass-through and are not cached as separate objects. -- Extend stale-if-error with configurable transport failure classes and an explicit 5xx status allowlist while preserving the previous default behavior. -- Add bounded proactive cache refresh with pre-expiry scheduling, deterministic sampling, concurrency limits and reuse of the existing validator revalidation path. -- Add selective cache PURGE by exact URL, prefix, tag, regex or cache key, including query/Vary handling, RAM + Cold Cache invalidation and a publication barrier preventing pre-PURGE fills from republishing stale data. -- Add lazy BAN rules with lookup-time enforcement, bounded rule count/TTL, incremental garbage collection, rule deletion and metrics. -- Add authenticated administration API and Cache panel controls for PURGE/BAN, broad-operation confirmation, per-object Purge URL actions, BAN listing/removal and audit entries. -- Extend cache diagnostics, OpenAPI, translations and regressions for the 0.22 cache behavior. -- Update package/source/UI/OpenAPI/Debian/Grafana/tooling identity to 0.22.0. -- Preserve the resolved dependency graph; compiler/live acceptance remains an explicit release-host gate when Cargo/Rust are unavailable in the packaging workspace. - -## 0.21.0 - -- Fix browser refresh on the dedicated **Transform** route by serving the dashboard document at `/transform` and covering it in the panel-route regression. -- Align the Cache **Transformed** marker with the dashboard geometry by replacing the capsule outline with the standard badge border and a 3px corner radius. -- Give the Live traffic **Transformed** series a dedicated violet chart token in dark/light themes so it remains distinct from HTTP/2 green. -- Add a dedicated Transform administration workspace with global/per-vhost runtime counters for applied/skipped/error transformations, transformed cache HITs, byte savings, reduction, transform time, kind breakdown and bounded skip reasons. -- Add a transformed-response series to **Live traffic** (fresh transformations plus transformed cache HITs) and render **Core state** uptime in compact human-readable units. -- Rename the proxy-owned backend timing diagnostic to `X-RustProxy-Backend-Time`; the legacy lookalike `X-Rust-Proxy-Backend-Time` is stripped but is no longer emitted. -- Fix Docker-vhost Passive Backend Health checkbox sizing so enabling the expanded editor cannot make fields overlap. -- Remove the unused mutable `origin` binding from the background revalidation path. -- Replace the monolithic `src/cache.rs` with the responsibility-based `src/cache/` module (`model`, lifecycle, cold storage, dashboard, tickets, lookup, policy, fill and tests) and update source/package gates for the new layout. -- Persist transformed-representation metadata through hot/Cold Cache, mark transformed objects in the Cache dashboard, and append `transformed` to standardized RFC 9211 `Cache-Status` diagnostics; origin `X-Cache-Status` lookalikes are stripped rather than emitted. - -- Shard Cold Cache storage and add bounded asynchronous garbage collection plus selective manual cleanup. -- Add RFC 5861 stale windows, request `max-stale`, age-aware request `max-age=N`, and correct `must-revalidate` / `proxy-revalidate` behavior while preserving `s-maxage` priority. -- Harden transformed cache fills so transformed Content-Length is authoritative, incomplete streams cannot publish partial objects, and canonical transformed identity representations do not fragment on origin `Vary: Accept-Encoding`. -- Add the static-response transformation pipeline before cache/client compression, with strict `no-transform`, separate origin/representation validators and JS/CSS plus optional conservative HTML/SVG and parsed JSON transforms. -- Keep route cache overrides tri-state and add effective route/cache/transform diagnostics. -- Make Passive Backend Health explicit opt-in (default OFF) for ordinary and Docker vhosts, independent from active TCP/HTTP health, with per-error-class policy, separate counters and persisted Docker assignment settings. -- Extend administration UI/OpenAPI and regression coverage for Cold Cache cleanup, transform settings, passive health and cache semantics. -- Keep release/package/UI/OpenAPI/Debian/Grafana/tooling identity at 0.21.0. -- Close the 0.21.0 feature scope for release packaging. Parser-backed JS/CSS minifiers are deferred; compiler/live acceptance still has to be executed on a Rust-enabled release host. - -## 0.20.0 - -- Rebuild **Certificates** into Installed / Renewal / Generate tabs using the same accessible keyboard tab pattern as Settings. -- Keep certificate issuance, PEM import and existing-file registration inline in the Generate tab; successful imports and edits return to Installed. -- Add explicit ACME operation state cards and include the exact private Certbot log directory in failed issuance/renewal messages. -- Expose per-managed-certificate Certbot log directories from the authenticated ACME status endpoint so persisted renewal failures remain actionable after refresh/restart. -- Rework renewal entries into readable cards with provider/environment, last check, next check, expiry and grouped actions. -- Update release/package/UI/OpenAPI/Debian/Grafana/tooling identity to 0.20.0. - -## 0.19.4 - -- Align the Docker-vhost Aliases editor with the standard 38px Docker form controls and remove the inherited `host-chip-field` label margin that created an extra visual line below `Aliases`. -- Preserve backend traffic display semantics: idle renders as `0.0 KiB/s`, and positive rates below 0.1 KiB/s render as `<0.1 KiB/s` in both backend rows and aggregate vhost traffic. -- Add a presentation filter to **Cache -> Domains and effectiveness** with All / Cache enabled / Cache disabled choices, using each vhost's resolved cache settings while leaving aggregate statistics unchanged. -- Update release/package/UI/OpenAPI/Debian/Grafana/tooling identity to 0.19.4. - -## 0.19.3 - -- Simplify the dashboard footer to API latency and dropped-log count, and add a fail-soft built-in release checker using embedded `UPDATE_CHECK.toml` defaults. The checker reads the official Cargo manifest immediately and then hourly, exposes a newer-version link in the footer, and never performs automatic upgrades. -- Allow runtime release-check overrides through `--update-repository-url`, `--update-version-url` and `--update-check-interval-secs` (plus matching `RUST_PROXY_*` environment variables); interval `0` disables checks. -- Fix the remaining oversized Live Logs column checkboxes by excluding checkbox/radio inputs from both broad `.log-tools input` text-field sizing blocks that were overriding the shared 15px checkbox rules. -- Add an account-scoped light/dark administration theme stored in the existing SQLite `admin_preferences` document; dark remains the default and the last authenticated choice is cached locally only to avoid login/dashboard first-paint theme flicker. -- Refactor theme-sensitive UI colors into shared CSS variables and keep checkbox/radio controls out of generic text-input sizing/density rules. -- Rework the Live Logs column picker into a compact scrollbar-free list with standard-size checkboxes; short viewports use two columns instead of an internal scroll area. -- Keep expired-session re-authentication on the exact panel and Settings sub-tab, use the minimal public login document without flashing the authenticated shell, and show embedded login errors inline instead of as global toasts. -- Canonicalize the legacy `/configuration` shortcut to `/settings?tab=manual-config` and keep later Settings tab switches in the URL so F5 restores the exact page. -- Add one shared saving-state helper for settings/security/certificate forms: disable the active submit button, show `Saving…`, restore its translated label, and prevent duplicate submissions while a save is in flight. -- Highlight and focus the actual invalid control for local validation failures in Logging JSON, named listeners, Docker-agent listener selection, TLS policy, ACL profiles, manual JSON and certificate forms. -- Preserve selected certificate/key files after a failed certificate import/save so the administrator can correct the problem without reselecting both files. -- Mark Settings tabs with a subtle unsaved-change dot, including Manual config, and keep dirty values from being overwritten by live config refreshes. -- Update release/package/UI/OpenAPI/Debian/Grafana/tooling identity to 0.19.3. - -## 0.19.2 - -- Restore the vhost local-listener risk control for administration backends when the administration socket is startup-locked or otherwise reported only by live status; treat `localhost`, `127.0.0.0/8` and `::1` as equivalent loopback forms for the same administration port. -- Make **Discard changes** reload the persisted vhost being edited instead of replacing the editor with a new blank/default vhost. -- Add descriptive HTTP reason phrases to the default-response status selectors and headings. -- Add a temporary, continuously updated 4096-entry Live-log buffer that resets when the log window closes. -- Align inline checkbox labels and compact Docker-vhost alias controls. -- Stop backend traffic EWMA tails on an idle sampling window, show idle consistently as `0.0 KiB/s`, and render positive sub-0.1 KiB/s traffic as `<0.1 KiB/s` instead of a rounded zero. -- Make the Live Logs column picker substantially smaller with subtle 11px checkboxes; let the menu escape the log-dialog clipping boundary and open upward on short viewports. -- Default newly created vhosts to the standard HTTP `:80` listener when available; HTTPS listeners are selected explicitly. -- Left-align the Settings -> Docker agents save action to match the other settings forms. -- Suppress dashboard/login first-paint flicker and preserve the active Settings sub-tab across F5, including a re-login redirect back to `/settings?tab=...` and `/docker-vhosts`. -- Replace native browser confirmations with application-styled confirmation dialogs and add panel-area context to API error messages. -- Present login failures as an inline styled error below the form, including invalid-credential responses. -- Install a login-shell PATH profile for the managed `current` release directory on first install; updates check and warn about the profile without changing it. -- Enforce `no-store` and suppress cache diagnostics/validators for local placeholder/static/default pages. -- Allow vhost-ID renames with duplicate protection, confirmation and an explicit runtime-identity warning. -- Update source, UI, OpenAPI, regressions and package identity to 0.19.2. - -## 0.19.1 - -- Fix Default responses section saves by passing the normal `{expected_revision, value}` payload to `/api/config/pages` and add server-side `pages` section handling/regression coverage. -- Add frontend scheme to the primary cache hash so otherwise identical HTTP and HTTPS requests cannot reuse one object. -- Retain 4096 completed access-log entries while keeping the 256-entry live window; add one-shot on-demand history loading and document the API limit. -- Render backend traffic with KiB/s as the minimum unit, simplify the Live Logs column picker, align generic checkboxes and move Docker-vhost Advanced switches beside Backend protocol on desktop; remove the redundant alias help line. -- Remove stale simulated dashboard screenshots, retain only two explicitly labelled legacy references and add a documentation index for historical/version-labelled material. -- Update source, UI, OpenAPI, regressions and package identity to 0.19.1. - -## 0.19.0 - -- Persist per-administrator Live Logs column visibility in SQLite schema v4 without changing the revisioned proxy configuration; JSON export remains complete regardless of visible columns. -- Keep the Live Logs column selector visually consistent with the existing compact `check vh-check` controls instead of rendering oversized standalone checkbox widgets. -- Add authenticated `GET/PUT /api/preferences` endpoints for dashboard-only account preferences, protected by the existing session/CSRF control-plane guards. -- Remove the duplicate version badge from the top-right dashboard toolbar while keeping the footer release identity. -- Retain the full disabled-verification legacy X.509 fix and expanded upstream connector diagnostics introduced during the 0.18.8 follow-up. -- Update release/package identity to 0.19.0. - -## 0.18.8 - -- Make backend `tls_verify=false` a complete certificate-authentication bypass instead of delegating TLS CertificateVerify to webpki. This restores compatibility with legacy appliance certificates such as X.509 v1 while keeping TLS encryption and all backend address/DNS/loop protections in force; `tls_verify=true` is unchanged. -- Expand upstream connection diagnostics with the full nested Hyper/rustls/io error chain, backend/TLS context, 2 KiB UTF-8-safe detail storage and warning-level journal entries for connect failures. -- Replace Docker-vhost alias comma input with hostname-style chips for add/remove/paste workflows. -- Add per-Docker-vhost HTTP-to-HTTPS redirect, WebSocket and cache controls; new compatible assignments default HTTP-to-HTTPS redirect on while legacy assignments retain their prior redirect-off behavior until explicitly saved. -- Align Docker-vhost boolean controls with the application's standard checkbox classes and keep checkbox inputs from inheriting full-width text-input sizing. -- Add a Docker-agent default ACL-profile selector. Generated Docker vhosts inherit it unless the application selects its own ACL profile; existing per-app inline ACL configuration is unchanged. -- Remove the compression-codec negotiation explainer from the Overview dashboard. -- Update release/package identity to 0.18.8. - -## 0.18.7 - -- Collapse **Response time history** by default on the Overview dashboard while keeping live sampling active. -- Restyle per-interface RX/TX histories to use the same chart geometry and visual treatment as the aggregate RX/TX history, without nested panel backgrounds. -- Align expanded Docker-vhost routing and ACL controls with uniform field blocks; Preserve Host and per-app ACL switches now occupy the same label/control rhythm as neighboring inputs, and ACL selects no longer stretch to textarea height. -- Update release/package identity to 0.18.7. - -## 0.18.6 - -- Add bounded per-interface RX/TX diagnostics from `/proc/net/dev` to the administration status payload and render separate live interface histories without adding per-interface Prometheus labels. -- Strengthen compression negotiation regressions with explicit `q=` preference cases for gzip, Brotli, Zstd and deflate; clarify that dashboard counters track the codec actually selected per response. -- Align Docker-agent settings inputs/actions, keep Enabled/status/remove controls on one stable row, and add spacing between listener selection and the Add Docker agent action. -- Update release/package identity to 0.18.6. - -## 0.18.5 - -- Make compression accounting follow an explicit RustProxy-applied codec marker instead of re-reading `Content-Encoding`; add independent gzip/br/zstd/deflate counter regression coverage. -- Add an explicit HTTP/1.1 request counter and render total + HTTP/1.1 + HTTP/2 + HTTP/3 request-rate series on the Overview chart. -- Add an Open app action to published Docker vhosts, using the assigned frontend listener and expanded proxied hostname. -- Label Docker-generated pools in the Backends view with their primary proxied domain. -- Remove the global Open proxy shortcut from the page header. -- Make Compact density materially tighter than Dense across cards, controls, tables, Docker rows and charts. -- Default vhost/Docker proxy timeout controls to explicit 10/120/60/60-second connect/header/read/send limits instead of blank inherited values. -- Replace free-form Docker-agent listener-ID entry with the configured named-listener selector; move Enabled left of the centered connection-status badge. -- Update release/package identity to 0.18.5. - -## 0.18.4 - -- Default backend health checks to TCP for newly created or otherwise defaulted ordinary and Docker vhosts; explicit existing HTTP/off health modes are preserved. -- Replace the dense Docker-vhost service editor with summary-first rows. Publish state, service identity, backend runtime health and Hide/Show stay visible; routing, ACL, timeouts and health tuning are expanded only for the selected service. -- Collapse secondary Docker-agent inventory metadata behind Agent details while keeping host, service count, last observation and base domain immediately visible. -- Rework Settings -> Security into focused account/session and host-policy cards with on-demand password and policy editors. -- Move Administration audit into a dedicated modal and stop fetching audit entries merely by opening the Security settings tab. -- Update release/package identity and EN/PL UI copy to 0.18.4. - -## 0.18.3 - -- Add Docker-vhost search plus persistent per-browser Hide/Show Hidden view controls, and allow each Docker agent to select a configured frontend certificate inherited by its generated vhosts. -- Keep Docker inventory polling driven by each agent's configured 5..3600 second interval; the 5-second dashboard refresh reads cached observations only. -- Move redirect HTTP/2 and HTTP/3 toggles below listener selection and keep Cache `Updated` + `Live` together at the right side of the toolbar. -- Remove the unused `config::yes` helper warning. -- Add an Open-in-browser action beside each vhost Enabled state; wildcard-only hosts open the wildcard base domain, while mixed host lists prefer the first non-wildcard name. -- Restore the documented HTTP/1.1 backend default for newly created ordinary and Docker vhosts: preserve the public `Host`; existing explicit backend settings are not rewritten. -- Write HTTP/1 upstream header names in conventional title case for compatibility with legacy/custom origins that incorrectly treat header names as case-sensitive; HTTP/2 is unaffected. -- Add bounded `upstream_error` diagnostics to access logs/live logs so TCP/TLS/HTTP-client failures that previously appeared only as 503 with no upstream status can be distinguished. -- Restrict local/error response selectors to supported error statuses; application-style 404 hosts offer only bundled 401/403/404/429/503/504 templates, including a new dedicated 404 template. -- Make Docker Publish/Unpublish an immediate per-service configuration action, add per-service Apply, and replace unconditional UI rerendering with an operator-controlled Live view that reads only RustProxy's in-memory state. -- Add per-agent Docker inventory polling intervals (5..3600 seconds), while manual refresh still polls immediately. -- Add reusable ACL-profile plus inline IP/CIDR ACL controls per Docker application, and protect referenced profiles from deletion. -- Add per-vhost proxy connect/response-header/read/send timeout controls for ordinary and Docker-backed vhosts. -- Print the Docker-agent bearer token after installation alongside the certificate fingerprint. -- Improve 404 Hosts response UX with supported error-status selectors, application-style built-in status pages, cache-diagnostic suppression and protocol toggles beside listener selection. -- Add optional per-vhost JSONL access-log files under a configurable directory while retaining the combined-file mode, Logstash and Loki sinks. -- Lock HTTP/HTTPS/admin listener addresses that are supplied explicitly by systemd/CLI/environment so the control plane cannot remove, disable or rewrite process-owned binds; listeners not supplied at startup remain editable. -- Remove the separate remote-admin risk-acknowledgement requirement; plain remote administration now requires only `--allow-remote-admin`, while legacy `--accept-remote-admin-risk` is accepted as a hidden no-op so older systemd units still start. -- Tighten Compact/Dense dashboard density, rename the Shared RAM cache surface to **Cache**, expose redirect targets directly on cards, and keep 404 Hosts aligned with the application error-code set. -- Clarify that TLS Policy is inherited globally and show currently effective cipher suites alongside all available rustls suites. -- Treat ACME-issued certificates as managed/read-only in the custom-file editor, require certificates to be unassigned (including the administration panel/default binding) before deletion, and default new ACME issuance to the production CA environment. -- Make classic HTTP/1.1 Upgrade the default WebSocket path; RFC 9220 Extended CONNECT over HTTP/3 is now an explicit experimental per-vhost opt-in and is advertised only on QUIC listeners with an opted-in vhost. -- Split Docker discovery/vhost observability into dedicated `rust_proxy_docker_vhost_*` and `rust_proxy_docker_backend_*` Prometheus metric families; Docker pools no longer appear in ordinary vhost/backend series. -- Add a separate live **Docker vhosts** Prometheus collector group, enabled by default, with configured agent/assignment/domain gauges plus Docker vhost traffic/status/duration and full backend health/probe/runtime metrics. -- Extend Prometheus/OpenAPI/UI documentation and regression coverage for the Docker metric boundary. -- Extend the bundled Grafana operations dashboard with a dedicated Docker-vhost section and per-agent/service filters using only the separate Docker metric families. - -## 0.18.2 - -- Secure Docker-agent transport by default with a persistent self-signed ECDSA certificate and SHA-256 certificate pinning in RustProxy; public-CA HTTPS remains available without a pin and plain HTTP is compatibility-only. -- Add certificate lifecycle helpers, hardened systemd state storage, installer fingerprint output and TLS regression tests for the standalone Docker agent. -- Enable per-Docker-vhost HTTP/TCP backend health checks using the same bounded backend-pool health machinery as ordinary vhosts, with live checking/up/down, latency, probe, request, active and error diagnostics in Docker vhosts. -- Rework Docker-agent and Docker-vhost UX around connection security, last observation, detected addresses, route controls and collapsible health-check tuning while retaining per-agent domains, aliases and per-service IP selection. - -## 0.18.1 - -- Extend the Docker agent inventory with all usable host addresses (interfaces, routed address, public lookup and optional advertised address) and preserve every published binding per service. -- Add per-service Docker backend-IP selection, per-agent default backend IP, arbitrary FQDNs and multiple aliases while retaining short-name expansion below each agent's own base domain. -- Support many Docker agents with independent domains such as `docker1.lab.local` and `docker2.lab.local`, plus operator-controlled custom domains. -- Remove all special `X-Rust-Proxy-Revision` response-header behavior; the name is no longer inserted, stripped, protected or accepted as a legacy diagnostic setting. -- Keep cache-Live placement, production password recovery and Settings navigation improvements from 0.18.0. - -## 0.18.0 - -- Add a standalone Go Docker discovery agent with a hardened systemd unit, token authentication, published TCP-port inventory and host/public-IP detection. -- Add per-agent Docker configuration, in-memory observations and dynamic Docker vhosts with operator-controlled service/subdomain assignments. -- Add Docker vhost/admin UI and OpenAPI endpoints for cached observations and explicit refresh. -- Move the cache Live toggle to the action-toolbar edge. -- Remove configurable `X-Rust-Proxy-Revision` response exposure and always strip the header. -- Use the installed `rust-proxy admin reset-password` command in production recovery guidance. -- Update EN/PL strings, documentation, release checks and package identity to 0.18.0. - -## 0.17.0 - -- Add Debian packaging assets, a hardened systemd unit, `rustproxy` service user/state/log directories, three-day maximum-gzip log rotation, local `.deb` build tooling and signed APT-repository generation suitable for `rustproxy.linuxiarz.pl`. -- Expand Prometheus coverage across all current bounded process/OS diagnostics plus GeoIP, certificate renewal, backend health-probe detail, stream, logging and frontend inventory metrics; add explicit token-free scraping for trusted actual-peer CIDRs. -- Add a Grafana operations dashboard under `grafana/`. -- Add dedicated listener/certificate-aware local-response / 404 hosts with built-in placeholder, empty/status-only and custom-HTML modes. -- Keep the default `RustProxy is running` placeholder uncacheable and intentionally free of optional proxy/cache/backend/diagnostic/security/compression headers. -- Reuse vhost-style hostname chips for Redirect source domains. -- Update OpenAPI, EN/PL UI, release checks and package identity to 0.17.0. - -## 0.16.1 - -- Remove internal configuration revision numbers from successful save feedback in dashboard editors/manual configuration while retaining revision-based CAS and stale-write protection internally. -- Update source, UI, OpenAPI, release tooling and packaging identity to 0.16.1. - -## 0.16.0 - -- Decouple Custom locations from the main vhost backend fallback. Unmatched paths now use the main pool even when custom rules exist; untouched legacy synthetic root routes are normalized automatically. -- Fix same-process public administration login by allowing `rustproxy_session` across only a guarded HTTPS self-admin backend that directly targets the configured administration listener and whose public host is explicitly allowed. -- Make RFC 9220 bootstrap wait for the backend HTTP/1.1 upgrade before downstream H3 success, and extend access logging with transport/WebSocket phase/error/reset/upstream status diagnostics. -- Add cache transient-memory gauges and procfs RSS/data/swap breakdowns to the dashboard and Prometheus. Existing allocator trimming/release behavior is unchanged; no new transient-memory throttle is introduced. -- Reorganize the Vhost editor around clearer backend cards, Authentication subcards and compact protocol controls. New vhosts select standard HTTP :80 plus HTTPS :443 together when available; Redirects reuse the same listener selector; disabled vhost cache hides its subordinate controls and new vhost drafts default cache to off. -- Show the local-listener risk acceptance only when the entered backend URL directly targets the configured administration listener. Add protected opt-in `X-RustProxy-Backend` and `X-RustProxy-Backend-Time` response diagnostics for selected member identity and origin response-header latency. -- Bump source, UI, OpenAPI and packaging identity to 0.16.0. - -## 0.15.1 - -- Fix Custom locations EN/PL translation loading by keeping all UI strings inside the language-pack `translations` map and add a regression check that rejects stray top-level translation keys. -- Make visual header rules easier to edit with numbered cards, clear field grouping, empty-state guidance and localized help. -- Add a live per-location behavior preview for matching, action, rewrite, cache, ACL and header rules, and reorganize the inline documentation with common examples. -- Add one live three-series HIT/MISS/BYPASS activity chart to the RAM cache card and CPU/RSS history charts to the Proxy process card. -- Extend offline source/release checks for the new location UX and dashboard charts; package identity is 0.15.1. - -## 0.15.0 - -- Expand vhost Custom locations with Nginx-style exact/prefix/`^~`/regex/regex-insensitive matching, upstream path preserve/strip/replace/regex rewrites and backend-free custom responses. -- Replace the location header JSON-only workflow with a visual request/response header builder, keep Advanced JSON as an escape hatch, add in-panel documentation and reorganize the location editor into compact functional sections. -- Add `nagios/` API checks for administration response time, unavailable/checking backends, backend latency and dropped logging/cold-cache work, with reusable protected session cookies and Nagios performance data. -- Move per-vhost `Server-Timing` and verified Cloudflare client-IP diagnostics into the main frontend/details configuration. -- Add compact dashboard history charts for RAM-cache residency, network RX/TX and mean response latency, plus administration API RTT and low-priority queue/drop information in the footer. -- Add independent cold-cache age policy controls: demote by time since last access, by total hot-tier residency, by LRU pressure and on graceful shutdown. -- Add verified effective client/source IP to live logs and logging field mappings; early responses still record at least the transport peer. -- Remove the runtime legacy renewal-observation JSON importer. Stopped source upgrades remove the obsolete sidecar as the final upgrade step without importing it or retaining a second legacy copy; SQLite remains the only live renewal-observation store. -- Normalize repeated literal path separators for routing/ACL decisions, matching Nginx `merge_slashes` behavior, while preserving the original request target to the backend when no rewrite is configured. URLs such as `/static/js//events.js?v=...` are therefore accepted without weakening encoded-separator/dot-segment protections. -- Bump source, panel, OpenAPI, upgrade tooling and package identity to 0.15.0 and extend offline regression contracts for the new controls. - -## 0.14.0 - -- Fix administration reverse-proxy Host/Origin handling: explicit DNS allowlist entries describe the external authority instead of the private admin socket port; direct IP/localhost access remains bound to the actual admin port. -- Trust forwarded administration host/scheme only from loopback or configured trusted proxy peers, use the external scheme for Origin/CSRF checks, and set/clear Secure session cookies consistently behind HTTPS termination. -- Implement RFC 9220 WebSocket over HTTP/3 Extended CONNECT, including `SETTINGS_ENABLE_CONNECT_PROTOCOL`, `:protocol=websocket` validation, HTTP/1.1 backend Upgrade translation, downstream 200 bootstrap, bidirectional H3 DATA transport and FIN/reset cancellation handling. -- Replace the separate `h3`/`h3-quinn` pair with `hpx-h3 2.5.19` and its integrated Quinn feature to expose the RFC 9220 protocol extension through the HTTP/3 stack. -- Rename dashboard backend, health, security, error-page and recommended-preset labels in English and Polish. -- Update release/static checks, HTTP/3 documentation and package identity. The source contains no `certificates.json` certificate metadata path. - -## 0.13.2 - -- Fix authenticated dashboard refresh/back-forward session restoration so valid cookies are honored on every panel document route instead of briefly rendering the login shell. -- Make managed software updates force a bounded hot-to-cold cache handoff whenever the cold tier is enabled, independent of the ordinary `cold_on_shutdown` preference, and extend the managed systemd stop budget. -- Persist non-secret automatic-certificate-renewal observations in SQLite schema v3, preload them before administration listeners are exposed, and import the older `certs/renewals.json` sidecar once for upgrade continuity. -- Move the raw full-document editor from primary navigation into **Settings -> Manual config** while retaining `/configuration` as a compatibility browser route. -- Update OpenAPI, EN/PL help, cache/renewal/API documentation, migration/backup checks and release identity for 0.13.2. - -## 0.13.1 - -- Make administrator browser sessions configurable from 7 to 30 days (30 days by default), replacing the previous short browser lifetime/idle behavior while preserving explicit logout/password/revoke invalidation. -- Refresh authenticated cookies only to the remaining absolute server-side expiry so session persistence cannot become an unbounded sliding session. -- Add per-backend `accept_local_listener_risk` with a dashboard confirmation and validation path for deliberate local-listener targets such as `127.0.0.1:9090`; `allow_private` and special-address protections remain independent. -- Update OpenAPI, transport/security/admin-session documentation, EN/PL strings and release regressions for 0.13.1. - -## 0.13.0 - -- Add first-class redirect hosts with dedicated dashboard navigation, named listeners/certificates, exact/prefix and scheme matching, URL/host/path targets, 301/302/303/307/308, path/query policies and redirect cache controls. -- Resolve redirect traffic locally before proxy routes while preserving listener ownership and global host ACLs. -- Change omitted backend `tls_verify` to default false and update UI/API/documentation accordingly. -- Compact TLS certificate cards: show DNS SAN coverage as the same policy-style badges used on vhosts, collapse bindings/files/assigned-vhost details, and inspect older/manual public PEM files when persisted coverage metadata is missing. -- Extend OpenAPI, English/Polish translations, regressions and release tooling for 0.13.0. - -## 0.12.1 - -- Add revisioned per-section configuration PUT endpoints and move ordinary dashboard forms to them, retaining full-document updates only where multiple sections must publish atomically. -- Decouple cache compatibility from the SQLite configuration revision so logging, administration, TLS inventory, metrics and other unrelated saves do not purge hot/cold objects. -- Add explicit dashboard subpage routes plus History API navigation so browser refresh preserves the selected panel. -- Add independent cold-tier policies for idle objects, LRU evictions and graceful shutdown, reduce the cold-write body queue, flush remaining hot objects before process exit, and trim released allocator memory on glibc/Linux. -- Expose certificate DNS SubjectAltName coverage separately from active SNI bindings for uploaded and ACME-managed certificates. -- Preserve secure-by-default backend TLS verification when omitted; keep the explicit debug-origin verification bypass and improve corrective validation/help text. -- Extend regression/OpenAPI/release checks and bump source/package identity to 0.12.1. - -## 0.12.0 - -- Move raw TCP/UDP stream management from Settings to a dedicated **Streams** main-navigation page directly below Vhosts. -- Add vhost-style searchable stream cards and a modal editor with Details, Listener & upstream, and Limits tabs plus an explicit **Add TCP/UDP listener** action. -- Add fixed-cardinality runtime telemetry per active stream: connections/sessions, completed sessions, active sessions, rejects, errors, RX/TX bytes and UDP packet counters. -- Add per-stream live RX/TX rate charts in the dashboard while keeping desired stream changes visibly pending until the process restarts and the listener is actually active. -- Change named HTTP/HTTPS listener bind-address editing from a textarea to chip-style addresses matching the Vhost hostname interaction, preserving existing multi-address validation. -- Keep stream forwarding as raw Layer 4 with no HTTP vhost, ACL, cache, headers, compression or authentication envelope. -- Extend authenticated-asset and offline release/source regressions for the new Streams page and telemetry contracts; bump Cargo, panel, OpenAPI and upgrade/package identity to 0.12.0. -- Extend the Debian 13+ one-line bootstrap with `auto`/`install`/`update` modes; managed updates build the new source as an unprivileged user and delegate to the existing backup/health-check/rollback update path while preserving saved listeners and private data. - -## 0.11.5 - -- Accept percent-encoded spaces (`%20`) in otherwise unambiguous request paths while preserving rejection of encoded separators, double-decoding hazards, controls and dot-segment ambiguity. -- Avoid the `h3-quinn 0.0.10` `stop_sending()` panic path when an HTTP/3 request body is dropped while a receive read is pending. -- Add raw layer-4 TCP/UDP stream hosts on dedicated listeners with bounded TCP connections / UDP client sessions and restart-bound configuration in the admin panel. -- Add a Debian 13+ source bootstrap that installs build dependencies and Rust, clones/builds the repository and installs the systemd service with the explicitly requested public admin profile. - -- Match Varnish built-in request-method behavior for the shared cache: `GET`/`HEAD` participate in lookup while successful POST/PUT/PATCH/DELETE/OPTIONS/TRACE requests no longer trigger an implicit global purge. -- Replace the conservative per-request-header/backend-member cache identity with a Varnish-style Host + URL primary identity, isolated by RustProxy vhost/config scope, and use response `Vary` as the secondary representation selector. -- Share cached objects across load-balanced members and across GET/HEAD, reducing avoidable MISSes while retaining Cookie/Authorization pass behavior and response privacy guards. -- Preserve `X-Cache-Resident-Age` across backend 304 revalidation and hot/cold tier movement. Cold metadata format 2 stores variant identity and resident age explicitly. -- Add cache regressions for Vary variants/schema changes, Host/URL hashing, explicit ports, HEAD eligibility and resident-age continuity; update release/package/API/UI metadata to 0.11.5. - -## 0.11.4 - -- Fix Force SSL frontend validation and messaging: require an actual non-TLS listener for the incoming redirect, prefer HTTPS port 443 when multiple TLS listeners are selected, and reject only ambiguous non-standard HTTPS target ports. -- Add a visual reusable ACL-profile rule editor with add/remove/action/CIDR rows. Keep bulk paste/import as an advanced path, normalize invisible Unicode whitespace and report malformed rules with their source line. -- When a vhost disables cache, strip and suppress RustProxy `X-Cache`, `X-Cache-Hits`, `Cache-Status`, `X-Cache-Reason` and `X-Cache-Resident-Age` diagnostics for that response. -- Render backend pools belonging to disabled vhosts as muted `Disabled` rows in the main dashboard instead of classifying them as unavailable. -- Add source/offline regression coverage and bump release/package/UI/OpenAPI/update metadata to 0.11.4. Rust compilation/runtime acceptance remains open in this packaging environment. - -## 0.11.3 - -- Add per-vhost control over forwarding the effective client IP to the application. Disabling it removes `Forwarded`, `X-Forwarded-For` and `X-Real-IP` only on the backend request; ACLs, GeoIP, rate limiting and Tinyauth continue to use the real client identity. -- Add a compact Vhost search/filter for vhost IDs, hostnames/wildcards and backend IDs/URLs. -- Fix JSONL access logging under the hardened systemd unit by provisioning `/var/log/rustproxy` as a writable service log directory while keeping `ProtectSystem=strict`. -- Add source/offline regression coverage and bump release/package/UI/OpenAPI/update metadata to 0.11.3. Rust compilation/runtime acceptance remains open in this packaging environment. - -## 0.11.2 - -- Fix administration HTTPS access negotiated over HTTP/2. The admin guard now derives the effective authority from H2/H3 `:authority` (via the request URI) when no legacy `Host` header is present. -- Preserve strict HTTP/1.x Host handling and reject conflicting HTTP/2 `Host` versus `:authority` values to avoid ambiguous authority validation. -- Add per-vhost Force SSL beside frontend selection. Cleartext requests receive a non-cacheable HTTP 308 to the same hostname/path/query on the vhost's unique selected TLS port; ambiguous multi-port TLS selections are rejected. -- Map NPM `ssl_forced` (and migrator `--force-https`) to RustProxy `force_ssl`, retaining HTTP+HTTPS listener bindings when available instead of importing forced hosts as TLS-only. -- Add unit/integration regression coverage for HTTP/2 administration authority, `/healthz`, Force SSL redirect construction/validation and NPM `ssl_forced`; bump release/package/UI/OpenAPI/update metadata to 0.11.2. Rust compilation/runtime acceptance remains open in this packaging environment. - -## 0.11.1 - -- Fix release update health checks when the administration panel uses persisted HTTPS or a persisted bind different from the install-time CLI address. The check now resolves the active admin listener/scheme from `config.sqlite3` and honors the admin Host allowlist. -- Add a standard-library-only Nginx Proxy Manager SQLite migrator. It reads `proxy_host`, creates RustProxy vhosts/backends, preserves wildcard names/WebSocket/enabled/H2 intent, uses the RustProxy default certificate for imported TLS hosts unless a named certificate is selected, and keeps unsupported NPM policy/custom-config items as explicit review warnings. -- Make NPM migration dry-run by default, emit `npm-migration-plan.json`, detect hostname conflicts, split source records above RustProxy's 16-host limit and use revisioned authenticated API writes only with explicit `--apply`. -- Add offline regression coverage and bump release/package/UI/OpenAPI/update metadata to 0.11.1. Rust compilation/runtime acceptance remains open in this packaging environment. - -## 0.11.0 - -- Add reusable ACL Profiles with optional IP/CIDR ACL and country allow/block rules. Vhost profile, inline vhost ACL and route ACL enforcement remains cumulative. -- Add an ACL Profiles navigation page and per-vhost profile selection without removing individual vhost policy controls. -- Add chip-style hostname editing and validated leading `*.` wildcard domains. Exact hosts win before the longest matching wildcard on the same listener. -- Add compact browser-session request-rate history charts to vhost cards, with progressive compaction of older samples. -- Add the built-in HTTP 401 page and classify normal WebSocket tunnel EOF/reset/abort/broken-pipe termination after a successful 101 upgrade as `websocket_closed` rather than a body/proxy error. -- Redirect plaintext requests received on the HTTPS administration socket to HTTPS while keeping application content unavailable over plaintext. Permit valid DNS Host values when the HTTPS admin hostname allowlist is empty; explicit allowlists remain strict. -- Extend named listeners to up to 16 explicit local addresses under one protocol policy. Multiple-address configurations reject unspecified `0.0.0.0`/`::` binds. -- Align the Logging field-builder dialog with the Vhost configuration modal and reposition the Configure fields action next to the format preset. -- Make source ZIP generation deterministic and verify byte-identical archives in release smoke tests. -- Bump Cargo, panel, OpenAPI, installer/update metadata and package identity to 0.11.0. Rust compilation/runtime acceptance remains open in this packaging environment. - -## 0.10.0 - -- Add an optional persistent cold cache tier under the private data directory. Hot LRU/idle objects demote through a bounded asynchronous queue; fresh cold hits are SHA-256 checked and promoted back into the bounded RAM tier. -- Add hot/cold object, byte, promotion, demotion, cold-hit, eviction/error and queue-drop observability to the Cache API, panel and Prometheus output. -- Add cold-tier path, byte/object budgets and idle demotion settings; purge/config publication invalidates both tiers and startup rebuilds the same-revision cold index. -- Document browser hard-reload revalidation behavior and add translations for the exact `request_no_cache`, `request_max_age`, `request_min_fresh` and `request_pragma_no_cache` reasons. -- Add structured-log Standard, Nginx-style, ECS-style and Minimal presets plus a visual field picker/renamer with JSON preview; preserve advanced raw JSON mappings. -- Reuse the shared application card component for Logging sinks and remove release-specific fieldset border styling. -- Add baseline multi-stage Docker/OCI packaging with an unprivileged runtime user and persistent data volume. Docker discovery remains deferred. -- Bump Cargo, panel, OpenAPI, installer/update metadata and package identity to 0.10.0. Rust compilation/runtime acceptance remains open in this packaging environment. - -## 0.9.0 - -- Redesign the vhost list as readable cards with listener/protocol/TLS/cache identity and live pool health, request, active, error, latency and traffic metrics. -- Add bounded asynchronous structured access-log sinks for JSONL files, Logstash TCP and Loki HTTP(S), with configurable JSON field mapping/static fields and sink delivery/drop/error counters. -- Expand cache object inspection with body bytes, resident bytes and metadata overhead; add optional `X-Cache-Resident-Age`. -- Add administration HTTPS with either the application-managed default certificate or a selected inventory certificate. Persisted non-loopback administration binds are accepted when HTTPS is enabled. -- Keep loopback HTTP as the default and retain the explicit legacy risk gate for plain remote HTTP. Secure session cookies are marked `Secure` on HTTPS administration. -- Add global TLS 1.2/1.3 and rustls cipher-suite policy plus per-vhost TCP TLS overrides. HTTP/3 vhosts must use the global TLS 1.3 policy. -- Add dedicated Logging navigation and full-width Settings tabs with new Administration and TLS policy pages. -- Complete roadmap items for remote HTTPS administration and `X-Cache-Resident-Age`; bump release identity, OpenAPI and packaging metadata to 0.9.0. -- Rust compilation/runtime acceptance remains unverified in this packaging environment. - -## 0.8.1 - -- Enable the shared RAM cache by default. -- Replace blanket conditional-request bypass with local conditional HITs for fresh `If-None-Match` / `If-Modified-Since` requests. -- Add validator-based backend revalidation: request `no-cache`, freshness constraints and legacy `Pragma: no-cache` can revalidate a stored representation; backend 304 refreshes metadata and reuses the cached body. -- Keep request `no-store` as a hard bypass. Allow response `Cache-Control: no-cache` with ETag/Last-Modified to be stored only for mandatory validation; `no-store` and `private` still forbid shared storage. -- Retain expired validator-bearing objects for a bounded one-hour revalidation keep window without stale delivery. -- Raise the configurable object ceiling from 8 MiB to 1 GiB, still bounded by the global 1 GiB resident budget. -- Grow fill buffers incrementally and serialize very large fills through the existing fill semaphore to avoid preallocating the configured object ceiling. -- Exclude cache-control/Pragma and conditional/precondition headers from representation-key hashing because they govern reuse rather than representation identity. -- Add cache diagnostics and regressions for conditional HIT, revalidation, no-cache/no-store semantics and the 1 GiB configuration ceiling. -- Bump Cargo, panel, OpenAPI, installer/update metadata and package identity to 0.8.1. - -## 0.8.0 - -- Global static-cache defaults now include common image, stylesheet/script, font, media and download extensions, with request Cookie ignored for matching assets by default. The global and per-vhost controls are visible in the cache UI; `Set-Cookie`, `private`/`no-store` and `Vary: Cookie` still prevent shared caching. -- HTTP/2 and HTTP/3 are per-vhost frontend opt-outs next to listener selection. Controls are enabled only when a selected named listener supports the protocol; TLS H2 ALPN is narrowed by SNI per vhost. -- Settings -> Listeners uses normal page scrolling instead of an extra nested scrollbar. - -- Add per-vhost request throttling with graphical request/window/burst controls, country-specific limits and country block/allow-only policies. Return built-in HTML 429/403 responses while preserving custom error-page overrides. -- Download the PDDL `sapics/ip-location-db` user-country IPv4/IPv6 data during first installation over HTTPS and verify publisher SHA-256 checksums; expose load status without exposing client data. -- Verify the installed GeoIP database against current publisher SHA-256 files on every managed update and download only stale/missing assets before downtime. -- Make managed updates regenerate the systemd unit and restore it together with code/private data on failure; preserve and report the original failure reason and add a development-only `--reset-config` recovery path for intentionally incompatible saved vhost/settings documents. -- Add an opt-in per-vhost Cloudflare client-IP mode that accepts `CF-Connecting-IP` only when the direct peer belongs to Cloudflare's published edge CIDRs. -- Add Server-Timing response diagnostics. Disabled per-vhost H3 removes `Alt-Svc` and rejects HTTP/3 requests for that vhost. -- Move vhost listener binding exclusively to named listener selection. Listener addresses are configured under Settings; remove the pre-production inline/global listener compatibility importer and reject the old vhost format instead of migrating it. -- Make health-check mode `off` fully disable active probes and passive health classification instead of quietly learning health from production requests. -- Add a new-origin debugging preset for HTTPS-by-IP origins: default pool/private-origin/HTTP1/preserve-host enabled and backend TLS verification disabled for newly created panel backends, with explicit warnings about the certificate-verification risk and mismatched settings. -- Keep per-vhost inherit/override controls for static-cookie cache behavior while the global defaults cover common static extensions. Cookie is ignored only for the cache key/request bypass decision. -- Fix cache `Age` calculation to retain sub-second response delay and apply RFC 9111 corrected received age before adding response delay. -- Bump Cargo, panel, OpenAPI, installer/update metadata and package identity to 0.8.0. -- Rust compilation/runtime acceptance remains unverified in this packaging environment. - -## 0.7.3 - -- Generate the one-time administrator setup token from the release CLI even before - the first server start; retain the rule that it is invalid after setup completes. -- Add explicit remote-panel opt-in for `0.0.0.0`/`::`, requiring both - `--allow-remote-admin`; keep plain-HTTP risk visible. -- Make Linux/systemd install defaults listen on HTTP 80 and HTTPS 443; add - `--no-https` for explicit HTTP-only installation. -- Add deployment and admin-host regressions and bump release/package/UI/schema - identifiers to 0.7.3. -- Hotfix backend HTTPS-by-IP with optional `tls_server_name`: TLS SNI and hostname - verification may use a DNS name while the validated TCP target stays pinned to - the configured backend URL. -- Add an explicit per-backend `tls_verify` switch, secure by default. Administrators - may turn verification off for an HTTPS origin without weakening DNS/address/self-loop - policy. Add matching 503/504 HTML gateway pages while preserving custom-page overrides. -- Rust compilation/runtime acceptance remains unverified in this packaging environment. - -## 0.7.2 - -- Publish a distinct patch release; keep both previous 0.7.1 deliveries historical. -- Remove the build.rs panic on absent SWAGGER_UI_DOWNLOAD_URL. The asset preparer - defaults to pinned Swagger UI 5.32.11 independently of Cargo config discovery. -- Remove the independently downloading utoipa-swagger-ui adapter and embed the - prepared JS/CSS directly, for debug and release. Existing admin guards stay in place. -- Prepare assets with the existing Python 3.10+ tool requirement, now also needed - for direct Cargo builds. Support absolute local tag ZIPs, optional archive SHA-256, - bounded exact-member reads, validated download caching and retained license notices. -- Resolve build input paths from CARGO_MANIFEST_DIR, track local sources and outputs, - and report missing tools/assets explicitly instead of silently changing UI versions. -- Add executable synthetic-archive regression tests and protected license notices. -- Update release identifiers, packaging/update metadata and migration/build notes. -- Rust compilation and actual Swagger rendering remain unverified in this environment. - -## 0.7.1 - -- Add visual request/response header editing to each custom vhost location, with - set/append/remove, validation, isolation and synchronized optional JSON. -- Split anonymous sign-in from the operational document and module/language bundles. - Remove the login tagline; preserve the original brand and compact square style. -- Reduce public health to `status` and auth discovery to `setup_required` only. - Make the public allowlist exact by method and path; private/unknown requests are - denied before body/query extraction, including method mismatches and aliases. -- Keep Origin/Host/CSRF/session guards; reject duplicate CSRF/marker headers and - hide login/setup JSON parser details. Clear UI data across tabs and on revocation. -- Add authenticated `/docs` and OpenAPI with pinned, build-embedded Swagger UI, - same-session authorization, read-only execution by default and CSRF-confirmed writes. -- Replace updater reliance on a public version with local MainPID/executable checks. -- Add source contracts, browser script regressions, actual-router Rust tests and a - read-only anonymous-access probe. Rust/build/vendor-renderer acceptance not completed. - -## 0.7.0 - -- Renew managed ACME profiles automatically with shared worker bounds, jitter, - backoff, private observations and validated CAS hot deployment; expose opt-out, - check-now and dry-run controls. Keep previous certificates active on failure. -- Add install.sh/update.sh for Linux/systemd, root-owned release switching, - stopped full-data backups and code/data rollback on failed startup/version checks. -- Add per-vhost Basic Auth with Argon2id-only storage, optional verified upstream - credentials and cumulative graphical IP ACLs. Keep protected content out of cache. -- Add WebSocket toggles and graphical custom path locations, including explicit - exclusion of dedicated path origins from normal pool/sticky selections. -- Replace the sample welcome page with neutral information, make the protocol - fixture opt-in, unify square modal and Settings-tab styling, preserve branding. -- Add renewal/deployment helper tests and Rust/browser regressions. Rust compilation, - live CA renewal and real systemd deployment remain unverified in this environment. - - -## 0.6.0 - -- Replace separate legacy routes with vhosts and a one-way, validated compatibility importer; preserve path-pinned origins, ACLs and existing listener scope. -- Add HTTP-01 webroot domain validation, Cloudflare DNS-01 and OVH DNS-01 alongside RFC2136 using private Certbot workers. -- Unify certificate creation/import in a provider-picker modal; record optional issuer metadata in the library and keep secrets outside exported configuration. -- Add an independently configurable Prometheus endpoint with actual-peer CIDRs, optional environment-sourced bearer token, bounded scrapes and collector switches. -- Export application/admin/listener/vhost/backend/cache/system data and full-exchange vhost histograms with bounded configured labels. -- Split Settings into functional switching cards; preserve dirty values, keyboard access and translation discovery; remove Security and legacy routing sidebar entries. -- Add offline provider/browser regression tests and Rust tests for migration, ACME challenge isolation and scrape authorization. Rust compilation/runtime and actual CA/provider integrations remain unverified. -- Preserve the original brand assets. Automatic certificate renewal, Cloudflare Tunnel and Docker discovery remain deferred. - -## 0.5.0 - -- Add a compact dedicated cache panel with vhost/domain counters, object pagination, - TTL/Age/hits, memory/fill diagnostics and authenticated selective purge. -- Fix reserved administrator-cookie ordering and empty terminating DATA frames; - emit fixed cache decision reasons without exposing request secrets. -- Replace special X-Mock-Origin hiding with the explicit global/vhost revision - switch; ordinary response rules still handle arbitrary removable headers. -- Default HTTP/3 on TLS bindings, retain explicit null opt-outs, and explain - overlapping sockets with ownership and concrete protocol/limit differences. -- Return 503 for upstream connection/unavailability, 502 for invalid responses - and 504 for header timeouts; generated errors use no-store. -- Add basic asynchronous Certbot DNS-01/RFC2136 issuance, staging-first consent, - private TSIG/account profiles, output validation and explicit library import. - No automatic renewal scheduler or real CA validation is claimed. -- Add offline helper and UI checks plus Rust regression source. Rust/Cargo remain - unavailable in the packaging environment; compilation/runtime unverified. - - -## 0.4.0 - -- Rebuild standard Forwarded and X-Forwarded metadata at an explicit trusted-proxy boundary; use verified client IP for ACL/affinity, never for destination routing. -- Add per-vhost Tinyauth subrequests, identity header allowlists, safe same-login-origin redirects and fail-closed checks before origin/static/cache/WebSocket use. -- Move certificate upload into an independent inventory; assign certificates by ID per vhost and expose metadata-only inventory reads. ACME is not implemented. -- Add named multiple-IP listeners with descriptive scope, opt-in legacy routes, reusable vhost references and actual/desired plans. -- Add Quinn/h3-based HTTP/3 UDP transport, TLS 1.3/SNI, Alt-Svc, per-endpoint connection and per-connection stream/idle limits, shared proxy policies and bounded shutdown. Preserve TCP HTTP/2 plus HTTP/1.1. -- Hide request-ID/revision headers by default; add individual overrides and optional mock-origin stripping. Apply effective Server policy across all response paths, including extra sockets. -- Partition cache by actual frontend IP/port and safely probe unknown-length empty requests; preserve nonempty body frames instead of discarding them. -- Raise toast above active dialogs without making it inert under the modal; retain the logo, compact UI and discovered language packs. -- Add a loopback-only fake-auth development peer and Rust integration sources for forwarding, auth, extra-socket cache, listener ownership and real QUIC/TCP fallback. -- Execute frontend/helper/static checks documented in TEST_REPORT.md. Rust/Cargo are unavailable: no passing compilation, real proxy run or full RFC-conformance claim. - - -## 0.3.0 - -- Split the large status JSON macro into small objects and an explicit feature map; - preserve response fields without increasing Rust's macro recursion limit. -- Add optional per-vhost certificate inventory selection and PEM file upload. - Validate key/certificate pairing and names, write private immutable files, and - activate only through the existing revisioned configuration update. -- Add per-vhost cache overrides and selective invalidation, including stale - in-flight fill cancellation while leaving other host entries intact. -- Add administrator-authored, bounded global/vhost error pages, static vhost and - missing-path responses, direct-IP and unknown-host responses. Retain ACL checks, - HEAD handling and authentication challenges; never execute HTML in the admin UI. -- Move access logs into a separate modal/docked viewer with fixed filters, - vhost identity, pause, scoped search/status/method/cache controls and JSON export. -- Add a compact vhost list, tabbed dialog, live health/latency summaries and visual - per-host response/request headers; keep the original logo and favicon. -- Add density preferences and a Settings shortcut to Security. -- Add a Python-only mock origin with random data, cache/no-store/private/cookie - paths, compression-size payloads, controllable delay/health and error statuses. -- Add Rust regression source for pages, cache scoping, TLS staging and API guards. -- Rust compilation/runtime acceptance remains unverified: toolchain unavailable. - Executed checks and their exact limitations are recorded in TEST_REPORT.md. - - -## 0.2.0 - -- Replace SizeAbove(u16) with a custom u64 predicate; add >64 KiB threshold regressions. -- Add IP/port/TLS-bound virtual hosts with frontend(s), backend pool and path policies. -- Enforce real accepted destination matching; isolate legacy routes from extra sockets. -- Add weighted round-robin, least-connections, least-traffic, adaptive and IP hashing. -- Add bounded TCP/HTTP health checks, separate passive failures, recovery and slow start. -- Add connection keepalive controls, backup members, drain and opaque cookie/IP affinity. -- Preserve compatible observations/tokens across hot edits; recheck changed/re-enabled members. -- Keep leases through full uploads/responses/tunnels; never replay failed requests. -- Add per-vhost TLS-only HSTS, nosniff, frame/referrer/CSP/permissions headers. -- Retain HTTP/1.1 ALPN with HTTP/2 for TLS frontends and HTTPS origins. -- Separate cache identities by selected member; bypass cookie-affine pools. -- Add compact vhost editor, atomic legacy migration, backend observations/API/metrics/log IDs. -- Extend discovered English/Polish translations; preserve original logo and favicon. -- Add Rust regression source and a real-binary vhost/keepalive/health acceptance suite. -- Execute 63 simulated-API UI, 24 SQLite, 21 upgrade, 4 language-mirror, 10 protocol-peer - and 7 backend-fixture checks. None is a passing Rust build or runtime test. -- Rust/Cargo remain unavailable in the packaging environment; compilation/runtime - acceptance and a resolved Cargo.lock remain mandatory before deployment. - -## 0.1.0 - -Development source advancing directly from 0.0.6. Cloudflare is deferred. - -### Added - -- Persisted public listener addresses/ports, 80/443 presets, startup defaults and restart status. -- Bounded streaming shared RAM cache, TTL/Age, LRU, privacy/Vary protections, per-route bypass, optional HIT/MISS headers and audited purge. -- Streaming gzip, Brotli, Zstandard and deflate controls, final Server policy and a global client-response header editor. -- Compact dashboard with system/process/cache diagnostics, log filters and export. -- Compile-time language discovery, JSON packs, metadata/RTL/fallback, and preservation of extra packs during upgrade. -- Example unprivileged low-port service and cache/settings real-binary integration test source. - -### Changed - -- Allow ordinary remote-origin 80/443 while guarding local proxy/admin listener targets. -- Keep original logo/favicon; reduce panel spacing and improve data density. -- Invalidate cache and older fills on config changes, explicit purge and successful unsafe methods. -- Expand source/SQL/upgrade/language/frontend checks; actual execution scope is in docs/TEST_REPORT.md. - -### Verification boundary - -Rust/Cargo and real Rust integration were unavailable. This is a source candidate, -not a successfully compiled or production-certified binary. No lockfile is fabricated. - -## 0.0.6 - -Cumulative development source release. The planned 0.0.3, 0.0.4 and 0.0.5 scopes -are included here, not represented as separately published archives. - -### Fixed - -- Explicitly select `src/auth/mod.rs` to prevent the reported flat/folder module - conflict when obsolete 0.0.1 source remains in an existing checkout. -- Detect mixed source layouts and preserve the known legacy auth file; refuse to - delete unrecognized local changes. -- Support interactive username input for `admin init`. -- Replace managed source directories with rollback on copy failure rather than - encouraging overlay extraction. - -### Added - -- DNS and HTTPS manual backends with per-backend pools, bounded DNS workers, - validated DNS answers, optional CIDR allowlists and additional PEM trust anchors. -- rustls public TLS listener, persistent development default certificate, exact SNI - bindings, local generate/import commands and explicit hot reload. -- Public HTTP/2 over TLS ALPN and HTTP/2/auto backend protocol options. -- Validated HTTP/1.1 WebSocket handshakes and bounded bidirectional tunnels, - including TLS on either side, connection leases and a separate idle timeout. -- Transport metrics, WebSocket lifetime logging, TLS editor and advanced backend - controls in the English/Polish panel. -- Backup of configured/default certificate files alongside the SQLite snapshot. -- Upgrade regression tests, transport configuration tests, real protocol test - scripts and CI steps. Test execution limits are recorded in TEST_REPORT.md. - -### Changed - -- Public TLS is enabled by default by `dev.sh`; direct binary startup keeps it optional. -- Listener ports are protected across HTTP, HTTPS and administration. -- All Markdown documentation is maintained in English. -- The roadmap identifies the combined transport release and upcoming Docker stages. - -## 0.0.2 - -- Introduced RustProxy naming, administrator setup, Argon2id passwords, sessions, - CSRF checks, login throttling, password recovery and credential revocation. -- Added host/route ACLs, generic request/response header policies, administration - audit, SSE dashboard updates and security controls in the bilingual panel. -- Added an additive SQLite migration, offline validation and online database backup. -- Rust build/runtime verification was unavailable for the source package. - -## 0.0.1 - -- Initial manual-backend HTTP/1.1 proxy, streaming and keep-alive. -- SQLite configuration, dynamic host/alias/path routing and local token control plane. -- English/Polish interface, bounded access log, counters, CLI, demo backend and dev.sh. -- Rust build/runtime verification was unavailable for the source package. diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md new file mode 100644 index 0000000..e102119 --- /dev/null +++ b/docs/CONFIGURATION.md @@ -0,0 +1,57 @@ +# Configuration and settings + +Most settings are managed in the authenticated panel. Save changes after validation; use **Configuration history** to compare or restore an earlier revision. Some listeners are process-owned and require restart to change. Configuration updates are not a replacement for encrypted full backups. + +## Configuration map + +| Panel area | Main settings | Operational note | +|---|---|---| +| **Settings → Listeners** | Named HTTP/HTTPS frontends, addresses, HTTP/2, H2C, HTTP/3, transport limits | The selected Vhost must bind the correct listener. Binding changes may require restart. | +| **Settings → Administration** | Dedicated admin address, HTTPS/certificate, accepted hostnames/public origin | Local HTTP by default. Remote admin requires deliberate security configuration. | +| **Vhosts** | Domains, certificates, source, backends, health, load balancing, custom locations | The primary place to publish a service. | +| **Web Server** | Document roots, static delivery, rewrites, PHP-FPM, cache headers | Attach a Web Service to a Vhost as its source. | +| **Access / ACL profiles** | IP/CIDR, DNS/DDNS allow rules, country policy, HTTP methods, rate limits | Access decisions combine across applicable levels. | +| **Settings → Cache** | HOT/COLD budgets, TTL, exclusions, diagnostics, invalidation | Global and Vhost permissions must both allow caching. | +| **Settings → Compression / Transform / Headers** | Response codecs, content modification, headers and security settings | Avoid changing personalized content in a shared cache. | +| **Certificates** | PEM import, managed ACME issuance, renewal and assignment | Issuing a certificate does not automatically publish a Vhost. | +| **Docker / Streams** | Docker agent hosts and published services; TCP/UDP forwarding | Streams bypass HTTP Vhost policy. | +| **Overview / Appearance** | Dashboard cards, presets, layout, charts and theme | Account-specific display preferences do not change proxy behavior. | +| **Logs / Monitoring** | Live/stored logs, error logs, analytics, Prometheus, GeoIP updates | Logs and metrics may need dedicated limits/network permissions. | + +Read the dedicated guide before enabling advanced options: [Vhosts](VHOSTS.md), [Cache](CACHE.md), [Web Server](WEB_SERVER.md), [Monitoring](MONITORING.md), [Security](SECURITY.md). + +## Listener and domain setup + +1. Create or review a named HTTP/HTTPS listener in **Settings → Listeners**. +2. For HTTPS, choose the certificate and protocol capabilities. HTTP/3 is optional and requires UDP access. +3. In a Vhost, set exact domains or supported leading wildcards (`*.example.com`), select listener IDs and certificate. +4. Set the source: HTTP reverse proxy or an existing Web Service. Test DNS, TLS and backend health. +5. Add HTTP → HTTPS (`308`) or www/non-www redirection if needed. Check routing before turning on caching or restrictions. + +Listener bindings set by startup CLI/systemd cannot be removed, disabled or modified through saved configuration. Saved settings may therefore be valid but not active until a service restart. Administration and public frontends use different security boundaries. + +## Backend protocols + +`http1` uses HTTP/1.1. `auto` selects H2/H1 over HTTPS and ordinary HTTP/1.1 over plaintext. The legacy `http2` value retains compatible HTTPS negotiation and H2C for HTTP. Explicit `h2` requires H2/H2C; do not use it with an origin that only supports HTTP/1.1. Backend TLS certificate verification is **off by default** in the source model; enable it deliberately for trusted origin identity. A private origin, loopback origin or origin on a local RustProxy listener also requires an appropriate explicit exception. + +## Access and header policies + +ACLs support IP/CIDR and **allow-only** DNS/DDNS hostname rules, plus reusable profiles and country rules. Host, Vhost and Custom-location checks are cumulative: an allowed location does not undo a host denial. Configure forwarding identity only behind an explicit trusted proxy. Optional Basic Auth or Tinyauth is attached to Vhosts; WebSockets should be enabled only where used. See [Access](ACCESS.md). + +Global response headers, per-Vhost rules and Web Service-specific headers are distinct scopes. Security and proxy-protocol headers may be protected from rewriting. Compression supports eligible response encodings; availability is still constrained by global limits and the requesting client. + +## Cache and transforms + +Caching is opt-in. HOT holds bounded memory entries; COLD is an optional disk tier (off by default). Freshness follows eligible HTTP policy and `Vary`; private/no-store responses and protected requests are not shared. Configuration saves can selectively invalidate affected cached responses; 0.34.0 behavior needs production validation. HTTP BAN/PURGE is **disabled by default** and uses an explicit source-IP allowlist. See [Cache](CACHE.md). + +Response transformations are optional and apply only to configured eligible MIME types and scopes. See [Response delivery](RESPONSE_DELIVERY.md) for codec defaults, transformation limits and header precedence. Content rewriting can affect validators, compression and cache entries; test actual representations after configuration changes. + +## Administration preferences + +Appearance settings, Dashboard Presets and Overview cards belong to the administrator profile. Up to 16 named presets can store card visibility, order, widths, density and sample count. Changing the dashboard does not restart proxy listeners or rewrite routing rules. Source sections under `config` have a revision guard to prevent accidental overwrite by a stale editor. + +## Sample files and safe changes + +Example JSON is in `examples/`. Treat it as a starting point: addresses, certificates, paths, backend health endpoints and service IDs must match your installation. The API uses full validated settings and rejects unknown fields; it is **not** a generic patch store. Use the current authenticated `/docs → API` OpenAPI reference to inspect field schemas. + +For planned changes, export configuration, create an encrypted backup, make one logical change, save, verify real traffic and confirm metrics/logs. Revert via configuration history only when rollback of the config (not filesystem/secrets) is sufficient. diff --git a/docs/CONTAINER.md b/docs/CONTAINER.md index 7229c36..ccea528 100644 --- a/docs/CONTAINER.md +++ b/docs/CONTAINER.md @@ -1,44 +1,38 @@ -# Container / OCI deployment +# Docker / OCI deployment -RustProxy ships a baseline multi-stage `Dockerfile`. It compiles the locked Rust -release and runs it as the unprivileged `rustproxy` user (UID 10001). The core image does -not mount the Docker socket. Docker discovery uses the separate `docker-agent/` process on -each Docker host, so the proxy container never needs Docker-socket access. See -[Docker discovery](DOCKER_DISCOVERY.md). +The included `Dockerfile` compiles RustProxy with a **Rust 1.99** build image and runs it on Debian as an unprivileged `rustproxy` account (UID 10001). It includes Python, Certbot and DNS-plugin packages for certificate handling. The main container does **not** need the Docker socket. -Build locally with a normal OCI builder: - -```bash -docker build -t rustproxy:0.29.1 . -``` - -Persist `/var/lib/rustproxy`; it contains the SQLite database, certificates and the -optional cold-cache tier. The default container proxy listener is `0.0.0.0:8080`. -Administration remains `127.0.0.1:9090` inside the container and is intentionally -not remotely reachable by merely publishing port 9090. +## Build and start + +Run from the repository root: ```bash +docker build -t rustproxy:0.34.0 . docker volume create rustproxy-data -docker run --rm -d --name rustproxy \ +docker run -d --name rustproxy \ -p 8080:8080 \ -v rustproxy-data:/var/lib/rustproxy \ - rustproxy:0.29.1 + rustproxy:0.34.0 +``` +In the image, the default HTTP frontend binds `0.0.0.0:8080`. The admin listener defaults to `127.0.0.1:9090` **inside the container**. Simply publishing container port 9090 does not make that loopback listener reachable from a remote host. Initialize the administrator from a local container shell: + +```bash docker exec rustproxy rust-proxy admin setup-token ``` -To expose administration, first enable administration HTTPS in the persisted -RustProxy configuration and choose a certificate, then bind the administration -listener to a non-loopback address. Publish the admin port only after HTTPS is -active. Plain remote HTTP keeps the legacy explicit risk gate. +Provide a secure, private path for first-time panel access. If remote administration is required, configure its own HTTPS mode, certificate and an intentional non-loopback listener before mapping the port publicly; restrict it with firewall policy. Never expose plain HTTP administration as a convenience. -The runtime image includes Python, venv/pip, OpenSSL and distribution Certbot packages -for Cloudflare, OVH and RFC2136 fallback. RustProxy prefers its own pinned Certbot -environment under `/var/lib/rustproxy/acme-python`; because `/var/lib/rustproxy` is the -persisted volume, that environment survives container replacement. If pip setup is not -possible, the image can fall back to the distribution Certbot binaries already in `PATH`. +## Persistent data and ports -For access-log files, prefer a writable path under `/var/lib/rustproxy` or mount a -separate writable log volume. The built-in JSON file sink is disabled by default. -Low ports such as 80/443 require an explicit container capability or host port -mapping; the image itself does not run as root. +- Mount `/var/lib/rustproxy` persistently for SQLite, private keys, certificate profiles and optional COLD cache. Back it up securely and keep volume ownership correct. +- Map the chosen HTTP and HTTPS TCP ports. If enabling HTTP/3, publish the corresponding **UDP** port too, and allow it through the host firewall. +- If you need low-numbered **container** ports, use host-port forwarding or deliberate capability settings; the image runs without root privileges. +- Store file access logs under a writable location/volume if enabled. The built-in on-disk access-log sink is not necessarily on by default. +- Managed ACME material in the persisted volume survives container replacement. DNS credentials and external mounts must be protected and carried over separately. + +For Docker-based application discovery, install the **separate Docker agent** on each target host and connect it over the supported restricted interface. See [Docker discovery](DOCKER_DISCOVERY.md). + +## Upgrade and validate + +Build a reviewed new image, preserve the volume, make an encrypted application backup and deploy with a maintenance window. Verify configuration compatibility, listener reachability, frontend certificates, backup/restore and live traffic. The supplied 0.34.0 source candidate is not itself a production-tested OCI image. diff --git a/docs/CUSTOM_LOCATIONS.md b/docs/CUSTOM_LOCATIONS.md index 8064a01..66a590e 100644 --- a/docs/CUSTOM_LOCATIONS.md +++ b/docs/CUSTOM_LOCATIONS.md @@ -1,132 +1,41 @@ -# Custom locations +# Custom locations and path rules -RustProxy 0.22.3 supports the common location-level controls that are typically used for application reverse proxies. The UI intentionally exposes a safe subset instead of accepting arbitrary Nginx configuration text. +Custom locations add exceptions to a Vhost's normal source. They are **not** required for the root path `/`: any request not matched by a custom rule continues to the Vhost's main backend pool or Web Service. Configure them under **Vhosts → Edit → Custom locations**. -## Routing model in 0.16.0 +## Path matching -The vhost's main backend pool is an internal fallback and is **not** one of the Custom locations. Custom locations are optional overrides only. Request routing is therefore: +| Match mode | Purpose | Priority | +|---|---|---| +| `exact` | One exact normalized path | Highest | +| `prefix_priority` | Longest prefix that suppresses regex evaluation | Ahead of regex when matched | +| `regex` / `regex_insensitive` | Case-sensitive or insensitive regular expression | Tested in configured order after priority-prefix rules | +| `prefix` | Ordinary longest matching path prefix | Fallback among matching rules | -```text -exact custom location --> priority/regex/prefix custom location according to the documented precedence --> main vhost backend pool -``` - -An empty Custom locations list is valid. Adding `/test`, a regex, a local response or a dedicated-backend location cannot disable `/`, `/abc` or other unmatched paths. Older saved documents that contain the former untouched synthetic `root` prefix `/` entry are normalized automatically. If `/` was deliberately customized with a backend, cache/ACL/header rule, rewrite or local response, it remains a real Custom location and intentionally overrides the main fallback. - -## Dashboard editor - -The visual editor is the primary workflow. Every location card now shows a live **Effective behavior preview** at the bottom with the match mode/pattern, proxy or local-response action, selected backend, rewrite behavior, cache policy, ACL composition and request/response header-rule counts. The preview is calculated from the unsaved form state so an administrator can verify intent before saving. - -Request and response header rules use numbered visual cards with localized **Operation / Header name / Header value** controls. `Set`, `Append` and `Remove` are supported. The Advanced JSON view remains available for automation/copy-paste, but is no longer required for normal editing. The documentation button in the location tab explains precedence, rewrites, local responses, policy composition and common examples. - -## Match modes - -Each location has an ID, a pattern and one match mode: - -| RustProxy mode | Nginx-style equivalent | Behaviour | -| --- | --- | --- | -| `prefix` | `location /path/` | Longest matching path prefix. A matching regex may still win. | -| `prefix_priority` | `location ^~ /path/` | Longest prefix and, when selected, skips regex locations. | -| `exact` | `location = /path` | Exact normalized path only. Exact matches have highest priority. | -| `regex` | `location ~ pattern` | Case-sensitive Rust regular expression. Regex locations are tested in configuration order. | -| `regex_insensitive` | `location ~* pattern` | Case-insensitive Rust regular expression. Regex locations are tested in configuration order. | - -Routing and ACL checks collapse repeated literal slashes, matching Nginx's usual `merge_slashes on` behaviour. For example `/static/js//events.js` is routed as `/static/js/events.js`. Unless a rewrite is configured, the original request target is still sent to the backend. - -Encoded separators (`%2f`, `%5c`), encoded `%`, dot segments and other ambiguous path forms are still rejected instead of being silently normalized. +Ambiguous and unsafe encoded path forms are rejected rather than treated as alternative spellings. Avoid overlapping regex rules when order matters; test paths with and without trailing `/`. A Vhost's main source handles paths without a matching custom location. ## Actions -A location can proxy to a backend, serve an existing Web Service, return a local response, or redirect the client to another absolute HTTP(S) URL. +- **Proxy:** use the Vhost backend pool or target a specific backend. A location can have an explicit cache override. +- **Web Server:** reference an existing Web Service with its saved document root and PHP/static settings. +- **Local response:** return a chosen `200–599` status, content type and body without connecting to an origin. `HEAD`, `204` and `304` do not return a body. +- **Redirect:** issue `301`, `302`, `303`, `307` or `308` to a valid absolute HTTP(S) target. Query preservation is configurable. Redirect rewrite expressions may reference regex captures. -### Proxy +### Path rewriting -The location can use the vhost pool or pin traffic to one backend. A dedicated backend can be created from the location editor. Cache policy can inherit from the vhost or be explicitly enabled/disabled for the location. +`preserve` forwards the original path, `strip_prefix` removes the matched prefix, `replace_prefix` substitutes a new normalized prefix and `regex` rewrites using captures such as `$1` or `${name}`. Queries remain intact unless a redirect explicitly changes handling. Public cache keys are based on the client-facing URL, not an internal rewritten backend path. -Supported path transformations: +**Example:** send `example.com/api/*` to a dedicated application while `/` remains on the main website. Create a `/api/` prefix location, select its backend and choose whether to preserve or strip `/api/`. Inspect the editor's effective-behavior preview before saving. -- **Preserve** — send the original client path unchanged. -- **Strip prefix** — remove the matched prefix before proxying. -- **Replace prefix** — replace the matched prefix with another normalized path prefix. -- **Regex rewrite** — available for regex locations; Rust regex captures can be referenced as `$1` or `${1}` in the replacement. +## Headers, access and method rules -The query string is preserved. Rewrites change only the selected source path; reverse-proxy cache identity remains based on the public client URL. +Each location can add request/response header operations (`set`, `append`, `remove`), reusable ACL profiles, inline IP/CIDR rules, country access and HTTP-method allow/block lists. Every applicable host and Vhost policy must still pass; a location cannot remove a higher-level denial. Transport/forwarded-identity headers are protected from unsafe overrides. Authentication, rate limits, WebSocket policy and other Vhost settings continue to apply. -### Web Server +## Internal error fallback -`Web Server` selects one reusable service previously created in the **Web Server** workspace. The location stores only the Web Service ID; document root, index list and directory-listing policy remain in that shared service. Existing location matching and path rewriting are reused, but backend pinning and reverse-proxy object-cache controls do not apply to local files. See [WEB_SERVER.md](WEB_SERVER.md). +Mark a location **Internal** to make it unavailable to ordinary public URI matching. A Vhost or location may direct specific origin responses (`403`, `404`, `500`, `502`, `503`, `504`) to a named internal location ID in the same Vhost. The dispatch is internal, not a browser redirect; request context and access evaluation are preserved. Invalid targets or loops are rejected. -### Local response +If all you need is a different error **page body**, use **Vhost → Pages → Intercept application error responses**, which keeps the original status without making a second origin request. -`Return custom response` is the equivalent of a simple Nginx `return`: choose an HTTP status from 200 through 599, a Content-Type and a response body. RustProxy does not open a backend connection for this action. `HEAD`, `204` and `304` responses do not send a body. +## Not an Nginx configuration parser -### Redirect - -`Redirect` is a backend-less action. Choose `301`, `302`, `303`, `307` or `308`, set an absolute `http://` or `https://` target, and optionally preserve the incoming query string. RustProxy validates the target and does not allow capture expansion inside the target scheme/authority. - -For capture-based path rewriting, configure a separate **Redirect rewrite regex**. Captures can be referenced as `$1` or `${name}` in the redirect target path/query. This separation mirrors an Nginx `location` selector followed by a separate `rewrite` expression. For example, the Nginx pattern: - -```nginx -location ~* \.(?:tar\.gz|zip?)$ { - rewrite ^/(.*)$ https://objectstorage.example.invalid/b/repo/o/$1 permanent; -} -``` - -corresponds to a case-insensitive regex Custom location `\.(?:tar\.gz|zip?)$`, action **Redirect**, status `301`, target `https://objectstorage.example.invalid/b/repo/o/$1`, and redirect rewrite regex `^/(.*)$`. Query preservation is controlled independently. - - -## Request and response headers - -Every location has a visual header configurator and an Advanced JSON view. Both edit the same data. - -Supported operations are: - -- `set` — replace the header with the configured value; -- `append` — append another header value; -- `remove` — remove the header. - -Example JSON: - -```json -{ - "request": [ - {"op": "set", "name": "X-App-Route", "value": "api"} - ], - "response": [ - {"op": "set", "name": "Cache-Control", "value": "no-store"}, - {"op": "remove", "name": "X-Legacy-Header"} - ] -} -``` - -Hop-by-hop/framing headers, forwarding identity headers, RustProxy request IDs and WebSocket handshake headers are protected and cannot be overwritten by custom rules. This prevents a location rule from changing transport framing or spoofing the verified client identity. - -## Access control - -A location can define its own IPv4/IPv6 CIDR ACL, select a reusable ACL profile, add a location-specific country block/allow-only rule, and configure an HTTP method policy (`off`, `allow` or `block`). Method names are normalized to uppercase and validated as HTTP method tokens. - -All access layers are additive. The request must pass the vhost method policy, location method policy, host/profile/vhost IP ACLs, location profile ACL, inline location ACL and every applicable country policy. A location therefore cannot override a denial from its vhost. Method denial returns `405 Method Not Allowed` (with `Allow` for allow-list mode); IP/country denials retain the normal `403` pipeline. These checks occur before backend selection/connection. - -Authentication, vhost rate limits, security headers, compression, custom error pages and WebSocket policy still apply through the normal vhost pipeline. - -## Internal locations and error fallbacks - -RustProxy 0.29.1 can mark a Custom location as `internal`. An internal location is addressed by its stable location `id` and is never selected by a public URI match. The vhost main source or another Custom location can use `error_fallback` to internally dispatch configured `403/404/500/502/503/504` responses to that target. The default intercepted status is `404`. - -This is an internal request handoff, not an HTTP redirect. The original method, URI/query and request context are retained. The fallback target re-enters normal access/header/cache/compression/response processing. Fallback targets must be enabled internal locations in the same vhost; validation rejects cycles and runtime dispatch is depth-limited. See [current vhost documentation](VHOSTS.md). - -### Replace origin error bodies without an internal location (0.29.7) - -The independent, optional **Vhost → Pages → Intercept application error responses** -setting (`pages.intercept_statuses`, for example `[404, 503]`) replaces a -backend or Web Server/PHP origin's error *body* with the configured vhost -error page, then global error page, or a built-in page. It retains the HTTP -status and sets `Cache-Control: no-store`. Unlike `error_fallback`, it does -**not** forward the request to a different backend. The existing internal -`error_fallback` action has priority and its final response remains intact. -See [Web Server – application HTTP error interception](WEB_SERVER.md#intercept-application-http-errors-0297). - -## What is intentionally not an Nginx parser - -Custom locations do **not** execute arbitrary Nginx directives or configuration snippets. RustProxy does not currently implement location-level `try_files`, per-location filesystem aliases/roots, embedded scripting, arbitrary `if` expressions or arbitrary module directives. Static roots are defined only by reusable Web Services. Use the explicit UI controls and the documented JSON schema instead; unknown fields are rejected on save. +The editor does not run arbitrary Nginx snippets, filesystem aliases, arbitrary `if` rules or per-location static roots. Configure static paths in a reusable Web Service, then assign that service to the location. Inspect [Vhosts](VHOSTS.md) and [Access](ACCESS.md) for the policies applied before connecting to the backend. diff --git a/docs/DASHBOARD.md b/docs/DASHBOARD.md new file mode 100644 index 0000000..45449b3 --- /dev/null +++ b/docs/DASHBOARD.md @@ -0,0 +1,32 @@ +# Administration dashboard and preferences + +The authenticated panel is the primary way to manage RustProxy. Pages cover **Overview**, **Vhosts**, **Web Server**, **Certificates**, **Docker vhosts**, **Streams**, **Traffic**, **Cache**, **Logs**, **Settings** and **Documentation**. Availability of individual cards depends on enabled features and the account's display preferences. + +## Overview cards and layouts + +The Overview contains live counters, charts and optional backend, cache, PHP, Docker and Stream cards. Under **Settings → Appearance → Dashboard layout** or the Overview editor, configure visibility, order, card width, density and the number of history samples. A hidden card stops consuming its optional browser-side visualization work but does not turn off its underlying proxy feature. + +**Dashboard Presets** supports up to **16 named layouts per administrator**. Save, load, rename, overwrite and delete presets; a factory layout can be restored. Presets preserve the dashboard arrangement, not the live traffic values. Interface theme, scale and other display settings are also account-scoped and do not alter proxy routing or rewrite shared configuration. + +## Editing configuration safely + +1. Open the relevant workspace and inspect existing state before editing. +2. Change a logically related group of settings and review the editor's effective-preview/status where available. +3. Save against the expected current configuration revision. A stale edit should be reloaded instead of silently overwriting a newer change. +4. Confirm whether the saved setting is active immediately or requires a process restart (notably listener binds and administration HTTPS). +5. Check the live page and request logs after sending test traffic. + +Unfinished editors may be minimized into a draft dock; this is a UI convenience and **not** a durable change or backup until saved. + +## History and restore + +**Configuration history** stores retained validated snapshots. Restoring a snapshot creates a new configuration revision. It does not restore every filesystem file, external certificate, Secret Store key or accumulated cache/log history. For disaster recovery, use a full encrypted backup; see [Backup](BACKUP.md). + +## Interpreting charts + +- Live views are process/sampling-based and can show no-data when an integration has not reported yet. +- Per-backend protocol badges can distinguish configured HTTP mode from the protocol actually observed in connection traffic. +- Response-header timing excludes the full streamed body duration. HTTP server/client percentages refer to the specified layer. +- A data sampling gap, a reset after restart and a true zero are not always equivalent. + +See [Monitoring](MONITORING.md) for metric details and [Configuration](CONFIGURATION.md) for persistent service settings. diff --git a/docs/DASHBOARD_PRESET.md b/docs/DASHBOARD_PRESET.md deleted file mode 100644 index d99fbb6..0000000 --- a/docs/DASHBOARD_PRESET.md +++ /dev/null @@ -1,74 +0,0 @@ -# Factory Overview dashboard preset (0.33.3) - -`src/admin/web/dashboard-defaults.json` is the single factory configuration -for the Rust admin preference API and the authenticated JavaScript panel. - -The preset contains: - -- `dashboard_widgets`: complete card identifier order, including hidden cards; -- `dashboard_hidden_widgets`: identifiers hidden in the factory Overview; -- `dashboard_density`: `normal`, `compact` or `ultra`; -- `overview_samples`: `60`, `120`, `300` or `600` samples for first use in a browser. - -Card IDs and labels are defined in `src/admin/web/dashboard-widgets.json`. -The preset may omit newly registered card IDs: the server/client will append -those cards and apply the registry's `default_hidden` flag. The preset must -not contain unknown or duplicate IDs, or hidden IDs absent from its order. - -The bundled 0.33.1 preset reproduces the supplied operator layout: 78 ordered -cards, 51 hidden, compact density and 600 initial samples. The packaged -Web Server cards remain hidden by default. - -## User-selected card widths (0.33.3) - -The factory preset intentionally retains the original 0.33.1 widths and -legacy grouped presentation. Version 0.33.3 adds a separate nullable preference: - -- `dashboard_widget_sizes: null` (including omitted in an old record): retain - previous size groups, so upgrades do not spontaneously rearrange cards. -- `dashboard_widget_sizes: {}`: freely order all card types, while leaving - their widths at the original values. -- `dashboard_widget_sizes: { "http_protocol": 4, "wide_transform_activity": 3 }`: - also override individual widths, in units of the 12-column Overview grid. - -Permitted widths: 2, 3, 4, 6 and 12 columns; regular charts need at least 3, -full diagnostic panels at least 4, and the backend table at least 6. At the -largest canvas four 3-column charts fit alongside each other. On small screens -the renderer reduces to a six- or two-column grid; the saved dimensions do not -change. In **Unlock layout**, click − / + or drag cards across their old groups; -**Arrange cards visually** provides the same controls. Save persists order, -visibility and sizes in the administrator's current preference document. - -**Cancel** restores the saved layout and sizes. **Restore defaults** restores -the original factory order/visibility and width defaults, but does not change -the independently saved Overview sample limit. Dense packing reduces gaps, -so when columns do not divide evenly, a later narrow tile may fill an earlier -free space without changing the saved DOM or keyboard traversal order. - -## Deploying an edited preset - -1. Edit `src/admin/web/dashboard-defaults.json` in the source tree. -2. Validate the JSON and identifiers (e.g. run - `python3 scripts/tests/dashboard_layout_0331_browser_test.py`). -3. Rebuild RustProxy (`./dev.sh check` / the normal release build) and deploy - the newly built binary. The preset is **embedded**; editing the installed - source JSON alone does not update a running binary. - -## Existing installation and reset behavior - -- Admin accounts with stored preferences in SQLite retain their chosen - ordering, card visibility and density on upgrade. The preset never rewrites - these saved records. Accounts without saved preferences receive the new - factory defaults, even on upgraded installations. -- In Overview, **Arrange cards visually → Restore defaults** restores the - factory card order/visibility after the user confirms; it does not reset - unrelated appearance or sample-history settings. -- The history selector uses browser-local storage key - `rustproxy.overview.liveChartSamples`. An explicitly saved choice (including - 60) wins over the factory sample count. A browser without a saved choice - starts with 600. The sample limit does not change polling frequency. -- While layout is unlocked, hide/show/reorder changes are temporary and stay - effective during backend/Stream status refresh. **Cancel changes** restores - the saved layout; **Save and lock** persists the current draft. - -No SQLite schema migration or additional HTTP endpoint is required. diff --git a/docs/DOCKER_DISCOVERY.md b/docs/DOCKER_DISCOVERY.md index b2ff103..60110b6 100644 --- a/docs/DOCKER_DISCOVERY.md +++ b/docs/DOCKER_DISCOVERY.md @@ -1,150 +1,47 @@ -# Docker discovery and dynamic vhosts +# Docker discovery and generated Vhosts -RustProxy 0.22.3 includes an optional standalone Docker discovery agent. The RustProxy core never opens the Docker socket. Run `docker-agent/rustproxy-docker-agent` on every Docker host that should be discovered; RustProxy polls each agent over HTTPS by default using that agent's Bearer token and certificate pin. +RustProxy discovers published Docker services through a **separate read-only Docker agent** on each Docker host. The RustProxy core does not need Docker-socket access. The agent observes Docker containers; RustProxy turns enabled assignments into dynamic, ordinary HTTP Vhosts. -## State model +## Set up a Docker host -- **Desired state** is stored in the normal RustProxy SQLite configuration: agent endpoint/token, per-agent base domain, optional default backend IP, listener IDs, optional frontend certificate and service-to-vhost assignments. -- **Observed state** stays in memory: Docker host addresses, container metadata and every non-loopback published TCP binding. -- A successful poll reconciles enabled assignments into ephemeral vhosts. Polling does not write a new configuration revision. -- If a published host port, binding or detected host address changes, the next poll rebuilds the effective route. -- If a service disappears, its dynamic vhost disappears until the service is observed again. -- Docker discovery is optional; ordinary vhosts and all other proxy functions remain independent of Docker. +1. On the Docker host, review `docker-agent/README.md`, `docker-agent/docker-agent.env.example` and `docker-agent/install.sh`. +2. Build/install the Go agent with `cd docker-agent && sudo ./install.sh`; the installer runs tests, installs the service, initializes a Bearer token and an HTTPS certificate, and prints the SHA-256 certificate fingerprint. +3. Restrict the agent port (default shown in the example environment: `9876`) to the RustProxy host/network. Access to the Docker socket is privileged, even though the agent's HTTP API is read-only. +4. In **Settings → Docker agents**, create an agent with an endpoint such as `https://10.10.0.21:9876`, its token and printed fingerprint, base domain, selected listener IDs and optional certificate/ACL/default backend IP. +5. Use **Docker vhosts → Refresh agents** and check that the agent observation is successful before publishing any service. -The agent returns only running containers with Docker-published TCP ports. Bindings limited to loopback (`127.0.0.0/8` or `::1`) are excluded. Multiple bindings for the same container port are retained instead of being collapsed to one address/port pair. +The agent normally presents its own persistent self-signed certificate; pin the exact fingerprint in RustProxy. When using a CA-issued agent certificate, normal TLS CA/hostname verification can be used instead. Plain HTTP exposes the bearer token and should be avoided. -In the dashboard, an enabled Docker agent is **WAITING** until its first background observation arrives after RustProxy starts. This is not a connection failure. Opening **Docker vhosts** fetches the current *cached* status immediately, even if the per-page **Live view** option is off. While no enabled agent has been observed, the page briefly checks cached status every five seconds (when global automatic refresh is enabled), and stops once all agents have a first result. A genuine failed poll is reported with the returned error, and the configured TLS certificate pin is marked as verified only after a successful observation. **Refresh agents** explicitly triggers an immediate agent poll. No TLS pin, token or routing verification is bypassed. +## What discovery reports -## Install an agent on a Docker host +- Running containers with **Docker-published, non-loopback TCP ports**; it does not turn an internal-only container port into a directly reachable service. +- Usable Docker-host interface/IP addresses and each published host binding, allowing an explicit backend address selection when the host has multiple networks. +- Current service status, container metadata and optional aliases. The agent does not install applications, change Docker network rules or deploy TLS on their behalf. -The agent is written in Go and uses only the standard library. +Agent inventory polling is separate from the panel's live status refresh. A new agent may initially show **WAITING** until the first poll, not a failure. **Refresh agents** requests an immediate agent update. Actual failures report a reason; a temporary agent outage can preserve the last successful inventory while a successful missing-service observation can lead to a generated route returning `503` until the service returns. -```bash -cd docker-agent -sudo ./install.sh -``` +## Publish a discovered application -The installer runs the Go tests, builds one binary, creates the `rustproxy-docker-agent` system user, generates a random token on first install, starts `rustproxy-docker-agent.service` and prints the persistent self-signed certificate SHA-256 fingerprint. +In **Docker vhosts**, select the service and set: -Manual build: +| Option | Guidance | +|---|---| +| **Hostname / aliases** | A short name expands under the agent base domain; a full FQDN stays as entered. Configure public DNS separately. | +| **Backend IP/port** | Pick a detected, reachable Docker-host address or the configured automatic option. Docker host/firewall binding must permit RustProxy to connect. | +| **HTTP / HTTPS upstream** | Match the container's actual service protocol; review origin TLS verification and Host behavior. | +| **Frontend listeners / certificate** | Selected through the agent defaults and RustProxy configuration; ensure certificate covers the generated name. | +| **Force HTTPS / WebSockets** | Enable only when needed and when the right frontend listeners exist. | +| **Cache / compression / transforms** | Optional; obey the global limits and privacy policy of the application. | +| **Health / ACL / timeouts** | Set checks and protections appropriate for the application, not just the Docker host. | -```bash -cd docker-agent -go test ./... -go build -trimpath -ldflags='-s -w' -o rustproxy-docker-agent . -``` +Use **Publish** and test the resulting hostname. Edits to an already published service use its own **Apply changes** action; unpublishing does not stop its Docker container. Hidden rows in the page are only a browser view preference, not a security policy. -Agent configuration is read from `/etc/rustproxy/docker-agent.env`: +## Troubleshooting -```text -RUSTPROXY_DOCKER_AGENT_LISTEN=0.0.0.0:9876 -RUSTPROXY_DOCKER_AGENT_TOKEN= -RUSTPROXY_DOCKER_AGENT_TLS=true -RUSTPROXY_DOCKER_AGENT_CERT_FILE=/var/lib/rustproxy-docker-agent/agent.crt -RUSTPROXY_DOCKER_AGENT_KEY_FILE=/var/lib/rustproxy-docker-agent/agent.key -RUSTPROXY_DOCKER_ADVERTISE_IP= -RUSTPROXY_DOCKER_PUBLIC_IP_URL=https://api.ipify.org -``` - -`RUSTPROXY_DOCKER_ADVERTISE_IP` is only a preferred address hint; it does not hide the other host addresses. The inventory includes usable addresses found on active host network interfaces, the primary routed address, the optional public-IP lookup result and the advertised address. Loopback/link-local and obvious Docker-generated bridge/veth interfaces are omitted; explicit Docker bind addresses are still preserved with each service. Each host-address entry includes its source and, where available, interface name/scope. - -The inventory endpoint is `GET /v1/inventory` and requires `Authorization: Bearer `. `GET /healthz` exposes no inventory. HTTPS is enabled by default. The agent creates an ECDSA P-256 self-signed certificate once and keeps it in `/var/lib/rustproxy-docker-agent/`; the key is mode `0600`. - -`install.sh` prints both the generated/reused bearer token and the SHA-256 certificate fingerprint. Copy the SHA-256 fingerprint printed by `install.sh` into the agent settings in RustProxy. You can print it again with `sudo rustproxy-docker-agent --print-fingerprint`. With a fingerprint configured, RustProxy accepts only that exact certificate and still checks the TLS handshake signatures. If the agent uses a certificate issued by a normal public CA, leave the fingerprint empty to use standard CA/hostname verification. Plain HTTP is retained only for explicit compatibility setups and exposes the Bearer token to the network. - -Docker-socket access is privileged. Restrict access to the agent port to the RustProxy host even when TLS is enabled. - -## Configure multiple Docker hosts - -Open **Settings -> Docker agents** and create one entry for every Docker host. Each agent also has its own inventory **poll interval** (5..3600 seconds, 30 seconds by default); manual refresh bypasses the interval. Agents are independent and may use different domains, for example: - -```text -docker-a -> docker1.lab.local -docker-b -> docker2.lab.local -docker-c -> apps.example.net -``` - -For each agent configure: - -- stable agent ID and display name; -- agent endpoint, normally `https://10.10.0.21:9876`; -- the Bearer token from that Docker host; -- the SHA-256 certificate fingerprint printed by the default self-signed agent installer, or an empty fingerprint only for a public-CA certificate; -- that agent's base domain; -- optional default backend IP; -- one or more RustProxy listener IDs; -- an optional configured frontend certificate to apply to every generated vhost for that agent; -- an optional default ACL profile inherited by generated vhosts unless a service selects its own profile. - -RustProxy polls each enabled agent according to its configured interval (30 seconds by default, checked by the scheduler in bounded steps). The 5-second Live-view refresh reads only cached RustProxy memory and does not force a Docker request. **Docker vhosts** shows the latest cached observation for every configured agent. A failed agent request retains the last successful inventory when available, so a temporary control-plane outage does not withdraw working routes. A successful inventory that omits an already published service keeps its hostname and changes the generated route to 503 until discovery sees the service again. - -## Publish a discovered service - -Use the search field in **Docker vhosts** to filter by agent, service, hostname or alias. Individual service rows can be hidden from the normal view; **Show Hidden** beside Docker agents settings reveals them so they can be restored. This is a per-browser view preference and does not disable or unpublish the vhost. - -For each discovered service you can change the mapping directly in **Docker vhosts**: - -- **Hostname**: a short name such as `app1`, or a complete FQDN such as `portal.example.com`; -- **Aliases**: zero or more additional short names/FQDNs, edited with the same add/remove chips as normal vhost hostnames; -- **Backend host IP**: Auto/default or one concrete address detected on the Docker host; -- **Backend protocol**: HTTP or HTTPS; -- **Preserve Host**: forward the requested hostname to the container when enabled; new HTTP/1.1 assignments default to enabled, while an existing explicit disabled value is preserved; -- **Redirect HTTP to HTTPS**: per-app Force SSL switch. New assignments default on when the agent has a usable plain-HTTP + TLS listener pair; assignments saved before 0.18.8 retain their previous redirect-off behavior until explicitly saved with the new switch; -- **WebSockets**: enable or disable classic HTTP/1.1 Upgrade forwarding for this application; -- **Cache**: enable or disable normal RustProxy caching for this generated vhost; -- **Compression**: one on/off checkbox for this published vhost. When enabled, Docker vhosts use the codecs and priority configured globally under **Settings -> Response**; there are no per-Docker-vhost codec selectors; -- **Transform**: opt this generated vhost into the global static-response transform engine; it is off by default and still requires global transformation to be enabled; -- **Backend health check**: HTTP/TCP/off active-probe mode, path/Host override, interval, timeout, slow threshold, rise/fall counters and accepted HTTP status range. -- **Passive Backend Health**: independent opt-in policy, OFF by default, with separate connect/timeout/I/O/HTTP-5xx/slow-response switches and optional passive rise/fall overrides. -- **Access control**: an optional reusable ACL profile plus an optional inline IP/CIDR ACL for this application. A per-app profile overrides the agent default profile; inline rules remain cumulative with the effective profile. -- **Proxy timeouts**: optional connect, response-header, read-idle and send-idle timeouts; blank values inherit RustProxy defaults. - -Publish/Unpublish is applied immediately. Editing a published service uses the per-service **Apply changes** action; there is no page-wide assignment save step. The optional **Live view** switch refreshes only cached RustProxy observations/health state and does not contact the Docker agent. - -A short name is expanded below that agent's base domain. With base domain `docker1.lab.local`, `app1` becomes `app1.docker1.lab.local`. A full FQDN is not rewritten. Aliases use the same rule, so `app-old` becomes `app-old.docker1.lab.local`, while `legacy.example.com` remains unchanged. - -This makes renaming and aliasing a Docker-backed vhost a normal RustProxy configuration change; the container/service identity remains the stable link to discovery. - -## Multiple Docker-host IPs and bindings - -The agent reports all usable addresses it can see on the Docker host. This is intended for hosts that have separate public, management and LAN addresses. - -RustProxy also keeps every published Docker binding. For example, one container port may be published as: - -```text -10.21.0.15:18080 -192.168.50.15:28080 -``` - -Selecting `10.21.0.15` for the Docker vhost uses port `18080`; selecting `192.168.50.15` uses `28080`. For Docker wildcard bindings (`0.0.0.0` / `::`), RustProxy uses the selected host address with the wildcard binding's published port. - -Backend selection order is: - -1. the Docker vhost's explicit `target_ip`; -2. the agent's optional `default_target_ip`; -3. a concrete Docker bind address; -4. the agent's preferred/primary detected address when compatible with the binding; -5. another compatible detected host address. - -An explicit target that is not compatible with the service's Docker bindings does not silently fall back to another IP. In 0.27.0 the published hostname remains materialized and returns `503 Service Unavailable` until the selected service/address/binding is available again. Disabling the Docker agent stops discovery but keeps already published hostnames claimed as 503 routes; re-enabling the agent resumes reconciliation. Publication is removed when the administrator explicitly unpublishes the assignment or removes the agent configuration that owns it. - -The generated backend is pinned to the selected IP through an address allowlist and uses the matching Docker-published host port. Cache, Transform, Compression, WebSockets and HTTP-to-HTTPS redirect are materialized from the per-app settings. Compression has inherit/on/off plus per-codec overrides. Global Response codec switches remain the hard availability ceiling and the minimum size remains global. If the Docker agent has a frontend certificate selected, that certificate ID is copied to every generated vhost and participates in the normal TLS/SNI certificate validation and ownership rules. The effective ACL profile is the per-app profile when selected, otherwise the agent default profile. Active backend health checks default to TCP. Passive Backend Health is materialized from the Docker assignment separately and defaults to OFF, so discovery refresh/restart does not silently re-enable production-traffic quarantine. The Docker vhosts page exposes the same passive policy as ordinary vhosts. Normal 5xx/timeouts/connect/I/O/slow-response observations remain visible in counters while passive health is OFF. Active checks can be switched between HTTP/TCP/off per service without changing the passive policy. - -## DNS - -DNS for Docker vhost names must resolve to the RustProxy frontend/listener that should receive the HTTP(S) request. For a per-agent base domain this is normally a wildcard record, for example: - -```text -*.docker1.lab.local -> -*.docker2.lab.local -> -``` - -Custom FQDNs and aliases likewise need DNS records pointing at the relevant RustProxy frontend. The Docker-host addresses detected by the agent are backend routing candidates; they are not automatically published to DNS. - -RustProxy does not change DNS records itself. -## Prometheus metrics - -Docker discovery has its own Prometheus collector switch, `prometheus.include_docker_vhosts`, enabled by default. It is intentionally separate from `include_vhosts` and `include_backends`. Dynamic Docker routes never appear under ordinary `rust_proxy_vhost_*` or `rust_proxy_backend_*` families. - -The Docker collector exports bounded configured labels only: agent ID, generated Docker-vhost ID, assigned service key, configured domains/aliases and backend ID. It includes `rust_proxy_docker_vhost_*` traffic/status/duration metrics and `rust_proxy_docker_backend_*` readiness, health, active work, requests/errors/bytes, current/EWMA latency, failure/success counters, health transitions and last-probe observations. No token, agent endpoint, backend URL or arbitrary request Host value is exported. +- **WAITING:** first poll not yet observed; refresh and inspect the agent endpoint. +- **Agent error:** verify firewall, TLS fingerprint/CA, bearer token, service state, advertised host and TCP route. +- **503 generated Vhost:** verify running container, published TCP port, selected IP/bind address, backend protocol and health check. +- **Wrong application hostname:** check base-domain expansion, aliases, DNS and conflicting ordinary Vhosts. +- **Docker service not listed:** agent reports only eligible running containers with published non-loopback TCP bindings. +For agent operations and its security model see `docker-agent/README.md`. For normal routing, health and cache controls see [Vhosts](VHOSTS.md) and [Cache](CACHE.md). diff --git a/docs/ERROR_LOGGING.md b/docs/ERROR_LOGGING.md deleted file mode 100644 index ffdb0b1..0000000 --- a/docs/ERROR_LOGGING.md +++ /dev/null @@ -1,47 +0,0 @@ -# Error and warning logging - -RustProxy routes diagnostics according to the **Error file** switch under Settings > Logging. When the file is enabled, `ERROR` and `WARN` events, including PHP-FPM stderr and Rust worker panics, go to the configured error file rather than also being printed to the systemd journal. When the file is disabled (the default), those events go to the console/stderr (journald under systemd). `INFO` and lower-severity messages continue to use the console and any independently configured access sinks are unaffected. - -A graceful upstream HTTP/2 `GOAWAY(NO_ERROR)` may coincide with a Hyper `Canceled -> connection closed` error. RustProxy treats the cancel as expected H2 churn **only when** it is correlated with a recently observed graceful GOAWAY on that backend client (one-second window). Safe empty methods may retry once. An unrecovered request remains an HTTP 502 and retains its detailed access/error context; its redundant upstream diagnostic uses `TRACE`, not `DEBUG`. Uncorrelated cancellations still follow normal error processing and may influence Passive Health. The text `connection closed` alone is not evidence of a graceful GOAWAY. This is not a global error-log suppression rule. - -## Configuration - -The settings document contains: - -```json -{ - "error_log": { - "enabled": true, - "path": "/var/log/rustproxy/error.log", - "format": "JSON" - } -} -``` - -`format` is `JSON` or `RAW`. The feature is disabled by default. The administration Settings UI exposes the same enable/path/format controls and applies changes at runtime, without restarting RustProxy. - -The sink accepts `WARN` and `ERROR` events. JSON is one object per line (including its actual `level`); RAW is one human-readable line per event. - -## Runtime behavior - -Events are handed to a bounded queue and written by one asynchronous worker. The normal request path does not perform file I/O. When the queue is full or closed, the event is counted as dropped by the file sink but immediately written to the console as a redacted emergency fallback; memory usage stays bounded. - -Filesystem errors do not stop RustProxy. The worker writes the affected diagnostics to stderr, rate-limits sink-failure messages, and retries opening the file. If the switch is disabled while events remain queued, they are sent to stderr rather than silently discarded. Changing the path or disabling the sink closes the current file; a format change takes effect without a restart. - -RustProxy installs a panic hook after initializing tracing so worker panics are recorded as `ERROR` events including thread, source location and panic payload. It intentionally does not install the default Rust hook in parallel, which would duplicate the message on stderr. It cannot capture forced termination (`SIGKILL`, `abort`) or messages written directly to stderr by external processes. - -Created log files use mode `0600` on POSIX. Symlink/non-regular targets are refused. - -## Secret redaction - -The common logging pipeline redacts sensitive keys and values before they reach the error-file sink. This includes authorization credentials, provider tokens/passwords, backup passwords, private-key PEM material and Secret Store/master-key related values. Structured secret fields are replaced with `[REDACTED]`. - -Error messages from dependencies should still be treated as untrusted diagnostic input; do not deliberately embed secrets in error text. - -## Rotation - -RustProxy does not implement file rollover, retention or compression. Use the system `logrotate`. Debian packaging is maintained outside this source tree and ready packages are published at `https://rustproxy.linuxiarz.pl`; package-specific logrotate policy belongs to that packaging repository. - -## Stored-log explorer - -The authenticated `/logs` workspace can read the configured error file alongside structured access/protocol files. It remains independent from Live Logs and can inspect an existing error file even while `error_log.enabled` is false. The browser API does not accept arbitrary paths; it resolves only the configured error-log path and applies the same bounded, read-only regular-file/symlink protections documented in [FILE_LOGS](FILE_LOGS.md). diff --git a/docs/FILE_LOGS.md b/docs/FILE_LOGS.md deleted file mode 100644 index 0d045e0..0000000 --- a/docs/FILE_LOGS.md +++ /dev/null @@ -1,109 +0,0 @@ -# Stored log explorer - -RustProxy provides an authenticated, read-only **Logs** workspace at `/logs`. The top bar has one **Logs** menu with separate **Stored logs** and **Live Logs** actions; the two data paths remain independent. - -## Separation from Live Logs - -Live Logs uses the bounded in-memory request history and Tokio broadcast channel. It remains available even when file logging is disabled and is optimized for current routed request activity. - -Stored Logs reads only paths derived from the active RustProxy logging configuration: the combined structured access log or per-vhost JSONL directory plus the optional error log. Existing files remain browsable after their writer is disabled. Low-level `protocol_error` records remain persistence-only and do not enter Live Logs. - -## Search syntax - -The dashboard uses one search field and a page-size selector. Search is case-insensitive and supports field clauses, negation, quoted values, wildcards and numeric comparisons. - -Examples: - -```text -req: GET AND !country: Poland -status:>=500 AND path:/api/* -error:"connection reset" -client_ip:192.0.2.* AND status:5xx -``` - -Rules: - -- whitespace and explicit `AND` join clauses; -- `!field:value` excludes matching records; -- quotes keep spaces in one value; -- `*` acts as a wildcard; -- numeric fields accept `>`, `>=`, `<` and `<=`; -- `status:5xx` matches a status class; -- bare terms search all stored values; -- `req`/`request` aliases `method`, `ip`/`client` aliases `client_ip`, and `source` aliases the internal log-source ID; -- `OR` is not supported and is rejected explicitly. - -The API continues to accept the older `source`, `event`, `status_class`, `client_ip` and `vhost` parameters for compatibility, but the dashboard no longer exposes those separate controls. - -## JSON parsing and dynamic columns - -JSON/JSONL lines are parsed into record objects. Canonical access-log fields are reconstructed from the active JSON field mapping where possible, so fields such as `client_ip` remain usable even when their output key was renamed. - -The query response includes a bounded list of discovered leaf field names. The browser builds the column picker from those fields. The default concise view is time, event, status, client IP, vhost, record summary and source; any returned JSON field can be selected dynamically. Fields such as `matched_by`, `sni`, `authority`, `country`, `request_id` or custom mapped fields therefore appear only when the data actually contains them. - -At least one column must remain visible. Stored-log column selection is saved per administrator account and accepts up to 64 bounded field names. - -Selecting a row highlights it and expands parsed field/value pairs directly below the row. A nested **Raw JSON** section shows the normalized complete record. - -## Sorting columns (0.31.8) - -Click a Stored Logs column header (desktop/table layout) to sort by that column; click again to reverse direction. The selected field and direction are saved with the administrator's column preferences. Numeric fields sort numerically, text case-insensitively, and absent values come last. Both standard columns (`time`, `record`, `source`) and discovered nested JSON fields are supported. - -Sorting takes place **on the server across the entire bounded matching scan, before page offset/limit is applied**. Thus page two continues the same sorted result instead of sorting only the current 100 displayed rows. Server bounds and truncation notices still apply; this is not an unbounded full-log historical index. The default remains newest first. The mobile card layout retains the selected sort order, set via the desktop table view. - -## Narrow-screen navigation (0.31.5) - -At viewport widths up to 700px, `/logs` uses a vertically scrolling list of -compact cards in place of a horizontally expanding results table. Each card -shows every enabled column, with long values wrapped or shortened until -expanded; tap the card (or focus and press Enter/Space) to inspect all parsed -fields and the complete Raw JSON. Search, source, page size, column selection, -pagination and repeated-value analysis stay available. Large raw text and -aggregate tables scroll **inside their own containers**, never across the entire -administration page. Tablet/desktop results keep the normal tabular layout. - -## Repeated-value analysis - -The **Repeated values** tab (advanced parser) can count repeated values for any parsed field returned by the explorer. Typical security-investigation fields are `user_agent`, `client_ip`, `host` and `path`. Counts are calculated across the bounded matching scan before pagination, so changing the page does not change the count set. Minimum repetitions and top-result count are configurable within server limits. Values in the result table are directly copyable. - -Aggregation does not remove the Stored Logs safety bounds: at most 4,096 distinct aggregate values are retained and at most 200 results are returned. The response marks an aggregate as truncated when a cardinality or result cap is reached. - -## RAW error logs - -RustProxy's built-in RAW error-log format is also normalized for browsing. The explorer extracts timestamp, level, target, message and recognizable `key=value` fields, while retaining the original line in `raw`. - -RAW is not a lossless structured serialization, so extraction is best-effort. Fields that cannot be separated safely remain visible through the message/raw record rather than being invented. - -## Vhost-aware protocol records - -Ordinary completed requests use `event: "request"`. Low-level failures use `event: "protocol_error"`. When RustProxy can safely identify a configured vhost, the record is persisted through that vhost's existing sink. In per-vhost file mode an unresolved record uses `_unmatched.jsonl`. - -Attribution priority remains routed vhost, HTTP `:authority`, HTTP `Host`, TLS/QUIC SNI, then unmatched. Matching reuses normal exact/wildcard frontend resolution and listener binding. Client Host/SNI text never becomes a filesystem path. - -## Browser security and bounds - -The dashboard no longer exposes the configured-file inventory. The Source selector is optional: `All configured files` is the default, while selecting one source narrows the same bounded query to that log only. - -The browser still never supplies a filesystem path. The server derives sources from the active configuration and reads only regular files. On Unix it opens with `O_NOFOLLOW`; per-vhost enumeration rejects a symlinked directory and accepts only direct regular `.jsonl` children. - -Logrotate-style gzip rotations are discovered next to configured combined access/error files and inside the configured per-vhost directory. Only regular non-symlink files matching the active filename rotation pattern (for example `access.jsonl.1.gz`) are eligible. The browser still refers to them by server-generated source IDs rather than paths. - -For per-vhost logging, the active `vhost.jsonl` and matching `vhost.jsonl.*.gz` files share one logical group ID. The panel enables **Consolidate rotated logs per vhost** by default, so selecting that vhost searches the active and compressed members together as one logical source. Disable the switch to inspect individual files. Physical source IDs and metadata remain separate internally, and the existing global source/byte/line scan limits still apply. - -Gzip reading is bounded before and after decompression. A compressed source larger than 8 MiB is not decompressed for a query, decompressed output is capped at 64 MiB, and only the bounded tail is parsed/searched. These limits prevent a gzip rotation from becoming an unbounded memory/decompression workload. - -Per-vhost directory discovery now retains only the newest **256** matching JSONL/gzip candidates while scanning, and a combined log’s gzip-rotation scan retains only the newest **64** matches. Both caps apply before sorting, so directory size no longer causes a proportional memory allocation for the inventory. The usual 32-source query budget still applies. Filesystem enumeration still takes time proportional to the number of directory entries. - -The reader remains bounded: - -- up to 2 MiB of retained tail data from one source; -- up to 8 MiB across one query; -- up to 20,000 lines across a query; -- at most 32 sources in an all-source query; -- page size at most 500 and offset at most 10,000; -- search input at most 512 bytes and 32 parsed clauses; -- dynamic field discovery at most 64 leaf fields. - -If more than 32 sources are eligible, the newest 32 are scanned and the response is marked truncated. File reads run in a blocking worker rather than the async proxy runtime. - -The explorer is diagnostic rather than a retention/indexing system. RustProxy does not create rollovers or compress files itself; use operating-system log rotation for retention. Existing gzip rotations can be read as described above. diff --git a/docs/HTTP3.md b/docs/HTTP3.md deleted file mode 100644 index 447fc45..0000000 --- a/docs/HTTP3.md +++ /dev/null @@ -1,188 +0,0 @@ -# HTTP/3 transport - -**Uncompiled development source in this environment.** This release adds an actual -QUIC/HTTP/3 adapter, not only an Alt-Svc header. It does not claim independent full-RFC -conformance, interoperability or production performance until the acceptance tests run. - -## Stack and configuration - -The adapter delegates QUIC transport/TLS to Quinn 0.11.12 and HTTP/3/QPACK plus the -Quinn adapter to `hpx-h3 2.5.20` with its `quinn` feature. The crate is imported as `h3` -in source. These are Cargo dependencies, not bundled binaries. -Rustls provides TLS 1.3 for QUIC; SNI certificates come from the existing inventory. -HTTP/1.1 and optional HTTP/2 remain on TCP at the same port. HTTP/3 is on UDP. -There is no HTTP/3 upstream client in this revision: origins still use HTTP/1.1 or HTTP/2. - -Each vhost has `http3_enabled` (default true). When false, RustProxy removes -`Alt-Svc` from responses for that vhost and rejects HTTP/3 requests for it with 421, -while HTTP/1.1 and any permitted HTTP/2 traffic on the selected TLS listener keep -working. The UDP listener is still a listener-level resource; the vhost switch narrows -use of that shared capability rather than hot-unbinding the UDP socket. - -HTTP/2 follows the same UI model through `http2_enabled` (default true). A vhost may -only opt into H2 when one of its selected named listeners enables H2. For TLS, RustProxy -reads ClientHello/SNI before finishing the handshake and selects H2+HTTP/1.1 or -HTTP/1.1-only ALPN for that vhost. The listener remains the capability ceiling. - - -Since 0.5.0, an omitted `http3` enables QUIC with defaults on TLS bindings. -Explicit `http3: null` disables it and is preserved when serialized. An object -enables it with chosen limits. Plain HTTP defaults to no QUIC. In 0.24.3 the -**Enable HTTP/3 over QUIC (UDP)** switch is edited with the TLS endpoint under -**Settings -> Listeners**. The listener row links to **Settings -> HTTP connections --> HTTP/3**, which contains only advertisement, connection, stream and idle-timeout -tuning for listeners where H3 is enabled. Both enablement and tuning remain -restart-required. Configure this only on named `listeners.endpoints[].binding`: - -```json -{ - "address": "127.0.0.1:8443", - "tls": true, - "http2": true, - "http3": { - "alt_svc_max_age": 3600, - "max_concurrent_connections": 512, - "max_concurrent_bidistream": 100, - "max_concurrent_unistream": 100, - "max_idle_timeout": 10 - } -} -``` - -| Field | Meaning and accepted limits | -|---|---| -| alt_svc_max_age | Advertisement lifetime, seconds, 0..604800 | -| max_concurrent_connections | Established/pending accepted connection slots per endpoint, 1..4096 | -| max_concurrent_bidistream | Peer bidirectional streams per connection, 1..1024 | -| max_concurrent_unistream | Peer unidirectional streams per connection, 3..1024 | -| max_idle_timeout | QUIC idle timeout, seconds, 0..86400; zero means no local QUIC idle timeout | - -Three unidirectional streams are required for the peer control and QPACK streams. -These stream limits are NOT global to all listeners. The existing global in-flight -request and application body/header deadlines still apply. Setting QUIC idle to zero -does not disable application deadlines, and the peer can negotiate its own idle timeout. -All changes to socket/ALPN/QUIC transport parameters require restart. - -## Transport behavior and safety - -- Bind UDP before announcing readiness; failure to bind is a startup failure. -- Advertise `Alt-Svc: h3=":port"; ma=N` only on an ACTUAL active TLS+H3 endpoint. - Origin Alt-Svc is stripped so it cannot advertise the origin or a disabled service. -- UDP address validation/Retry, bounded pending handshakes, a 10-second handshake - deadline, per-endpoint permits, stream flow control and bounded application concurrency. -- 32 KiB decoded HTTP field section limit; request content-length checks and rejection - of forbidden connection/framing headers; streamed bodies and trailers. -- Shared routing pipeline: real local socket -> validated authority -> trusted client - address -> ACL -> Tinyauth -> cache -> origin -> response/security/Server policies. -- HTTP cache semantics are shared with HTTP/1.1 and HTTP/2. In 0.27.3, body-less - `HEAD` requests use the same bounded empty-body probe as `GET`, so an initially - unpolled HTTP/3 receive stream does not cause a false `request_body` cache bypass. -- 0-RTT is disabled to avoid replaying writes; no retry to another origin or transport. -- Endpoint active migration is disabled. Missing real local destination data on a - wildcard QUIC connection fails closed rather than substituting a forwarded IP. -- Configuration reload changes SNI certificates for new QUIC handshakes without - changing active socket limits. Graceful shutdown requests GOAWAY then bounded drain. -- Counters: http3_requests, http3_connections, http3_active_connections and - http3_rejected_connections in status/Prometheus; H3 request count also in dashboard. - -A connection receives a maximum aggregate 8 MiB QUIC window and 256 KiB per-stream -window; these limits are not process memory guarantees. Many simultaneous connections -can use substantial memory. Size connection/stream limits to the machine and measure. -No active path migration, 0-RTT, server push, WebTransport or HTTP datagrams are enabled. -RFC 9220 WebSocket Extended CONNECT is experimental and disabled by default per vhost. -RustProxy advertises SETTINGS_ENABLE_CONNECT_PROTOCOL on a QUIC listener only when at least -one vhost bound to that listener explicitly enables `websocket_rfc9220`; request routing -still rejects RFC 9220 for vhosts that did not opt in. Generic CONNECT and other Extended -CONNECT protocols remain unsupported and are answered with 501. The downstream H3 request must -not carry HTTP/1.1-only `Connection`, `Upgrade`, `Sec-WebSocket-Key` or -`Sec-WebSocket-Accept` fields. RustProxy creates a private RFC 6455 key for the HTTP/1.1 -backend handshake; a valid backend `101` becomes downstream `200`, after which H3 DATA -bytes are the WebSocket byte stream. Client FIN half-closes the backend write side; -backend EOF finishes the H3 send side; abnormal/forced termination uses -`H3_REQUEST_CANCELLED`. No side TCP listener or protocol bypass is used. - -### WebSocket bootstrap ordering and diagnostics in 0.16.0 - -For RFC 9220, a validated backend `101` is no longer enough to send the client-facing H3 success immediately. RustProxy first awaits Hyper's backend `OnUpgrade` and obtains the actual upgraded stream. Only then is downstream `200` returned and the H3 DATA bridge started. If backend takeover is cancelled or fails, the client receives a normal 503/502 response instead of `200` followed by an immediate `H3_REQUEST_CANCELLED` reset. - -Access logging can now distinguish this path with `http_version`, `transport`, `websocket_bootstrap`, `websocket_phase`, `websocket_error`, `h3_reset_code` and `upstream_status`. This makes Safari/iOS interoperability failures distinguishable from backend Socket.IO responses and from H3 tunnel resets. Query-string logging remains subject to the normal privacy policy; do not use access logging to capture session secrets. - -The advertised port is the real listener port. Behind port-rewriting NAT, use a -matching externally reachable UDP/TCP port or do not enable this advertisement; there -is no separate external-port override. Open UDP as well as TCP in your firewall/load -balancer. Scope labels do not perform those operations. Clients lacking QUIC or blocked -UDP can still connect using TCP; no guarantee is made about browser protocol preference. - -## H3 full-page navigation to HTTP/1.1 backend (0.31.6) - -The RustProxy logo remains a normal `href="/"` link, not a client-side panel change. On an H3-enabled frontend it produces a new document GET through the proxy. When forwarding an H3 GET/HEAD to an HTTP/1.1 backend, RustProxy now probes for the stream's end for at most 35 ms per initial frame. A finished empty request is passed to H1 with a confirmed complete receive half rather than an unknown body state. Hyper already special-cases H1 GET/HEAD framing; no claim is made that ordinary GET requests were chunked. A nonempty frame and all subsequent bytes are replayed without buffering the whole request. The slow-peer case falls back to regular streaming. - -If `h3.accept()` fails, existing request tasks get the normal bounded drain instead of immediate mass abort. The new diagnostics use status=0 for cancellations (not a real HTTP response), and persist interrupted-response details including `phase=`, `stream_id=`, `method=`, `path=`, `response_status=`, `body_bytes_sent=` and `error=` in the bounded, redacted `error_detail`. These are stored per vhost when file logging is enabled; the normal access logs remain available for successful document and asset GETs. - -**iOS validation procedure:** keep H3 on, click the logo repeatedly on an authenticated panel behind the real RustProxy vhost, and collect timestamps for `http3_peer_cancelled_before_headers`, `http3_peer_cancelled_after_headers`, `http3_response_write_error`, `http3_accept_error`, and the GET `/` and asset access records. Collect browser Web Inspector Network/QUIC trace if a blank document persists. Correlate the **new GET/** status and transmitted bytes with older cancelled streams. Mere cancellation of an obsolete API or SSE stream is not proof that the new document failed. This implementation is not proven to resolve an iOS issue until live tests pass. - -## Browser refresh and stream cancellation (0.31.5) - -A browser may cancel an obsolete H3 stream when the user reloads or leaves a -page, sometimes before its HEADERS arrive. RFC 9114 `H3_REQUEST_CANCELLED` -(and a peer-originated `H3_NO_ERROR` reset) is not itself a server-side HTTP 400. -RustProxy therefore omits that fabricated 400 access-log entry and avoids -adding `H3_INTERNAL_ERROR` to a directly identified cancelled response stream. -Malformed or forbidden headers still yield a protocol-error 400. - -This correction does **not** establish the cause of a blank document after F5. -If the new navigation fails, compare its actual document/asset statuses over H3 -and H2, the configured reverse-proxy Host/Origin/forwarded-proto chain, and any -connection-level QUIC errors. An error record without HTTP method, path and -request headers cannot identify the browser document which failed. Capture -browser network traces and QUIC qlogs with timestamps for live acceptance. - -Per-vhost `http2_enabled`/`http3_enabled` toggles also no longer rotate the global -HOT/COLD cache identity. Origin representation policy and cache invalidation are -unchanged; the frontend protocol remains part of live request routing. - -## Verification gate - -```bash -bash dev.sh check -cargo test --locked --test http3 -- --nocapture -cargo test --locked --test integrations --test listeners -bash dev.sh test -``` - -`tests/http3.rs` creates a real loopback UDP endpoint, a trusted generated test -certificate, a real origin and a Quinn+h3 client. It checks MISS/HIT, body preservation, -Server/diagnostic policy, connection capacity and ALPN HTTP/2/HTTP/1.1 compatibility. -The same integration test explicitly enables `websocket_rfc9220`, then opens an RFC 9220 Extended CONNECT stream and translates it -to a real HTTP/1.1 backend Upgrade, sends a masked WebSocket frame as H3 DATA, half-closes -the client send side and verifies byte-transparent DATA return plus tunnel metrics. Unit -regressions separately reject HTTP/1.1-only proof/framing headers on the H3 side. -It is provided but was NOT run in the packaging environment. The same-stack client -is a regression test, not independent interoperability evidence. - -After applying examples/http3-vhost.json on a disposable test instance and starting -`bash dev.sh mock --port 9001`, use an independently built curl with HTTP3 support: - -```bash -curl --version -curl --http3-only --cacert data/certs/default.pem -D - https://localhost:8443/cache -curl --http3-only --cacert data/certs/default.pem -D - https://localhost:8443/cache -curl --http2 --cacert data/certs/default.pem -D - https://localhost:8443/cache -curl --http1.1 --cacert data/certs/default.pem -D - https://localhost:8443/cache -``` - -Do not substitute `--http3` fallback or `-k` for strict protocol/certificate validation. -Also test independent clients, malformed-frame handling, stream exhaustion, slow -uploads/downloads, IPv6 wildcard destination metadata, cancellation, graceful drain, -certificate rotation, and memory/CPU at configured maxima before exposure. -No full RFC conformance or load-test result is asserted by this source package. - -## Primary references - -- HTTP/3 RFC 9114: https://www.rfc-editor.org/rfc/rfc9114.html -- QUIC RFC 9000: https://www.rfc-editor.org/rfc/rfc9000.html -- QPACK RFC 9204: https://www.rfc-editor.org/rfc/rfc9204.html -- Quinn: https://docs.rs/quinn/latest/quinn/ -- RFC 8441 Extended CONNECT: https://www.rfc-editor.org/rfc/rfc8441.html -- RFC 9220 WebSocket over HTTP/3: https://www.rfc-editor.org/rfc/rfc9220.html -- hpx-h3 2.5.19: https://docs.rs/hpx-h3/2.5.19/h3/ diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 7df4169..9440ee4 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,384 +1,108 @@ -# Installing and updating RustProxy 0.29.1 +# Installation, updates and first start -The scripts prepare a Linux/systemd installation. This source archive is **not a -verified production build**: Rust/Cargo, real CA operations, live systemd startup -and rollback against a real Rust database have not been exercised in the packaging -environment. Perform the acceptance steps below on an isolated server first. +Use maintained packages whenever possible. For custom installations, use the scripts included with this source version; do not combine parts of different releases. -## Official installation repositories +## Supported entry points -Use the project-maintained package repositories as the normal installation/update path: +| Installation | Recommended starting point | +|---|---| +| Linux DEB/RPM | https://rustproxy.linuxiarz.pl/ | +| OPNsense plugin | https://rustproxy-opn.linuxiarz.pl/ | +| Debian 13+ source bootstrap | `install-debian13.sh` from the project repository | +| Manual Linux/systemd install | Compile the application; use `install.sh` and `update.sh` | +| OCI container | Included `Dockerfile`; see [Container](CONTAINER.md) | -- **DEB/RPM:** `https://rustproxy.linuxiarz.pl/` -- **OPNsense plugin:** `https://rustproxy-opn.linuxiarz.pl/` +Package/plugin instructions and maintenance policies are maintained separately from the source archive. -Package-build metadata is intentionally maintained outside this source tree. The Debian source -bootstrap below remains supported for development, custom builds and environments where the -packaged path is not suitable. +## Debian 13+ source bootstrap -## Optional: Debian 13+ one-line source install/update +Review the remote script before execution. On a managed installation, `auto` selects install or update from `/opt/rustproxy/install.json`. ```bash -# auto: first install or managed update +# Initial install or existing managed update wget -qO- https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/install-debian13.sh | bash -# force the update path +# Explicit managed update wget -qO- https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/install-debian13.sh | bash -s -- update ``` -The bootstrap installs build dependencies, installs Rust with rustup for an unprivileged build user, -clones `https://git.linuxiarz.pl/gru/RustProxy` and builds with `cargo build --release --locked`. -Its default `auto` mode calls `install.sh` only when no managed installation exists. When -`/opt/rustproxy/install.json` exists it calls `update.sh`, which uses the saved installation record, -backs up private state, switches the release, verifies health/version and rolls code/data/unit back -on failure. First install keeps administration on `127.0.0.1:9090` over HTTP, matching the normal -`install.sh` loopback-safe default. Updates preserve the saved admin/listener settings. For initial -remote access use an SSH tunnel, then enable Administration HTTPS before persisting a non-loopback bind. -Plain remote HTTP requires both an explicit non-loopback `RUSTPROXY_ADMIN_LISTEN` and -`RUSTPROXY_ALLOW_REMOTE_ADMIN=1`, and should be limited to a trusted private network. +The script installs dependencies, builds with `cargo build --release --locked` as an unprivileged build user, then uses the local deployment scripts. Run it only on Debian 13+ and with access to its required repositories. Installing from a moving `master` branch does **not** pin a specific audited release; use a reviewed commit when reproducibility matters. -Equivalent prerequisite commands are: +## Build from the source + +The current `Cargo.toml` declares **Rust 1.99 minimum**, with a `stable` toolchain in `rust-toolchain.toml`. Use Cargo, a C compiler/linker, Bash and Python 3 (Python 3.11+ is advisable for project checks). Run as a normal build user: ```bash -apt install -y build-essential gcc git ca-certificates python3 python3-venv python3-pip pkg-config wget sudo certbot python3-certbot-dns-rfc2136 python3-certbot-dns-cloudflare python3-certbot-dns-ovh -wget -qO- --https-only --secure-protocol=TLSv1_2 https://sh.rustup.rs | sh -``` - -## Build as a normal user - -Requirements: stable Rust (minimum declared 1.88), Cargo, a C compiler/linker, -Python 3.10+, Bash. Linux/systemd deployment also requires `systemctl`, `runuser` -and a usable non-login service account (created by the installer when missing). -The static TOML checker additionally needs Python 3.11+. No script downloads an -executable or installs a compiler as root. - -```bash -cd RustProxy bash dev.sh doctor bash dev.sh check bash dev.sh test bash dev.sh release ``` -The first build resolves and creates `Cargo.lock`; retain the real lockfile and -binary hash for repeatable subsequent builds. There is no fabricated lockfile or -precompiled binary in the archive. Set `--binary` explicitly when using a custom -`CARGO_TARGET_DIR`. Review and trust the source and built binary before installation. - -## First installation - -`install.sh` also downloads the direct `user-country-ipv4.csv` and `user-country-ipv6.csv` -assets from the `sapics/ip-location-db` latest release over HTTPS, validates their CSV -structure and writes them privately to `DATA_DIR/geoip`. Package/plugin installs may omit -that packaging-time download: after all listeners bind successfully, RustProxy starts an -enabled-by-default GeoIP worker, performs an immediate background refresh and then checks -every 24 hours by default. Country allow-only policies continue to fail closed until a valid -database is available. Settings can use separate IPv4/IPv6 CSV files or one native MaxMind -MMDB file; direct source URLs, optional per-file SHA-256 URLs, maximum file size and interval -are configurable under **Settings -> GeoIP updates**. +`dev.sh release` builds the application; it is not equivalent to a successful production rollout. Cargo dependencies and Swagger assets must be available to the build environment. An example systemd unit is in `deploy/`. +For a first Linux/systemd installation: ```bash bash install.sh --plan --binary target/release/rust-proxy sudo bash install.sh --binary target/release/rust-proxy ``` -Defaults: HTTP `0.0.0.0:80`, HTTPS `0.0.0.0:443`, administration -`127.0.0.1:9090`, code `/opt/rustproxy`, all private state `/var/lib/rustproxy`, -service account `rustproxy`. The initial page is a small informational response; no demonstration -backend or sample application is started. The first database has no configured -vhosts. Existing, nonempty data is refused rather than reset. +Use `--plan` to inspect changes first. The installer refuses existing unmanaged/nonempty state rather than overwriting it. Common options include `--prefix`, `--data-dir`, `--http`, `--https`, `--no-https`, `--admin`, `--allow-remote-admin` and `--no-start` (see `install.sh --help`). -The first install also writes `/etc/profile.d/rustproxy-path.sh`, which prepends the -managed `PREFIX/current` directory (normally `/opt/rustproxy/current`) to `PATH` for -new login shells. Open a new login shell, or source that profile explicitly, before -expecting `rust-proxy` to resolve without its absolute path. +## Defaults and administrator setup -HTTPS is enabled by default and starts with a **self-signed certificate**, not a -publicly trusted certificate. For an explicit HTTP-only installation use: +For the standard managed Linux installation: -```bash -sudo bash install.sh --binary target/release/rust-proxy --no-https -``` -Assign the intended certificate in the panel before exposing applications. Existing -HTTP/3 defaults enable QUIC on TLS listeners: permit the corresponding UDP port -when desired, or explicitly disable H3 in Listeners. Review firewall rules yourself; -the installer does not change them. Public frontend TLS and administration TLS are -independent settings. +| Item | Default | +|---|---| +| HTTP frontend | `0.0.0.0:80` | +| HTTPS frontend | `0.0.0.0:443` with an initially self-signed certificate | +| Administration | `127.0.0.1:9090`, HTTP on loopback | +| Installation | `/opt/rustproxy/current/` | +| Private data | `/var/lib/rustproxy` | +| Service account | `rustproxy` | -Generate the one-time setup capability directly from the installed release CLI: +The first installation has no predefined administrator and no application Vhosts. Obtain the one-time setup token locally: ```bash sudo -u rustproxy /opt/rustproxy/current/rust-proxy \ --data-dir /var/lib/rustproxy admin setup-token ``` -This works before the first server start. After an administrator exists, setup -tokens remain invalid. You can alternatively initialize the account interactively: +Use the setup page, or initialize the account interactively using `admin init`. To access loopback administration from another machine, tunnel over SSH: ```bash -sudo -u rustproxy /opt/rustproxy/current/rust-proxy \ - --data-dir /var/lib/rustproxy admin init +ssh -L 9090:127.0.0.1:9090 operator@server ``` -For direct remote administration, first sign in on the default loopback listener and -open **Settings -> Administration**. Enable HTTPS, choose the application-managed -certificate or a certificate from the inventory, set the bind address (for example -`0.0.0.0:9090`) and optionally add DNS names accepted by Host/Origin validation. -Save and restart RustProxy. The persisted remote bind is accepted only while -administration TLS is enabled. Keep the listener behind a firewall/private network; -the certificate must be trusted by the browser if you want a clean HTTPS connection. +Then visit `http://127.0.0.1:9090` on your workstation. For a directly reachable administration listener, configure **Settings → Administration → HTTPS** and a suitable certificate, restrict the network, save and restart. Do not publish unauthenticated or plain-HTTP administration over the Internet. Public frontend HTTPS and administration HTTPS are separate. -An SSH tunnel remains available without exposing the listener directly: +### Listeners and networking -```bash -ssh -L 9090:127.0.0.1:9090 operator@your-server -``` +- Assign a trusted public certificate to the relevant hosts before exposing them. +- Configure DNS to reach the intended public listener; check IPv4/IPv6 and firewall rules. +- HTTP/3 needs UDP on the HTTPS port as well as TCP for normal HTTPS. +- Listener bindings supplied explicitly via service arguments/systemd are locked against panel changes. Network bind, administration TLS and some monitoring changes take effect after a restart. +- Ports below 1024 need the appropriate OS privileges/capability; use the installed service model rather than running the whole application as root. -Open `http://127.0.0.1:9090` on that workstation while the administration listener -is still using its default HTTP mode. No default administrator/password is added. +## Managed update -Plain-HTTP remote administration is possible with the explicit allow flag: - -```bash -sudo bash install.sh --binary target/release/rust-proxy \ - --admin 0.0.0.0:9090 --allow-remote-admin -``` - -That plain-HTTP path has no transport confidentiality and should be limited to a trusted -private network. Prefer the built-in administration HTTPS mode for a direct remote -listener. - -Other installation switches: `--prefix`, `--data-dir`, `--user`, `--http`, `--https`, -`--no-https`, `--admin`, `--allow-remote-admin`, `--sha256` (expected binary digest), `--no-start`. Paths must be dedicated, -absolute, separate, non-nested and without spaces or symlink components. -`--plan` reads the supplied binary but changes nothing; it does not prove port -availability, compiler success or systemd readiness. - - -### Startup-owned listeners - -When HTTP, HTTPS or administration listen addresses are supplied explicitly by systemd, CLI -arguments or the corresponding `RUST_PROXY_*_LISTEN` environment variables, RustProxy treats -those exact binds as process-owned. Settings shows them as locked and configuration updates -cannot remove, disable or rewrite them. Listeners not supplied at process start remain normal -editable desired-state entries. - -## ACME prerequisites - -Automatic renewal runs inside RustProxy, not a separately installed cron timer. RustProxy -prefers a private Python environment at `/acme-python`. When that environment is -missing or incomplete, the service creates it automatically and installs the pinned Certbot -core plus the Cloudflare, OVH and RFC2136 plugins with `pip`. The application never executes -`sudo` and never modifies distribution-owned Python site-packages. - -The host should provide Python 3 with either the standard `venv` module or `pip`; RustProxy can -bootstrap `virtualenv` into its own data directory when a distribution splits `venv` into a -separate package. OpenSSL remains a local certificate-inspection fallback. If the private -environment cannot be created, RustProxy uses a runnable distribution `certbot` from `PATH` -when one is available. - -If Python is absent (a possible layout on appliances such as OPNsense), or Python is present -but no local environment can be created and no system Certbot exists, the Certificates panel -shows ACME as unavailable. PEM upload and references to existing certificate files remain -available. - -On the standard service installation the managed environment therefore lives at -`/var/lib/rustproxy/acme-python`. Do not run a second Certbot renewal timer over RustProxy's -`certs/acme` profiles. See [renewal](RENEWAL.md). - -A source-tree preflight can still inspect a distribution-provided fallback environment with: - -```bash -sudo -u rustproxy env PATH=/usr/local/bin:/usr/bin:/bin \ - python3 scripts/runtime/acme_provider.py --doctor -``` - -The provider script is embedded in the RustProxy binary; this command is diagnostic only. - -### Using a separate system Certbot - -If another service already owns a certificate through the system Certbot, keep that lineage -outside RustProxy's managed ACME profiles and create/use a **manual** RustProxy certificate id. - -The preferred 0.28.2 workflow is to select **Existing server files / external paths** and then -**External files (absolute paths)** in the Certificates panel. For example: - -```text -Certificate: /etc/letsencrypt/live/example.com/fullchain.pem -Private key: /etc/letsencrypt/live/example.com/privkey.pem -``` - -RustProxy does not copy these files into its data directory. On every targeted reload it resolves -Certbot's `live/` symlinks again, validates the new certificate/key pair and atomically publishes a -new public TLS snapshot. A renewal wrapper can therefore call: - -```bash -curl --fail --show-error -X POST \ - -H 'Authorization: Bearer TOKEN' \ - -H 'X-RustProxy-Certificate-Key: certbot-main' \ - -H 'Content-Type: application/json' \ - --data '{}' \ - https://proxy-admin.example/api/certificates/shared-cert/reload -``` - -The `rustproxy` service identity must be able to traverse the parent directories and read both -files. RustProxy 0.28.2 supports POSIX ACLs and does not require one exact chmod value. External -certificate/key files must not be group/world-writable, and an external private key must not be -world-readable. Named ACL read access for `rustproxy` is accepted; the operating system still -performs the final access check when RustProxy opens the file. Every parent directory in the -configured and resolved path must be owned by root or the RustProxy service user and must not be -group/world-writable (root-owned sticky ancestors such as `/tmp` are the narrow exception). -These external files are not included in a RustProxy backup. - -The packaged systemd service keeps `/etc/letsencrypt` and `/etc/ssl` read-only. `ReadOnlyPaths=` -does **not** grant Unix permissions, so use narrow ACLs instead of making private keys globally -readable. For example: - -```bash -# Traversal only for the protected roots. -setfacl -m u:rustproxy:--x /etc/ssl/ -setfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/ - -# Traversal only for this Certbot lineage. -setfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/ - -# Read only the certificate/key material RustProxy actually needs. -setfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem \ - /etc/letsencrypt/archive/example.com/privkey*.pem - -# Optional default ACL for renewers that preserve inherited ACL masks. -setfacl -m d:u:rustproxy:r-- /etc/letsencrypt/archive/example.com/ - -# Robust renewal-hook form: re-grant read access to the current generation before API reload. -setfacl -m u:rustproxy:r-- \ - "$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)" \ - "$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)" -``` - -Because `/etc/letsencrypt/live/.../*.pem` are symlinks into `archive/`, access must cover the -resolved archive files. Some renewal tools recreate private keys as `0600` and may mask an -inherited default ACL; therefore the renewal wrapper should reapply the named file ACL before -calling the RustProxy reload API. Never grant `w` to `rustproxy` on the Certbot tree. Verify a deployment -with `sudo -u rustproxy test -r /etc/letsencrypt/live/example.com/privkey.pem && echo OK`. - -If you do not want the RustProxy service to read `/etc/letsencrypt`, keep the previous copy/deploy -workflow instead. A Certbot deploy hook can publish every successful renewal into RustProxy's -managed certificate directory: - -```bash -/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \ - --cert /etc/letsencrypt/live/example/fullchain.pem \ - --key /etc/letsencrypt/live/example/privkey.pem -``` - -The deploy command reads the Certbot files locally and sends them to the authenticated loopback -`POST /api/certificates/deploy` endpoint. The running RustProxy service writes a private managed -generation and hot-publishes it for new public TLS/H2/H3 handshakes. Do not use `deploy` for a -certificate ID configured with external absolute paths; renew those files in place and call the -targeted reload endpoint instead. Administration TLS is startup-fixed and may still require a -service restart. - -Optional environment overrides go in a root-owned `/etc/rustproxy/environment` -(mode 0600); create the directory when needed. Restart after changing service -environment values. Provider secrets remain in private profile files, not this -file or the exported configuration. A systemd environment file is not a shell script. - -## Update an installation created by install.sh - -Build and test the new release as a normal user first, then: +Test the new build first. Then execute: ```bash bash update.sh --plan --binary target/release/rust-proxy sudo bash update.sh --binary target/release/rust-proxy ``` -Before stopping the service, the updater downloads the current direct `user-country` -IPv4/IPv6 CSV assets, validates their structure and compares their local SHA-256 with the -installed files. Unchanged files are left untouched; changed or missing assets are staged -and published only after the private-data backup exists. This intentionally avoids requiring -a separately moving publisher-checksum release. The manual updater and the in-process GeoIP -worker use the same `.geoip-update.lock`, so they cannot publish concurrently. Use -`--skip-geoip` only when intentionally accepting a stale/missing country database (for -example during an upstream outage). +Managed updates preserve the saved configuration, create a private backup, switch releases and attempt rollback on failed startup/health checks. **They interrupt traffic and are not a substitute for a separate off-host backup.** Keep your backup password and test a restore on a separate installation. Source-tree/unmanaged migrations require the dedicated stopped upgrade path: see [Migration](MIGRATION.md). -Use the original `--prefix` for a non-default installation. Other startup/account/ -data settings are read from root-owned `install.json`; update does not silently -replace them with command-line defaults. Change intentional listener settings in -RustProxy, or maintain the unit separately when needed. +## ACME prerequisites -Update mode checks that `/etc/profile.d/rustproxy-path.sh` still matches the saved -installation prefix, but deliberately does not rewrite it. A missing or changed file -produces an explicit warning before service downtime; repair the profile separately -if direct `rust-proxy` shell invocation is required. +The managed certificate service can create a private Python/Certbot environment in the data directory; a usable system Certbot may be used as fallback. DNS-01 providers need credentials and appropriate permissions. HTTP-01 needs incoming public TCP `80`. On systems without an available Python/Certbot runtime, PEM import and external certificate references can still be used. See [Certificates](ACME_PROVIDERS.md). -The updater verifies the supplied binary hash and runs only its `--version` as the -service account. Immediately before stopping a RustProxy 0.14.0+ service it creates a -private one-shot managed-update marker. During graceful shutdown the process forces hot -objects into the enabled cold tier even when ordinary `cold_on_shutdown=false`; the marker -is removed before backup. The managed unit allows up to 120 seconds for controlled stop. -If cold storage is disabled, RAM-only cache cannot survive the process exit. The updater -then stages an immutable release, stops the service and backs up the **entire** data directory. This includes SQLite and sidecars, certificates, account -keys, provider credentials, Certbot renewal configs and internal relative symlinks. -Code changes use an atomic `current` symlink switch. It then starts the new process -and requires systemd activity, minimal local `/healthz` readiness, and the actual -MainPID executable matching the staged, locally version-checked binary. The local -health probe follows the persisted administration bind and HTTP/HTTPS mode (including -the configured Host allowlist), rather than assuming the install-time plain-HTTP -address. Version is no longer exposed by the anonymous health endpoint. -The managed systemd unit is regenerated from the saved installation record and -`daemon-reload` is run before startup. The previous unit is included in the backup -and restored on rollback. Keep custom service policy outside this managed unit or -reapply it after an update. +## After installation -A failed start/health check restores the previous release, private data **and** -managed systemd unit. The reported error now includes the original update failure; -recent service status/journal output is emitted before rollback when available. -Failed-generation data is retained alongside the original data directory; the -backup is retained under `/opt/rustproxy/backups`. A failed rollback health check is -reported explicitly. Protect these directories: they contain private keys and -credentials. They are never automatically pruned. Plan enough free space for the -full backup, restored data, retained failed state and old/new executable releases. +Verify `systemctl status rustproxy` and `journalctl -u rustproxy` (managed systemd), the panel login, frontend certificates, first Vhost, real backend response, access policies, optional QUIC, current logs and a full backup/restore test. For error diagnosis, consult [Troubleshooting](TROUBLESHOOTING.md). -During development, if an older saved vhost/settings document is intentionally -incompatible with the current format, use: - -```bash -sudo bash update.sh --reset-config --binary target/release/rust-proxy -``` - -This happens only after the backup is created. It deletes the saved desired -configuration row so the new process creates current listener defaults; administrator -account data, certificates, ACME/provider files and other private state are retained. -Do not use this flag when the old vhost/settings configuration must be preserved. -After a failed update, **do not run `install.sh`**: rollback leaves the previous -installation installed. Fix the reported cause and rerun `update.sh` (optionally -with `--reset-config` for this development-only incompatible-config case). - -An update interrupts traffic; zero-downtime deployment is not implemented. `--no-start` -leaves the service stopped and cannot certify a healthy installation. Backup failure -leaves the old code/data in place and attempts to restart a previously running service. -Keep an independent off-host backup; local rollback is not a disaster-recovery plan. - -## Existing source-tree installations from earlier archives - -Do not point a first installer at your old live data. To keep the current source- -tree deployment, stop RustProxy and use the retained source updater: - -```bash -bash /path/to/new/RustProxy/update.sh --source-tree /path/to/old/RustProxy --stopped -``` - -This mode keeps the old `upgrade.sh` contract and does not install a systemd service -or build a binary. Back up **all** private data and ACME profiles separately before -starting it. See [migration](MIGRATION.md). To adopt the new service layout, make an -explicit offline migration with ownership, absolute Certbot paths and port settings -reviewed; do not merely move a Certbot account tree to another location. - -## Server acceptance - -Check actual non-root HTTP/TLS/QUIC binds, certificate assignment, a real renewal -validation (`Dry run`), backend failures, live traffic, Basic Auth over TLS, ACLs, -WebSocket enable/disable and location isolation. Verify the backup can be restored -on another isolated instance. Run `systemctl status rustproxy` and -`journalctl -u rustproxy` after installation/update. Only then decide on rollout. +**Release verification:** supplied 0.34.0 source changes still require target-platform compilation and functional acceptance before Internet-facing production use. diff --git a/docs/INTEGRATIONS.md b/docs/INTEGRATIONS.md new file mode 100644 index 0000000..9e7dc8e --- /dev/null +++ b/docs/INTEGRATIONS.md @@ -0,0 +1,21 @@ +# Integrations overview + +RustProxy offers built-in integrations and optional helper components. Each has its own permissions and network requirements; enabling an integration does not automatically configure its external service. + +| Integration | Purpose | Setup and limits | +|---|---|---| +| **Docker agent** | Discover running Docker services and publish generated Vhosts | Install separate agent on Docker host; use Bearer token and TLS certificate pin. [Guide](DOCKER_DISCOVERY.md). | +| **Tinyauth** | Delegate authentication for selected Vhosts | Configure verifier URL/login URL, allowed headers and HTTPS. [Access](ACCESS.md). | +| **ACME / Certbot** | Issue/renew HTTPS certificates | HTTP-01 or DNS-01 via Cloudflare, OVH or RFC2136; protect credentials. [Certificates](ACME_PROVIDERS.md). | +| **Prometheus** | Scrape operational metrics | Separate endpoint/token/CIDR rules. [Monitoring](MONITORING.md). | +| **Grafana** | Visualize Prometheus results | Example dashboard under `grafana/README.md`. | +| **Nagios** | External service checks | Sample checks under `nagios/README.md`; configure authorized network/API access. | +| **Nginx Proxy Manager** | Import existing NPM proxy hosts | Included Python dry-run migration, without importing all NPM settings or private certificates. [Migration](MIGRATION.md). | +| **OPNsense certificate deploy** | Deploy certificates from OPNsense ACME plugin | Review `integrations/opnsense-acme/README.md` and use scoped certificate credentials. | +| **RouterOS OCI packaging** | Build an image suited to RouterOS-container environments | Separate experimental packaging under `integrations/routeros-oci-build/README.md`; test the host architecture/network constraints. | + +## Rules of thumb + +Use API credentials with the smallest allowed action/scope and terminate TLS on sensitive network links. The Docker socket must remain confined to the Docker agent host, not the main proxy. Certificate deploy/reload tokens are **not** general administrator API tokens. For imported NPM services, revisit backend TLS verification, frontend certificate assignment, access rules and special Nginx directives manually. + +External compatibility depends on the versions and settings of those external products; this source archive cannot establish acceptance against a live provider or CA. diff --git a/docs/INTEGRATIONS_040.md b/docs/INTEGRATIONS_040.md deleted file mode 100644 index 1c54d27..0000000 --- a/docs/INTEGRATIONS_040.md +++ /dev/null @@ -1,165 +0,0 @@ -# RustProxy 0.4.0 integrations - historical implementation record - -> Historical context. This document records the 0.4.0 integration milestone and is retained -> for traceability. Current behavior is documented in the functional/technical guides linked -> from [README.md](README.md). - -This is source implementation, not a verified binary. Read TEST_REPORT.md before deployment. -The admin panel remains local; public frontend TLS does not make the admin listener remote-safe. - -## Forwarded identity and the trust boundary - -Every origin request now gets canonical `X-Forwarded-For`, `Forwarded`, `X-Real-IP`, -`X-Forwarded-Proto`, `X-Forwarded-Host`, `X-Forwarded-Port` and `X-Forwarded-URI`. -Without configured trusted proxies, incoming proxy headers are discarded. The socket -peer IP is used, so a client cannot supply an arbitrary address in X-Forwarded-For. -Both header formats are emitted; RFC 7239 IPv6 nodes are quoted and bracketed. -There is no ephemeral client port in the representation fingerprint. - -When RustProxy is behind a real reverse proxy, Settings -> Forwarding accepts explicit -IPv4/IPv6 CIDRs (max 32). Do not enter application client networks as trusted proxies. -The last hop must sanitize incoming forwarding headers. `0.0.0.0/0` and `::/0` are rejected. -With a trusted socket peer, X-Forwarded-For is preferred; Forwarded's `for=` chain is the -fallback. The chain is walked right-to-left and anything before the nearest untrusted -hop is discarded. Malformed or oversized chains fall back to the actual peer. -`X-Forwarded-Proto` is honored only with the separate `trust_forwarded_proto` opt-in. -Authority/Host always comes from validated Host or HTTP/2/3 `:authority`, not X-Forwarded-Host. -ACLs and IP affinity use this centrally resolved client address. Listener selection -continues to use the REAL local destination socket and actual TLS state. - -```json -"forwarding": { - "trusted_proxies": ["10.87.7.10/32"], - "trust_forwarded_proto": true -} -``` - -That address is illustrative, not a default. Leave the list empty for direct clients. -RFC 7239: https://www.rfc-editor.org/rfc/rfc7239.html - -## Tinyauth per vhost - -Open Vhosts -> Edit -> Authentication. Enable protection, enter the verification -server and the public login URL, allow a private verifier explicitly if needed, -and save. An origin URL automatically appends `/api/auth/nginx`; an explicit URL -path remains unchanged. No public `/tinyauth` proxy route needs to be created. - -The following matches the supplied nginx-style setup: - -```json -"tinyauth": { - "enabled": true, - "url": "http://10.87.7.99:3000/api/auth/nginx", - "login_url": "https://auth.linuxiarz.pl/login", - "allow_private": true, - "address_allowlist": ["10.87.7.99/32"], - "connect_timeout_seconds": 5, - "timeout_seconds": 10, - "request_headers": ["cookie", "authorization", "x-api-key", "user-agent", "accept"], - "response_headers": ["remote-user", "remote-email"], - "redirect_forbidden": true -} -``` - -Verification is an internal body-free GET, carrying canonical forwarding headers, -X-Original-URL and X-Original-Method. Original method, path and query remain available -to Tinyauth, while the upload body is sent only to the application after authorization. -The RustProxy admin session cookie is never passed to the verifier or application. -Only 2xx approves access. Selected Remote-* / safe X-* response headers become trusted -identity headers. Incoming Remote-* and configured identity headers are removed, -including after administrator request-header rules. Missing identity fields are not -filled from client values. Transport/framing/forwarding fields cannot be selected -as identity headers. Each direction supports at most 32 names; approved identity values are bounded -to 8 KiB in total. - -401 redirects to a validated X-Tinyauth-Location on the configured login origin, -or constructs `login_url?redirect_uri=`. -403 stays 403 unless the provider returns an allowed same-origin redirect and the -403-redirect option is enabled. Cross-origin locations are rejected. Verifier errors -fail closed (502/504), not open. Auth denial/error is not replaced by custom error pages. -Public login URLs require HTTPS; HTTP is allowed only for loopback test servers. -Additional CA PEM can extend trust for Tinyauth HTTPS verification; there is no -verify-off switch for the authentication verifier. The per-backend `tls_verify` option -does not apply to Tinyauth. -Configure Tinyauth itself to trust the ACTUAL RustProxy source IP, not 0.0.0.0/0. - -Authorization runs before origin connection, static pages, WebSocket upgrade to the -origin and every cache lookup. Authenticated vhosts always BYPASS the internal shared -cache and emit `Cache-Control: private, no-store`, even when their cache toggle is on. -This intentionally also disables privacy-sensitive compression through the existing -predicate. Login/session validity is NOT cached. Revocation is checked on each new -HTTP request or WebSocket handshake; an already upgraded socket is not periodically -reauthenticated. There is no automatic replay of POST or failed upgrades. -Do not enable this protection on the Tinyauth login vhost itself (login loop). - -Provider reference: https://tinyauth.app/docs/guides/nginx-proxy-manager/ -Header reference: https://tinyauth.app/docs/reference/headers/ - -## Named listeners - -Settings -> Listeners owns every proxy socket. Each endpoint has a stable ID, display -name, scope label, enabled state and a binding with numeric IP:port, TLS, HTTP/2 and -optional HTTP/3 parameters. Scope labels do not install firewall rules. - -Vhosts reference endpoints only through `frontend.listener_ids`. Inline frontend -listener definitions and the old global listener-pair compatibility fields are not -accepted in 0.8.0. A named endpoint can be shared by multiple vhosts; same-socket -transport settings must agree. Disabled endpoints create no socket and grant no vhost -binding. Unknown/duplicate IDs and admin-port conflicts are rejected. - -Listener transport changes require restart. The panel shows desired and actually bound -endpoints separately. A wildcard bind never widens a vhost's own destination-IP match. - -## Certificate library - -Certificates is an independent sidebar section. Import a PEM certificate/full chain -and PEM private key, give the entry an ID, specify domain names to validate the pair, -and save it to the inventory. In Vhost -> SSL certificates, select the saved entry. -Import does not add public SNI names by itself; active vhost assignment supplies names. -The advanced filename editor remains for CLI-managed existing pairs and direct SNI maps. -The library shows assignment and default status. ACME/Let's Encrypt and expiry renewal -are not implemented; the inventory/API boundary is ready for that future work. - -Uploads: at most 128 KiB per file, key matching/name validation, immutable randomly -named private files, limited parallel imports and mode 600 on Unix. Config stores -filenames and IDs, never PEM private key bytes. File inputs are cleared after import. -Failure between staging and configuration publication may leave unused private files; -they are not automatically activated or deleted. Removing inventory metadata does not -delete key files. Back up the cert directory together with SQLite. -Existing referenced/default entries cannot be removed in the UI without unassigning them. -Certificate changes affect new handshakes; existing sessions continue normally. -The resolver is shared by listeners: one DNS name cannot use different certificates -on different destination IPs. Conflicts are rejected rather than silently chosen. -HTTP-only startup also prepares the private certificate directory, but creates no TLS -socket unless configured. No existing certificates are overwritten. - -## Response policies and cache on extra frontends - -Global and per-vhost `server_header` accept `preserve`, `hide`, `custom` plus `value`. -Omitted/null vhost policy inherits the global choice. Final policy applies to origin -responses, HITs, local pages and errors for inline/named TCP/TLS/H3 listeners. -Generic header rules run before this dedicated Server policy. - -As of 0.18.8, `diagnostic_headers` exposes only the request-ID switch: - -```json -"diagnostic_headers": { - "request_id": false -} -``` - -There is no revision response-header switch or special `X-Rust-Proxy-Revision` handling. If an upstream or ordinary response-header rule supplies that name, it behaves like any other non-protected response header. -Request IDs remain available internally/upstream and in logs. X-Mock-Origin is preserved and can -be removed with ordinary global or per-vhost response-header rules. An explicit vhost diagnostic -object overrides the request-ID choice; null inherits. - -Cache keys now include the actual frontend IP:port and stable canonical forwarding -metadata. Empty HTTP/2/HTTP/3 GET bodies without an exact length are checked by peeking -at most eight frames, skipping empty DATA frames; nonempty data/trailers are -replayed to the origin without buffering the full body. -A stream with data/trailers bypasses cache. Reusing the same socket with a different -client source PORT no longer changes the forwarded representation. -Client IP still affects the conservative all-header fingerprint, so different clients -can have separate cache entries. Auth, cookies, no-store, private, upgrades and partial -bodies still bypass caching. Cookies created by a login or the sticky-session feature -are not a cache bug. Close/consume the entire response before expecting a stored HIT. diff --git a/docs/LIVE_LOGS.md b/docs/LIVE_LOGS.md deleted file mode 100644 index 43bc66f..0000000 --- a/docs/LIVE_LOGS.md +++ /dev/null @@ -1,55 +0,0 @@ -# Interactive live logs - -RustProxy provides an operator CLI that follows structured request/error events from every HTTP vhost: - -```bash -rust-proxy --livelogs -``` - -The command connects only to the loopback administration listener and creates a bounded local CLI session from the private RustProxy database. When `./data/config.sqlite3` is absent, the bare command discovers common installed layouts: `/var/lib/rustproxy` is preferred on Linux/non-BSD systems and `/var/db/rustproxy` on BSD/OPNsense-style installations, with the alternate convention checked as a fallback. An explicit `--data-dir` always wins and custom installations should pass the same path used by the service. `--admin-listen` can select a non-default loopback port. - - -## Administration HTTP/HTTPS transport - -The CLI first probes the loopback administration `/healthz` endpoint over HTTP. An HTTP-only administration listener stays on HTTP; the `308 Permanent Redirect` emitted by a TLS-enabled administration listener switches the CLI to HTTPS before a CLI session cookie is sent. HTTPS is pinned to leaf certificates present in the private `/certs` directory, so a configured self-signed administration certificate does not require a system trust-store installation. - -This fixes the previous reconnect loop where `--livelogs` treated the TLS listener's expected HTTP-to-HTTPS `308` response as a stream failure. The same local transport selection is used by CLI config and certificate API operations. - -The live path is separate from configured file/syslog sinks and from the `/logs` stored-log explorer. Request processing publishes ordinary routed request records into a bounded Tokio broadcast channel without waiting for viewers. Low-level malformed HTTP/TLS/HTTP2/HTTP3 protocol failures introduced in 0.26.0 are intentionally persistent-sink-only and do not enter this Live Logs ring, so scanner noise cannot evict normal live request history. A slow viewer receives an explicit `gap` event with the number of entries lost from that viewer's live queue. The CLI also consults the retained history after connecting/reconnecting and de-duplicates entries by request id. Each request carries a fresh, cryptographically unpredictable 256-bit (64-hex-character) `request_id`, generated by a thread-local CSPRNG and mixed with a per-process sequence; the same protected value is forwarded to the backend as `X-Request-ID` for correlation. From the OPT5 DEV revision onward, this format no longer requires a separate per-request SHA-256 operation. - -## User-Agent, country and Traffic analytics - -Structured access entries expose `user_agent` and `country` as selectable source fields in addition to the standard entry. User-Agent capture is enabled by default and is bounded before it enters retained logging/analytics state. Country capture is disabled by default. Normally country resolution uses the local GeoIP database; for a vhost with verified Cloudflare client-IP mode, RustProxy first accepts a valid single `CF-IPCountry` value from a direct Cloudflare edge and falls back to GeoIP of the verified effective client IP. With country capture disabled, country resolution is kept off the normal request path unless a configured country access rule or country rate-limit already requires it; in those policy cases resolution still occurs for enforcement, but the country is not retained in access logs unless capture is enabled. - -The authenticated **Traffic** workspace aggregates completed requests per vhost without database writes. The request path uses a bounded non-blocking queue, and high-cardinality dimensions use fixed-capacity approximate top tables. The page shows aggregate requests, 5xx errors, request/response bytes and mean duration plus top countries, User-Agents, effective client IPs, paths, routes, methods, status classes and cache outcomes. Donut charts summarize countries, User-Agents, client IPs, methods, status classes and cache outcomes while the bounded tables retain exact displayed labels/counts from the approximate TOP state. The lists are process-lifetime diagnostics, are not persisted across restart, and may drop analytics samples under overload rather than applying backpressure to proxy traffic. Country rankings are populated only while country capture is enabled. Client-IP rankings use the same verified effective client identity used by ACL/logging after trusted-proxy processing. - -## Interactive commands - -No regex language is required. Type commands while the stream is running: - -```text -host api.example.com -vhost api -backend origin-a -route users -client 192.0.2.10 -method GET -transport h2 -cache MISS -status 5xx -path /api/ -errors on -slow 250 -detail on -filters -show 12345 -pause -resume -reset -clear -quit -``` - -Use `*` to clear a scalar filter, for example `backend *`, and `slow off` / `errors off` for boolean filters. `show ID` prints the complete retained structured event for that request. `detail on` expands the continuously printed view with route/upstream/cache/transform/WebSocket/H3/byte metadata. - -The server revalidates the streaming session every 10 seconds rather than writing session state for every request. Password changes, session revocation or expiry therefore terminate an existing stream within that bounded interval. diff --git a/docs/LOGS.md b/docs/LOGS.md new file mode 100644 index 0000000..0bd6c4a --- /dev/null +++ b/docs/LOGS.md @@ -0,0 +1,50 @@ +# Live logs, stored logs and error logging + +RustProxy has several independent logging paths. Choose the one matching the question; enabling stored file logging is not required to view recent in-memory requests. + +## Live Logs + +Use **Logs → Live Logs** in the authenticated panel, or locally: + +```bash +rust-proxy --data-dir /var/lib/rustproxy --livelogs +``` + +The CLI connects to the local administration listener and follows structured request events. It can filter by host, Vhost, backend, route, method, client, protocol, status, cache state and latency; use `filters`, `pause`, `resume`, `detail on` and `quit` in the interactive view. It adapts to a loopback administration HTTPS listener where configured. + +Live viewing is **bounded in memory**. Slow consumers may receive a gap/loss indication and are not allowed to block proxy traffic. Low-level malformed HTTP/TLS/H2/H3 protocol errors can be sent only to persistent sinks rather than appearing in the ordinary live request list. A request ID (`X-Request-ID`) helps correlate an application response with proxy records. + +## Stored Logs + +The authenticated **Logs → Stored logs** page reads the configured structured JSON/JSONL access logs, optional per-Vhost files and optional error log. These files can remain readable even when their current writer is disabled. The explorer supports paging, dynamic columns, sorting, record expansion/Raw JSON and repeated-value analysis. + +Search uses a restricted case-insensitive query syntax, for example: + +```text +status:>=500 AND path:/api/* +client_ip:192.0.2.* AND status:5xx +method:POST AND !country:Poland +error:"connection reset" +``` + +Plain words search stored values; quotes allow spaces, `!` negates a clause, `*` matches wildcards and numeric operators work on supported fields. **`OR` is not supported.** Searches and repeated-value statistics are bounded by server scan limits; do not interpret a truncated result as the entire historical archive. + +## Error and protocol logs + +RustProxy can persist warnings/errors and selected protocol failures separately from access traffic. Set output level, sink and optional retention/rotation under **Settings → Logging** and related error-log controls. Access logs may use the global structured file or per-Vhost locations. Log paths must be writable by the service identity, including when running inside a container/systemd sandbox. + +The installed Linux deployment may configure `logrotate` for durable logs; check the installed policy rather than assuming all platforms rotate automatically. Logging is bounded/asynchronous where practical, and some records can be dropped under saturation; inspect sink drop/error counters in monitoring. + +## Privacy and investigation + +Logs can contain client IPs, URLs, application hostnames and timing metadata. Limit access and retention. RustProxy masks selected known credential fields, but avoid deliberately logging secrets or personal data in query parameters and custom headers. The administration API and stored-log search remain behind authentication. + +For common symptoms: + +- **502/503/504:** correlate request ID, Vhost and backend health with upstream timeout/connect/TLS errors. +- **403/405/429:** inspect ACL, method rule, country or rate limiting. +- **Unexpected cache result:** inspect optional `X-Cache` diagnostics and Cache dashboard alongside request logs. +- **Files empty:** enable the correct sink and check permissions, path and rotation state. +- **Protocol failures missing from Live Logs:** inspect configured persistent error/protocol logs instead. + +Also see [Monitoring](MONITORING.md) and [Troubleshooting](TROUBLESHOOTING.md). diff --git a/docs/MANAGEMENT.md b/docs/MANAGEMENT.md deleted file mode 100644 index 3c9a65f..0000000 --- a/docs/MANAGEMENT.md +++ /dev/null @@ -1,139 +0,0 @@ -# Vhost management - historical 0.4-era implementation record - -> Historical context. This document is retained for implementation traceability and must not -> be used as the current vhost/operator guide. Use [VHOSTS.md](VHOSTS.md), -> [CUSTOM_LOCATIONS.md](CUSTOM_LOCATIONS.md), [VHOST_ACCESS.md](VHOST_ACCESS.md) and -> [WEB_SERVER.md](WEB_SERVER.md) for current behavior. - -This guide describes implemented source behavior. Read TEST_REPORT.md before -acceptance: the packaging environment did not compile or run the Rust backend. - -## Compact operations - -Vhosts opens a compact list with domains, bound sockets, backend count and live -health/latency. Edit opens a modal with Details, Backends, Health, SSL, Cache, -Security, Pages, Authentication and Advanced tabs. Existing source/ACL JSON remains available. -The structure is inspired by proxy-host managers, not a claim of full feature -parity with nginx-proxy-manager. Density is configured in Settings and stored only -as a browser preference. Logo and favicon have not been redesigned. - -## Optional certificate upload - -In Certificates, upload certificate/full chain PEM and an unencrypted private-key -PEM. The pair is cryptographically validated and written to private immutable files; -the form then saves inventory metadata through revision CAS. No vhost or DNS name -is required just to import a certificate. In a separate vhost save, choose its ID -in SSL; validation checks that it covers the host's configured DNS names. -The file inputs clear after the request. Failed/stale inventory saves can leave -unbound staged files; no key bytes are returned by the API or stored in the config. -Clear/inherit uses the global SNI/default behavior. - -Limits: 128 KiB per file, 300 KiB JSON request, up to 16 supplied DNS names, two concurrent -upload workers, 64 configured certificate inventory entries and 128 staged/upload -pairs on disk. Canceled uploads remain staged; no automatic deletion of potentially -active keys is attempted. New uploads eventually stop at that limit. Perform -manual cleanup only after backup and confirmation that neither the saved -configuration nor the serving snapshot references those files, preferably stopped. -The configured-certificate backup does not collect unused staging files. - -Certificates must cover the configured DNS names. This does not prove browser trust, -chain completeness, revocation status or remaining lifetime. ACME-issued certificates -are shown as managed/read-only inventory entries; they are renewed through the ACME -workflow rather than edited as uploaded custom PEM files. New issuance selects the -production CA environment by default but still requires explicit production confirmation; -staging remains available for testing. A certificate can be deleted only after it has -been unassigned and saved everywhere, including vhosts, redirects, 404 hosts, the -default TLS certificate and the administration panel. Encrypted-key password prompts -are not supported. The single SNI resolver rejects different selected certificates for -one DNS name on different IP bindings. Socket changes still require restart; changing -a certificate for an existing listener uses hot snapshot publication after Save. -Requests already in flight are not interrupted. - -## Cache and response headers - -Cache selectors distinguish inheritance, on and off. Blank numeric fields inherit. -TTL/object limits, query/path exclusions and optional diagnostics are configurable -per host. The memory/entry ceilings are shared globally; one host cannot increase -its object ceiling above the global limit. Explicit per-vhost enable is permitted -when the global cache is explicitly disabled. Cookie-affine pools and path cache=false still -bypass. Purge on a host clears its entries and invalidates its in-progress fills, -without rewriting configuration or evicting other hosts. Changing configuration -still performs a full invalidation. - -Security includes visual rules for headers sent to the backend and to the client, -and per-vhost HSTS/nosniff/frame/referrer/CSP/permissions policies. Advanced JSON -edits update the visual rows. Global, vhost and path header rules run in order; -existing final Server/security/protocol safeguards retain precedence. In -particular, a hidden Server cannot be restored by an ordinary header rule. An -explicit per-vhost Server override can replace the inherited global Server policy. - -## Custom pages - -Global Settings / Pages supplies error defaults, direct-IP and unknown-host -responses. Vhost / Pages supplies an optional static response, a missing-path -fallback and error overrides. Modes are inheritance, built-in congratulations, -empty and administrator HTML (typed or loaded from an HTML file). - -Supported error keys: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503 and 504. -Keep the original status by leaving status unset; an explicit status is allowed -from 200, 204, 400, 401, 403, 404, 408, 410, 413, 429, 500, 502, 503 or 504. Only empty -mode permits 204. HTML is at most 32 KiB UTF-8 per page and the complete serialized -configuration remains bounded to 256 KiB. No CSS/image asset hosting or directory -serving is provided; use self-contained HTML or an upstream for multi-file sites. -The built-in congratulations text is English; replace it with your own HTML for -other languages. RustProxy-generated 503 and 504 failures use matching built-in dark -HTML pages when no administrator override is configured; vhost/global custom error -pages still take precedence. An origin's own 503/504 response is not rewritten unless -a custom error override is configured. Admin interface translations still come from -language packs. - -Request behavior: - -1. Validate authority/path and real local binding; check applicable host/path ACLs - and enabled Tinyauth authentication before serving any content. -2. Use the explicit direct-IP page for a literal IPv4/IPv6 authority, if enabled. -3. Otherwise use the matching vhost's static response, if configured. -4. If no path is selected, use that vhost's missing-path fallback; if there is no - matching vhost, use the global unknown-host fallback. Otherwise keep normal 404. -5. Forward normally when a route exists, then replace a configured error with its - vhost override or global default. Apply the normal response/security policy. - -Static/fallback endpoints accept GET/HEAD; other methods get 405 with Allow. -HEAD has the selected response headers and no body. Default status for a static -vhost/direct-IP page is 200; a missing-path/unknown-host fallback retains 404 unless -explicitly changed. Error responses retain their original status by default. -Already-authored pages are not rewritten a second time by error rules. Replacement -drops old encoding, length, cookies and other representation metadata, while -preserving authentication challenges, Retry-After and Allow. Pages use no-store. - -ACL denial always prevents upstream access, even when its 403 page is configured -to return 200. A success status for an authentication/server error can mislead -clients and monitoring. Prefer preserving original statuses. Direct-IP HTTPS -must still finish TLS; this HTTP feature cannot bypass certificate validation. - -HTML is not previewed in the admin DOM. It is trusted administrator content and -may execute scripts when served to visitors. Never import untrusted HTML. - -## Separate live-log window - -Use Live logs in the toolbar or a host's Logs button. Choose modal or docked mode; -filters remain above a scrollable table. Filters combine vhost, method, status -family, cache/error outcome and text (host, path, method, vhost, backend). Pause -freezes the displayed bounded snapshot, not collection. Resume uses the latest -snapshot. Export contains exactly the selected rows. Closing/reopening keeps -filters; signing out clears viewer state. - -The source is a 256-entry in-memory access ring refreshed through SSE/polling. -It is not an unbounded tail, durable file, guaranteed replay or audit store. The -vhost field comes from the actual selected frontend and remains available for -cache/static responses, without fabricating a backend request. Pool health data -are live observations, not settings; disabled checks are not proof of liveness. - -## Additional 0.4.0 controls - -The Authentication tab enables Tinyauth. Security includes inherited/per-vhost -Server and diagnostic header settings. Details selects named listeners shared -with other vhosts; inline listeners are not part of the current configuration model. Certificates are managed in -the separate inventory rather than uploading keys from each vhost editor. -The toast moves into the active dialog/top layer so its message and action remain -visible and interactive above the backdrop. See INTEGRATIONS_040.md for contracts. diff --git a/docs/MEMORY_STRESS.md b/docs/MEMORY_STRESS.md deleted file mode 100644 index e6fa664..0000000 --- a/docs/MEMORY_STRESS.md +++ /dev/null @@ -1,91 +0,0 @@ -# Long-running memory verification (RustProxy 0.30.0) - -This procedure supplements static source checks. A synthetic Python model and a -local Python HTTP peer **do not execute RustProxy** and cannot establish its -production RSS, heap growth or absence of leaks. - -## Audit coverage - -| Area | Source-level finding | Action in the same 0.30.0 version | -| --- | --- | --- | -| Selective cache PURGE | Per-key generation metadata could retain every distinct purged key indefinitely after its object was removed | Reset stale per-key metadata at the existing global generation barrier; drop an abnormally large map allocation; regression for 512 different purges | -| HOT/COLD cache | Body/entry limits, bounded transient leases, limited cold queue, revalidation slots and background cleanup permits | Kept existing behavior | -| Log output (JSONL/file/Logstash/Loki) | Serialized the same entry again when Loki and a JSONL sink ran simultaneously; sink queues and per-vhost open files were already bounded | Reuse serialized JSONL content for Loki; keep redaction and sink-specific formatting | -| Historical log discovery | Prior audit already capped directory inventory while scanning, at 256 per-vhost and 64 gzip entries | Retained existing 0.30.0 limits | -| Traffic analytics | Top lists limited to 32 entries per dimension, at most 64 vhosts and a 2048-item input queue, but repeated labels incurred transient `String` allocations | Borrow existing labels, allocate only when admitting/replacing a top-list entry | -| Local directory listing | Two simultaneously live vectors for directory metadata and a copied HTML response | Filter directory metadata in place; move output bytes into response; keep all filenames and HEAD Content-Length behavior | -| TCP/UDP streams and HTTP/H2/H3 | Connection/session permits, finite queues, idle deadlines and completed task draining | No change | -| Upstream clients, DNS, health probes | DNS worker limit, timeouts, pool recycling, bounded health checks | No change | -| Compression, transforms, PHP-FPM | Streaming response paths, transform memory and concurrency budgets, FastCGI streaming | No change | -| Rate limits, ACME, Docker, background tasks | Limited client buckets with expiry and shrink, cancelled/limited background activities | No change | - -This is **not** a proof that every runtime allocation is released. The directory -listing feature intentionally generates the complete visible directory as HTML; -very large directories may still need substantial temporary memory. A high RSS -after a load spike may also be allocator reuse instead of an active leak. Verify -with real-runtime measurements rather than assuming either explanation. - -## Source/synthetic verification - -```sh -./dev.sh check -./dev.sh test -# Perform separate real-runtime load and RSS measurements on staging. -``` - -The old Python cardinality model and protocol-peer load driver were removed -with `tools/checks/`. They were synthetic tests, not proof of RustProxy memory -bounds. `./dev.sh test` now runs only the Rust suite; runtime memory behavior -must still be verified by sustained staging traffic and RSS tracking. - -## Real RustProxy staging traffic and RSS - -On a Linux staging machine with Rust/Cargo installed, configure a vhost and a -known reachable backend/local Web Service. Enable whichever logging targets you -need to test (JSONL file, Logstash, Loki) in the normal dashboard. Start the -proxy and determine the **RustProxy** PID, not the load generator's PID. - -```sh -./dev.sh check && ./dev.sh test -# In another terminal: run the configured instance, e.g. ./dev.sh run -pid=$(pgrep -n -x rust-proxy) - -# Warm / repeated URL: cache HITs, persistent clients, normal JSON access logs. -python3 tools/dev/memory_load.py \ - --url http://127.0.0.1:8080/ \ - --host-header example.test \ - --pid "$pid" --requests 400000 --concurrency 128 \ - --cooldown 120 --output /tmp/rustproxy-memory-repeat.json - -# High cardinality: many URL paths, cache misses, large analytics churn. -# Pick a staging path prefix accepted by your origin; status codes will be -# recorded even when the requested URLs return 404. -python3 tools/dev/memory_load.py \ - --url http://127.0.0.1:8080/ \ - --host-header example.test \ - --pid "$pid" --requests 400000 --concurrency 128 \ - --unique-paths 100000 --cooldown 120 \ - --output /tmp/rustproxy-memory-distinct.json -``` - -Run at least three **successive** traffic/cooldown cycles on the same process. -Test separately with access JSONL only, JSONL+Logstash and JSONL+Loki using -reachable staging services; add streaming large response bodies, directory -listings, TCP/UDP and HTTP/3 using appropriate specialized clients. Send -controlled cache PURGE requests through your authenticated cache invalidation -API and ensure previously purged cache entries never reappear. - -The tool samples `/proc//status` (RSS, high-water mark, threads) and -`/proc//fd` at a configurable interval. Output includes response codes, -requests/second, received bytes, time series and start/peak/end RSS. Check -steady-state RSS after each cooldown, open file descriptors, timeouts, dropped -logs, HIT/MISS behavior, and whether growth **continues** across periods of -similar load after caches and connections have stabilized. In addition, inspect -cache/transformation memory accounting and capture a Rust heap/allocation -profile on the actual instance if RSS rises repeatedly. - -Do not run these load tests against an active production service without a -maintenance or capacity plan. The load driver does not create vhosts, backend -services, log collectors or a RustProxy executable, and it cannot reproduce -all TCP, UDP, H2 and H3 protocol behavior. None of these real-instance -acceptance steps were performed in the source-only audit environment. diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index 9a67967..b1549ab 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -1,535 +1,63 @@ -# Deployment and configuration migration history +# Upgrades and migration -## 0.32.3 -> 0.32.4 Preserve Host and Dashboard gutters +This guide covers changes that need operator action when moving to RustProxy 0.34.0 and the included Nginx Proxy Manager importer. For ordinary package upgrades, first follow [Installation and updates](INSTALL.md). -No SQL schema, saved vhost JSON, account preferences or widget IDs change. -`preserve_host: true` now works with `http1`, `auto`, `http2` and `h2` instead of -being rejected for the latter three. If an HTTPS backend is addressed by IP but -serves multiple vhosts, retaining the visitor Host also sets HTTP/2 `:authority` -to the public host, while TCP/DNS use the configured URL and optional -`tls_server_name` sets SNI/certificate identity. Keep a separate -`health_check.host` for HTTP probes that require the vhost name. The debugging -H1 preset remains optional, and original Overview widget preferences survive. -Check TLS verification, hostname/certificate coverage and Angie handling on a -real staging instance before deployment; the Rust compiler acceptance gate -is still open. +## Before changing versions +1. Record installed version, platform, current listener binds and external certificate paths. +2. Create a password-encrypted off-host backup and confirm its password is available; separately copy external cert/key and integration state. +3. Verify the target source and dependencies, compile and run tests on a staging copy when possible. +4. Read release notes for cache compatibility, configuration changes, API token permissions and listeners requiring restart. +5. Update with a maintenance window. Test production DNS, TLS, Vhost routes, access, cache behavior, PHP, Docker, Streams, logging and backup recovery before considering the upgrade complete. -## 0.32.2 -> 0.32.3 upstream protocol and Dashboard compatibility +A managed install uses `update.sh`; an unmanaged source-tree install must follow the supplied stopped upgrade path. Never overwrite an active database by extracting a ZIP over live private data. RustProxy normally applies forward SQLite schema migrations as needed; do not manually edit internal migration tables. -No database or backend JSON rewrite is required. In 0.32.2 an HTTPS origin -saved as `"protocol": "http2"` could negotiate HTTP/1.1 via TLS ALPN. The same -value **still** permits H2/H1 over TLS in 0.32.3 and still uses H2C on plaintext -HTTP. To enforce HTTP/2 only, explicitly select new `"protocol": "h2"`. -Existing `http1` and `auto` values, HTTP/1.1 WebSocket Upgrade, configured -keepalive and pool settings retain their meaning. +## Notable compatibility points -The dashboard registry grows to 24 IDs. Eight HTTP backend cards are optional, -hidden by default for all accounts until enabled; old ordering and hide/show -preferences survive. These use existing status data with no extra background -polling or schema migration. Verify live behavior and WebSocket negotiation on -a Rust-enabled staging environment before deployment. +| Change area | What to review | +|---|---| +| **0.34.0 cache** | Operational Vhost saves aim to retain compatible HOT/COLD cache. Content-affecting changes invalidate a scope; older disk cache objects can require refilling. Validate actual behavior before rollout. | +| **0.34.0 Web Server** | `sendfile=auto` is an optional Linux cleartext HTTP/1.1 path; default is `off`. No existing service needs to enable it. | +| **0.32.x origin protocols** | Legacy backend `http2` remains compatible on TLS (H2/H1) and H2C for plain HTTP. New strict `h2` must be set explicitly. | +| **0.33.x Overview** | Dashboard presets/layouts are per administrator, independent from Vhost configuration; existing preferences should be retained and new widgets may remain hidden. | +| **Certificate API grants** | Older broad/reload-only credentials can exist after additive migration; replace them with explicit certificate-ID allowlists and least privilege. | +| **PHP-FPM** | Distinct applications with different PHP INI overrides need separate external FPM pools and a worker restart; RustProxy cannot isolate FPM workers itself. | +| **Legacy custom root location** | A former untouched synthetic `/` location may normalize into the main Vhost fallback; a deliberately customized `/` rule remains an override. | +| **External Certbot files** | File-backed certificate paths stay external; backups and restores do not include those bytes. Check symlink and permission changes after renewal. | -## 0.28.1 -> 0.28.2 +Restore of configuration history changes **only configuration**, not a full application state snapshot. See [Backup](BACKUP.md) for disaster recovery. -SQLite schema 8 is upgraded automatically to schema 9. The additive migration creates `certificate_api_grants`. Existing Certificate API credentials become reload-only compatibility credentials covering all certificate IDs (`*`); rotate them in **Settings -> Certificate API** to replace the wildcard with explicit certificate IDs. New credentials require 1-32 explicit certificate IDs and are reload-only unless deploy permission is enabled. +## Nginx Proxy Manager (NPM) import -The saved proxy configuration document and COLD cache format are unchanged. External certificate paths keep their 0.28.1 semantics, but 0.28.2 validates the full configured/resolved parent chain and accepts POSIX ACL read access without requiring an exact chmod value. The packaged systemd service marks `/etc/letsencrypt` and `/etc/ssl` read-only; Unix permissions/ACLs still need to grant the `rustproxy` user the required traversal/read access. +The included `scripts/migration/migrate_npm.py` reads NPM's SQLite database and prepares RustProxy Vhosts. **It does not modify NPM or migrate its private TLS keys.** Keep `migrate_npm.py` and its two sibling helper modules together when running outside this source checkout. -## 0.26.1 -> 0.26.2 +What can migrate: NPM proxy-host domain names, upstream scheme/host/port, basic enabled state, WebSocket and force-HTTPS policy, and supported frontend listener selection. The importer reports more complex access lists, custom Nginx locations/directives, caching, exploit rules and certificate details **for manual review**. Private/backend origin policy and upstream TLS verification must be explicitly checked after import. -No SQLite or saved-configuration migration is required. Version 0.26.2 changes only administration/log-explorer behavior and account-scoped view preferences. Existing JSONL and RAW log files remain readable, and old Stored Logs filter query parameters remain accepted by the API for compatibility even though the dashboard now uses one query field. +### Dry run first -## 0.26.0 -> 0.26.1 - -No SQLite or saved-configuration migration is required. Version 0.26.1 changes persistent protocol-error attribution and the Stored Logs UI/API normalization only. Existing JSONL files remain readable, Live Logs behavior is unchanged, and unresolved protocol errors continue to use `_unmatched.jsonl`. - -## 0.25.7 -> 0.26.0 - -There is no SQLite schema or saved-configuration migration. Version 0.26.0 extends the existing structured access-log record with optional protocol-diagnostic fields and adds a read-only stored-log explorer. Existing logging settings remain valid and file logging remains disabled by default. Low-level malformed/TLS/protocol events are written only when at least one external structured-log sink is enabled; they do not enter the Live Logs memory ring. Existing JSONL access files and the configured error file can be browsed after upgrade without conversion. - -The `/logs` explorer is authenticated administration UI. Its API derives source IDs from the active RustProxy logging configuration and does not accept filesystem paths. On Unix it refuses symlink targets when opening files for browsing. - -## 0.25.6 -> 0.25.7 - -There is no SQLite schema or saved-configuration migration. Version 0.25.7 reuses the existing `admin_preferences` document to persist backend-table visible columns and sort order per administrator account; search text remains transient. It also adds vhost enable/disable UI actions, themed scrollbars and additional Prometheus/Grafana observability. Existing vhost `enabled` values and backend configuration remain unchanged until an administrator explicitly uses the new action. - -## 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. - -## 0.25.3 -> 0.25.4 - -There is no SQLite schema or saved-configuration migration. Schema 8 and the existing 512-row `configuration_history` retention remain unchanged. Version 0.25.4 adds an authenticated restore operation that validates a retained document and saves it as a new revision; existing history is not rewound or overwritten. - -## 0.25.2 -> 0.25.3 - -SQLite schema 7 is upgraded automatically to schema 8. The migration adds `configuration_history`; on first open the current revision is seeded into history, and subsequent successful configuration saves retain the newest 512 revisions for the dashboard revision-history view. No manual SQL is required. - -New vhost/location access fields are additive. Missing `method_policy`, location `acl_profile_id`, location country policy and location inline ACL fields retain the prior behavior. In that release RustProxy-generated request IDs became SHA-256 values forwarded to origins as protected `X-Request-ID`; custom request-header rules cannot replace that correlation identifier. OPT5 DEV 0.34.0 retains the same 64-hex-character protected identifier but produces it directly from the CSPRNG mixed with a sequence, without the additional SHA-256 digest. - -## 0.25.1 -> 0.25.2 - -There is no SQLite schema or saved-configuration migration. Full backup creation may now run while the service is active; restore still requires RustProxy to be stopped. Backup archive version 2 preserves validated internal relative symlinks such as Certbot `live/` entries pointing into the profile `archive/` tree. Existing archive-version-1 backups remain accepted for restore. - -The administration server keeps the same strict Host/Origin/request-marker/CSRF checks. The browser shell only adds a bounded one-shot same-origin reload when a restored or resumed authenticated tab receives `auth.invalid_origin`. - -## 0.25.0 -> 0.25.1 - -There is no SQLite schema or saved-configuration migration. 0.25.1 reorganizes the Traffic dashboard and source tree only; existing Web Services, vhosts, traffic settings, cache state and administrator data remain compatible. Browser asset URLs remain `/assets/app.js` and `/assets/vhosts.js` even though their source is now split into bounded modules. - -When updating a source checkout by extracting the archive over an older tree, remove the obsolete source monoliths `src/admin/web/app.js` and `src/admin/web/vhosts.js` if they remain. Checkouts older than the Web Server module split may also retain obsolete `src/web_server.rs`; remove it as well. Do **not** remove `src/config.rs`, `src/docker.rs`, `src/runtime.rs`, `src/balance.rs`, `scripts/deploy/deploy.py`, `scripts/migration/migrate_npm.py` or `docker-agent/main.go`: these are intentional module/CLI entry points in 0.25.1. Build/cache artifacts such as `target/` and `__pycache__/` may be deleted safely, but `.env`, `data/`, databases and local certificates are runtime/operator state and must not be deleted as part of a source refresh. - -## 0.24.10 -> 0.25.0 - -There is no SQLite schema migration for the first Web Server stage. `web_services` is an -additive collection inside the existing revisioned configuration document, and missing -vhost/location `source` fields continue to deserialize as Reverse Proxy. Existing 0.24.10 -configurations therefore remain valid without manual conversion. - -A Web Service must be created separately before a vhost/location can reference it. Document -root, index files and directory-listing settings belong to that reusable Web Service and are -not migrated into individual vhosts. - -## Historical deployment changes - -## 0.15.1 -> 0.16.0 - -There is no SQLite schema migration. Existing vhosts, backends, cache files, administrator accounts and sessions are retained. - -Custom locations are now interpreted only as overrides. An untouched legacy synthetic `root` prefix `/` location with no custom backend/cache/ACL/header/rewrite/local-response behavior is normalized away and replaced by the internal main-vhost fallback. A deliberately customized `/` location remains a real override. No manual edit is required for the normal legacy `root` entry. - -Publishing the administration listener through a vhost remains explicit-risk functionality. For reserved session-cookie passthrough the public request must be HTTPS, the external hostname must be listed in `administration.allowed_hosts`, the backend must directly address the configured administration listener by IP, and that backend must have `accept_local_listener_risk=true`. Ordinary upstreams continue to have `rustproxy_session` removed in both directions. - -Access-log JSON may contain the additional fields `http_version`, `transport`, `websocket_bootstrap`, `websocket_phase`, `websocket_error`, `h3_reset_code` and `upstream_status`. Cache/system metrics add memory-breakdown gauges; existing consumers should ignore unknown fields as usual. - -## 0.15.0 -> 0.15.1 - -There is no schema or configuration-format migration. This patch changes only administration UI language-pack placement, Custom locations editing/help/preview and dashboard visualizations. Existing vhosts, locations, cache settings and SQLite data remain unchanged. - -## 0.14.0 -> 0.15.0 - -There is no new SQLite schema version in 0.15.0. Renewal observations remain in the existing -schema-v3 `certificate_renewals` table. The application no longer contains a runtime JSON importer. - -When `upgrade.sh` / `scripts/deploy/upgrade.py` is used while RustProxy is stopped, an obsolete -`/certs/renewals.json` observation sidecar is validated as a regular file and removed as -the final source-upgrade step. It is not imported and no second legacy copy is retained. If the -source-upgrade transaction fails before that final removal, the old sidecar remains untouched. - -If a site replaces source manually instead of using the stopped upgrader, the old sidecar is simply -ignored. Back it up and remove it. An empty renewal-observation table is safe: the ordinary scheduler -reconstructs non-secret status/timestamps on its first 5–10 minute startup check, while certificates, -keys, ACME account material and renewal profiles remain unchanged. - -## 0.13.1 -> 0.13.2 - -Back up `config.sqlite3` and the configured certificate files before updating. On first -0.13.2 open, RustProxy applies the additive SQLite schema-v3 migration and creates -`certificate_renewals`; existing configuration, administrator accounts, sessions, audit -records and revisions are retained. The table stores only non-secret renewal observation -state (timestamps/status/message). If it is empty and the older private -`certs/renewals.json` exists, validated observations are imported once and the sidecar is -removed only after the database write succeeds. That one-time runtime compatibility path existed in -0.13.2/0.14.0 and is removed in 0.15.0; use the 0.15.0 stopped upgrader described above instead. - -The managed `update.sh` path for 0.13.2+ also marks the stop as a software update so an -enabled cold tier receives remaining hot cache objects even when ordinary -`cold_on_shutdown=false`. This is a best-effort persistent handoff bounded by the managed -stop timeout; no cache persistence is possible when cold storage is disabled. - -The raw JSON editor moved to **Settings -> Manual config**. `/configuration` remains a -browser compatibility alias, so old bookmarks do not require migration. - -## 0.8.0 -> 0.8.1 cache behavior - -The source default for `cache.enabled` changes to `true`. Existing saved configurations -that explicitly contain `enabled: false` remain disabled; the new default applies to new -or missing cache settings and is not a silent rewrite of an administrator choice. -`cache.max_object_bytes` may now be configured up to 1 GiB when `cache.max_bytes` is at -least as large. Browser conditional requests/no-cache refreshes now use conditional HIT -or backend revalidation instead of blanket BYPASS; `no-store` remains a hard exclusion. - - -- Existing vhosts deserialize new traffic-policy fields with conservative/default-off - behavior except `http2_enabled` and `http3_enabled`, which default true to preserve prior protocol behavior. -- The pre-production inline/global listener format is intentionally no longer readable. - Recreate development listener configuration under `listeners.endpoints` and bind each - vhost with `frontend.listener_ids`. No automatic vhost migration is performed. -- Existing backend TLS settings are preserved. Only newly created panel backend rows use - the 0.13.0 default `tls_verify:false`; enable verification explicitly for origins that require certificate identity checks. -- First installs fetch verified GeoIP country files. Existing installations upgraded with - `update.sh` keep their data directory; install the two verified files under `data/geoip` - before enabling country rules if the installation predates 0.8.0. - - -No database schema migration is introduced. The release changes deployment defaults -and control-plane startup behavior: - -- `admin setup-token` can create the one-time setup token before first server start. -- New managed systemd installations default to `0.0.0.0:80` and `0.0.0.0:443`. - Existing installations keep the saved service unit during `update.sh`; update does - not silently add port 443. -- Wildcard administration is opt-in only. Direct server startup requires - `--admin-listen 0.0.0.0:9090 serve --allow-remote-admin`. - The installer uses the equivalent `--admin` and `--allow-remote-admin` options. - This is plain HTTP; keep it behind a firewall/private network or use a tunnel. - -Back up private state before updating and run the real Rust check/test suite on the -target build host. - ---- - -# 0.7.1 -> 0.7.2 build fix - -This is a distinct source release. It removes the absent-SWAGGER-environment panic; -it does not migrate the database, alter vhosts or reset any account/certificate. -Back up private data as usual and update the complete source tree, not just build.rs. -The patch changes Cargo dependencies and adds a required build helper, so copying -only the Rust file would leave an inconsistent package. - -The existing `dev.sh` Python 3.10+ requirement now also applies to direct Cargo -builds. No `.cargo/config.toml` or manual `SWAGGER_UI_DOWNLOAD_URL` export is required. -The default pinned tag is fetched on the first build; a reviewed local tag archive -remains supported for restricted build hosts. See [API_ACCESS.md](API_ACCESS.md). -No default to an older Swagger distribution is introduced. - -Use the existing stopped-source `update.sh --source-tree` workflow or build a new -binary and use the managed-service updater as documented in [INSTALL.md](INSTALL.md). -`dev.sh` backs up an older Cargo.lock before regenerating it; do not fake the new -lockfile by replacing its root version text. A valid new lockfile is not included. -Rebuild/restart after successful tests; the old process cannot load embedded changes. -There is no need to clear application data or certificates, or to run `cargo clean` -just to remove this panic. Test the real admin session and `/docs` after building. - ---- - -# 0.7.0 -> 0.7.1 access changes - -No database/configuration schema migration is introduced by this patch. Back up -existing data/certificates and use the existing install/update instructions. -The reported release is source-only; compile and test before installing a binary. - -Operational JS and full translation packs are now session-protected. Old tabs -should be reloaded after upgrading. Automation reading `/api/auth/status.version` -or `/healthz.version` must use authenticated `/api/status` instead. Dedicated -Prometheus scraping retains its own CIDR/token policy and remains independent of -browser sessions. No reverse-proxy vhost auth policy is relaxed by this patch. - -In 0.7.1 the Swagger pin depended on `.cargo/config.toml`. That requirement is -removed in 0.7.2 (see the build notes above). Source-tree updates still back up and -replace that managed directory; retain any unrelated local Cargo settings from -your private backup. Read [API_ACCESS.md](API_ACCESS.md) for current build inputs. - -Run `python3 tools/audit/admin_audit.py --url http://127.0.0.1:9090` after deployment. -It prints only status checks, no response bodies or credentials. - ---- - -# Updating, backup and recovery - -## 0.7.0: renewal and service deployment - -See [INSTALL.md](INSTALL.md) for separate installer/updater commands and complete -code-plus-data rollback. Older source deployments retain `update.sh --source-tree`. -Back up the entire private state, including Certbot profiles, before any update. -Do not overlay a new binary onto a live, untested database without a recovery plan. - -Managed 0.6 certificate issuer metadata defaults to automatic renewal in 0.7. -The scheduler starts after a 5-10 minute delay and reuses existing consented -profiles. Imported PEM without issuer metadata is not scheduled. Review auto-renew -flags, service PATH, DNS permissions and HTTP-01 reachability before starting. -The new process does not implicitly switch staging certificates to production. - -Missing WebSocket flags preserve existing enabled behavior. New UI-authored vhosts -default to disabled. Existing backend members remain in the normal pool; new -dedicated location origins default to `pool_eligible:false`. Basic Auth is opt-in, -requires actual TLS by default and combines with (rather than bypasses) IP ACLs. - -Normal dev/server startup no longer starts the demonstration origin. Existing -sample vhosts are not silently deleted: remove or replace them deliberately, or -explicitly enable the protocol fixture in an isolated old test installation. New -databases show a neutral informational landing page with no sample backend. - -New fields are not a promise of backward parser compatibility. Downgrade by -restoring both old code and the pre-upgrade private-data snapshot. No verified Rust -build is supplied; run check/test and server acceptance before rollout. - -## 0.6.0: historical vhost migration note - -0.6.0 originally shipped a one-way importer from the pre-vhost development format. -That importer is removed in 0.8.0. Current builds accept only `vhosts`, named -`listeners.endpoints` and `frontend.listener_ids`. Development databases containing -top-level route/backend documents or inline frontend listeners must be recreated or -manually rewritten before starting 0.8.0. No automatic compatibility conversion runs. - -The dedicated Prometheus listener remains opt-in/disabled by default. Settings -> -Prometheus controls it. A restart is required for enable/bind/token-variable changes. -Certificate provider metadata remains additive and secrets remain outside exported -configuration. - -## 0.5.0 behavior changes - -Back up the database and configured PEM pairs before upgrading. The managed -updater replaces the full source tree, not live data. Build and test on staging. -No verified binary or fabricated Cargo.lock is supplied. - -TLS bindings with omitted `http3` now enable UDP with defaults; older versions -omitted disabled options when serializing, so review ALL TLS bindings. To retain -TCP-only operation, explicitly save `http3: null` on the named TLS listener before -restarting the new process. Shared/wildcard -IP-port bindings must agree on all protocol flags and H3 limits. A UDP bind failure -is not silently ignored. HTTP/1.1 and optional HTTP/2 remain on TCP. - -`diagnostic_headers.hide_mock_origin` was removed and is no longer accepted or -exported; use explicit response-header rules if needed. X-Mock-Origin is a normal upstream field; remove it with ordinary -response-header rules when desired. Request ID remains off by default. The removed 0.17 `diagnostic_headers.revision` field is no longer part of the configuration schema. Custom Server values -are NOT automatically rewritten to the new binary version. - -Cache contents are process-local. Reserved admin-cookie removal now occurs before -cache eligibility; application cookies still bypass. Domain counters reset on -configuration publication. See CACHE_DIAGNOSTICS.md for explaining BYPASS. - -ACME/RFC2136 is optional and adds external Python/Certbot/plugin requirements. -No DNS update or CA request happens simply by upgrading. Issuance is explicit, -staging-first and does not activate a certificate without import. Current encrypted full backup includes the durable ACME profile/account state and -Secret Store material required for DNS-provider renewal; the historical `dev.sh backup` -helper does not. Use `rust-proxy backup create` for current installations. Automatic -renewal behavior is documented in RENEWAL.md. - - -## Reported auth module compilation failure - -0.0.1 used `src/auth.rs`. 0.0.2 moved the implementation into `src/auth/mod.rs` and -`src/auth/store.rs`. Overlay extraction can retain both layouts and cause Rust -E0761. Missing `Store::session_at`, `admin_record`, `end_session` and -`revoke_all_sessions` messages can follow because the implementation module did not -load. The 0.0.2 archive itself did not contain both auth layouts. - -0.4.0 retains an explicit `#[path = "auth/mod.rs"]` declaration, including for direct -Cargo invocations. Preflight also detects mixed source trees. It recognizes the -exact original auth file by SHA-256 and moves it to -`.upgrade-backup/legacy-auth/auth.rs.0.0.1`. Unknown custom source is left untouched -and the development script stops with instructions. Never delete custom files just -to silence a compiler error. +Locate the NPM SQLite database (often `/data/database.sqlite` inside its container). Back up its live consistent database before reading it; SQLite WAL may contain uncheckpointed changes. ```bash -bash dev.sh doctor -bash dev.sh repair +python3 scripts/migration/migrate_npm.py \ + --database /path/to/npm/database.sqlite \ + --rustproxy https://rustproxy.example.net:9090 ``` -These commands do not require Rust. Repair is deliberately limited; a safe whole -source-tree update is preferable to extracting individual files over a checkout. +The default invocation creates `npm-migration-plan.json` with planned additions/skips; no write happens without `--apply`. Enter admin credentials through the prompt or secure environment, and use a trusted server certificate. The optional `--ca-file` supports a private CA; `--insecure-admin-tls` is a deliberate local/test exception, not a recommended setting for ordinary production automation. -## Recommended source update +Review each conflicting hostname, missing HTTPS listener/certificate, custom Nginx directive and backend policy. Explicit listener selection is possible with `--http-listener` and `--https-listener`. -Extract the new archive beside, not inside, the installed project. On installations -that provide the current encrypted backup command, stop RustProxy and create the -backup outside the data directory before replacing source files: +### Apply after reviewing the plan ```bash -rust-proxy --data-dir /home/mg/RustProxy/data backup create \ - --output /home/mg/RustProxy-backup-before-upgrade.rpb -unzip RustProxy-0.4.0.zip -d /home/mg/rustproxy-040-staging -cd /home/mg/rustproxy-040-staging/RustProxy +python3 scripts/migration/migrate_npm.py \ + --database /path/to/npm/database.sqlite \ + --rustproxy https://rustproxy.example.net:9090 \ + --apply ``` -`tools/legacy/backup.py` is a legacy release-test helper and is not a complete Secret -Store backup. For current backup/restore behavior see `SECRET_STORE_BACKUP.md`. +The migration writes through RustProxy's **revisioned administration API**, so it should not silently overwrite a newer config save. Test each imported domain and assign actual RustProxy certificates manually. Keep NPM available for rollback until the imported configuration is verified. -A missing database means there is no database to back up. Do not initialize an empty -replacement accidentally. For data stored elsewhere, use the actual directory. +## Unsupported or very old configurations -Stop RustProxy using its terminal or service manager. Then: - -```bash -bash upgrade.sh /home/mg/RustProxy --stopped -# External data directory: also pass --data-dir /actual/private/data -cd /home/mg/RustProxy -bash dev.sh test -bash dev.sh -``` - -The updater checks a process lock on POSIX, requires the explicit `--stopped` -acknowledgement and rejects nested source/target paths and managed symlinks. -It preserves `.env`, `data`, `.git`, `target` and unmanaged files. Source, scripts, -tests, examples and documentation are replaced as whole managed paths. Old managed -files remain in `.upgrade-backup/before-0.4.0-`. An interrupted copy -exception triggers source rollback. Abrupt power loss is not a transactional -filesystem update; inspect the backup and source tree before restarting. - -The updater does not evaluate the old `.env` or discover arbitrary external data -paths. Pass `--data-dir` when necessary, and stop every process yourself before -changing source. It does not perform a database migration or backup automatically. -Custom source edits are backed up, not merged. - -## Database compatibility - -RustProxy 0.13.2 advances the SQLite schema from version 2 to **version 3** with one -additive `certificate_renewals` table for non-secret ACME observation state. The existing -0.0.1-to-0.0.2 auth migration still runs when needed. Configuration-document additions -remain defaulted/non-destructive. Existing accounts, policies, revisions and configuration -remain intact. Starting with 0.15.0 the application reads renewal observations only from this table; -historical JSON observation state is handled, when present, by the stopped source-upgrade remove -described above rather than by the running service. - -The generated default certificate is created on first TLS-enabled startup, in -`/certs`. Its key is never overwritten by startup. A partial or invalid -pair causes a clear startup error; it is not silently discarded. Demo settings -never overwrite an existing configuration, including an empty existing one. - -## Backup contents and restore - -Current RustProxy releases use the password-encrypted full-backup format documented -in `SECRET_STORE_BACKUP.md`. The supported interface is `rust-proxy backup create` -and CLI-only `rust-proxy backup restore`; it includes the encrypted Secret Store, -key material required to recover it, certificates and other durable application -state while excluding cache, logs and runtime files. - -The historical `./dev.sh backup` / `tools/legacy/backup.py` snapshot helper described by -older release notes is not sufficient after Secret Store migration and is retained -only for offline compatibility/release tests. - -## Downgrade - -Older versions reject unknown configuration fields such as `tls`, `protocol` or -`tls_ca_pem`. Restoring old source alone is therefore not a safe downgrade after -saving new configuration. Restore the matching pre-upgrade database and environment -from a verified backup, with the server stopped. Do not edit password hashes or -schema versions manually. - - -## 0.0.6 -> 0.1.0 document and source changes - -No new SQL schema version is introduced. Old documents default to inherited -listeners, disabled cache/compression and preserve-Server behavior. New optional -route cache policy does not change existing route behavior. Existing account, -revision, TLS and ACL state is retained. - -Managed paths now include build.rs, langs, deploy and make_zip.py. Extra *.json -language packs from the installed langs directory are copied into the new tree -when the new distribution does not contain the same filename. Shipped packs are -updated, with old bytes preserved in the source backup. Invalid custom packs can -correctly block a build; review their JSON syntax and metadata before building. No symlinked -language pack is followed. The updater never copies a database from the package. - -The old lockfile is preserved in the managed source backup and a new dependency -graph is generated by Cargo on first build. No 0.1.0 resolved Cargo.lock is included -because Cargo was unavailable here. Keep the actual newly resolved file afterward. - -Saving any 0.1.0 document serializes its new fields. Older binaries use strict -unknown-field rejection, so rolling back only the source/binary may fail startup. -Restore the pre-upgrade database/certificates into a new private directory and keep -the newer data separately. Do not directly edit live SQLite to remove fields. - -A saved invalid listener address or missing low-port permission can prevent -startup. The online config CLI cannot repair an offline server. Keep a pre-change -backup and use the documented stopped-server restore path; remove persisted -listener overrides through Settings while the service is still running before -changing deployment assumptions. The cache itself is RAM only and is not backed up. - -## 0.1.0 -> 0.2.0: compression, vhosts and pool behavior - -The `SizeAbove` u16/u64 mismatch is fixed by replacing that predicate with a custom -u64 implementation. Do not use `try_into().unwrap()` or truncate the setting to -work around the old source: configured thresholds above 65535 must remain valid. -This source change removes that call, but is not a successful compiler result; -run `bash dev.sh check` and `bash dev.sh test` on a Rust-enabled staging machine. - -SQL schema version stays 2; accounts, ACLs, certificates and revisions are retained. -New `vhosts` defaults empty; named TLS listeners default to H2/H3 capability when those fields are omitted. Legacy routes -continue on the global listeners and are never silently migrated or exposed on -additional IPs. The editor's explicit migration action converts one unambiguous -route group in a single revision and keeps the migrated route group's existing health behavior. - -New vhosts default to TCP health checks and remain unavailable until -`rise` checks succeed. Plan the correct path/status/timeouts before migration. -Health/counter/affinity observations are ephemeral. They survive compatible edits, -not restarts; new/changed/re-enabled origins require new checks. Cache and idle -upstream pools are not carried across config publication. Admitted exchanges finish -with their old snapshot and are not replayed on a different backend. - -HTTPS origins previously configured `http2` now retain HTTP/1.1 ALPN compatibility. -Plaintext `http2` is still strict h2c. Per-listener IP/port/TLS/HTTP2 changes require -restart. During an address move, hot routing can deny the previous destination -before the new socket is bound. Plan a maintenance window, verify assigned IPs, -privileges/certificates and retain an offline-restorable backup. - -After saving any 0.2.0 document, 0.1.0 can reject new fields even without a vhost, -because `https_http2` is serialized. Downgrade requires matching pre-0.2.0 data, -source and environment. Never remove fields by editing a live SQLite database. -The archive contains no fabricated lockfile, executable, database or private key. -Generate and retain the real dependency graph before acceptance/deployment. - -## 0.2.0 -> 0.3.0 - -The reported `json_internal!` recursion error is addressed by splitting -`status_payload` into small JSON values and inserting feature flags through a map. -Do not overlay only this function: this release also adds the `query` Axum feature, -`src/pages.rs`, `management.js`, new configuration fields and matching UI assets. -The earlier u64 compression predicate remains in place. Compilation has not been -executed in the packaging environment; this is not a successful-build assertion. - -Existing database documents deserialize with empty/default `pages`, vhost `cache` -and `certificate_id` fields. No SQL schema migration is added. Per-vhost certificate -selection rewrites the effective SNI map in a snapshot, not the files. Upload is a -staging action: Save must publish a validated configuration before traffic uses it. -Canceling the editor leaves an unbound pair, not an active certificate change. - -Use the complete backup/whole-source update procedure above. The existing backup -copies configured keys and fallback files. Unbound upload staging files are not in -the configuration and therefore are not part of that configured-certificate backup. -Retain them separately only when needed. Never delete a filename still referenced -by the configuration or currently serving snapshot; stop the process before manual -cleanup and preserve a backup of the full private certificate directory. - -New defaults do not activate custom pages or vhost cache overrides. An explicitly -disabled global cache can still be overridden by one vhost; the global byte/entry/object -limits still bound it. Current releases rotate cache compatibility only for configuration -changes that can alter cached/origin semantics; unrelated control-plane publication preserves -reusable objects. The dedicated per-vhost Purge action remains selective. - -Downgrade requires restoring the earlier database/configuration together with its -matching source: 0.2.0 rejects the new fields. Do not merely relabel a new document. -The source archive contains no fabricated lockfile. Resolve and retain Cargo.lock -on the first compiler-enabled build and run all tests before restarting production. - -## 0.3.0 -> 0.4.0 - -Use the full-source update above, not an overlay of a few Rust files. This revision -adds Quinn/h3 dependencies, new modules and a new embedded integrations.js asset. -No successful compiler run or dependency resolution is claimed. Generate and retain -a real Cargo.lock, then run check and all tests before acceptance. - -SQL schema stays 2. Proxy sockets are persisted only in `listeners.endpoints`; a new -database is seeded from startup CLI listener addresses. Existing development documents -using the removed listener format are rejected rather than migrated. Tinyauth and -vhost Server policy continue to use their normal defaults. New diagnostic defaults remove x-request-id from client responses unless it is enabled explicitly where needed. Access logs retain request IDs. The old revision diagnostic field/header behavior was removed in 0.18.2. Forwarding headers are -rebuilt from actual peers unless known proxy CIDRs are explicitly configured. - -Reference named listener IDs per vhost instead of duplicating bindings. There is no -inline/global compatibility conversion path in 0.8.0. Scope names have no firewall meaning. Changed sockets, ALPN or H3 limits -require restart; opening UDP is necessary for H3. Saving intent does not prove -that an OS bind or UDP firewall rule will succeed. - -The certificate directory/fallback is prepared even with HTTP-only startup, so -library uploads can be staged before enabling TLS. The generated fallback is -self-signed development material and does not automatically open an HTTPS socket. -Import the PEM pair in Certificates, save inventory, then select its ID in SSL. -Unbound staged files are not automatically backed up; preserve private files needed -for rollback and never remove any currently referenced certificate/key. - -Tinyauth-protected vhosts bypass shared cache regardless of cache toggles. Failures -deny access instead of serving cached/static content. Configure its verifier URL, -public login URL and explicit private-origin permission before enabling it on an -existing service. Test login, refusal, logout/revocation and WebSockets on staging. - -The examples are complete replacement documents for disposable test databases, -not merge patches. Saving a 0.4 document serializes new fields; older binaries may -reject them. Downgrade requires matching pre-upgrade database, certificates, -environment and source, with the process stopped. The source backup alone is not -a database/configuration rollback. +Do not assume arbitrary old Nginx config files, plugin-specific JSON, unmanaged data directory layouts or incompatible backup formats migrate automatically. Follow the original release notes or restore on an isolated older version first, then upgrade in controlled steps. Use the current API schema rather than undocumented JSON keys. For problems see [Troubleshooting](TROUBLESHOOTING.md). diff --git a/docs/MOCK_AUTH.md b/docs/MOCK_AUTH.md deleted file mode 100644 index 889ea96..0000000 --- a/docs/MOCK_AUTH.md +++ /dev/null @@ -1,37 +0,0 @@ -# Fake Tinyauth protocol peer - local development only - -This is NOT authentication. Any client can mint the allow cookie. The fixture binds -only to 127.0.0.1 and must never protect a production application. - -```bash -bash dev.sh mock --port 9001 --name origin-a --ttl 30 -# Separate terminal: -bash dev.sh mock-auth --port 9300 -``` - -In a disposable vhost's Authentication tab, set: -- verifier: http://127.0.0.1:9300 (adds /api/auth/nginx) -- login: http://127.0.0.1:9300/login -- explicitly allow private verifier IPs -- forwarded identities: remote-user and remote-email - -Use your actual frontend address/hostname. Example for a local plain-HTTP vhost: - -```bash -# Denied -> 302 to configured login, no application request: -curl -i -H 'Host: app.localhost' http://127.0.0.2:8181/inspect -# Allowed -> app receives mock-user / mock@example.test; cache remains BYPASS: -curl -i -H 'Host: app.localhost' -H 'Cookie: mock_auth=allow' http://127.0.0.2:8181/inspect -# Force authentication failure even with an allow cookie: -curl -X POST -H 'Content-Type: application/json' -d '{"status":503}' http://127.0.0.1:9300/control -# Simulate slow verifier; choose less than or greater than the vhost total timeout: -curl -X POST -H 'Content-Type: application/json' -d '{"delay_ms":12000}' http://127.0.0.1:9300/control -# Restore default deny/allow behavior: -curl -X POST -H 'Content-Type: application/json' -d '{"reset":true}' http://127.0.0.1:9300/control -curl http://127.0.0.1:9300/state -``` - -/control replaces status/delay/redirect behavior (omitted values reset to defaults). -Optional location sets X-Tinyauth-Location for redirect-policy tests. /state exposes -only the last 16 metadata records, not Authorization, cookies or query strings. -Real Rust-router auth tests are in tests/integrations.rs. diff --git a/docs/MOCK_SERVER.md b/docs/MOCK_SERVER.md deleted file mode 100644 index af064fe..0000000 --- a/docs/MOCK_SERVER.md +++ /dev/null @@ -1,116 +0,0 @@ -# Python-only mock origin - -Run independently of Cargo/Rust, in a separate terminal: - -```bash -bash dev.sh mock --port 9001 --name origin-a --ttl 30 -``` - -This runs until Ctrl+C. A second origin for balancing uses another terminal: - -```bash -bash dev.sh mock --port 9002 --name origin-b --ttl 30 -``` - -The helper binds 127.0.0.1 by default. It accepts IPv4 addresses only and refuses -non-loopback bindings without --allow-remote. Do not expose it publicly: -/control deliberately has no authentication. It has 32 connection workers, -15-second socket timeout, a 2 MiB maximum response and a 10-second delay ceiling. -It is a development fixture, not a secure production website. - -## Configure a test vhost - -After building and starting RustProxy, create a vhost with domain mock.localhost, -frontend 127.0.0.1:8080, backend http://127.0.0.1:9001, private backend permission, -HTTP health path /health, and Cache enabled with X-Cache diagnostics. Leave sticky -cookies off to test shared cache. Wait for its backend to become healthy. - -`examples/mock-vhost.json` is a complete ready-to-apply document for a disposable -installation. It explicitly enables the per-vhost cache (the 0.8.1 global default is -also enabled), adds a custom 503 page, and serves the congratulations page on direct-IP access. Applying -it replaces all existing routes/vhosts; it is not a merge patch. Review first: - -```bash -bash dev.sh config validate examples/mock-vhost.json -bash dev.sh config apply examples/mock-vhost.json -``` - -Do not replace your real configuration for this demonstration. Use the panel to -add equivalent settings to an existing project instead. - -## Verify cache, not just the origin - -Requests must go through the proxy with the right Host: - -```bash -curl -si -H 'Host: mock.localhost' http://127.0.0.1:8080/cache -curl -si -H 'Host: mock.localhost' http://127.0.0.1:8080/cache -``` - -After the first complete response, expect MISS then HIT with an identical nonce -while fresh. Origin JSON always generates a new nonce and timestamp; a cache hit -therefore makes reuse visible. Health probes can advance the sequence too, so -compare nonce/body, not an assumption that sequence increases by exactly one. -GET is the clearest fill test because it consumes the complete body. HEAD is also -cacheable in 0.11.5 and shares the GET representation. Only response `Vary` fields -create request-header representation variants; unrelated request headers and the -selected backend-pool member no longer fragment the same Host + URL object. - -Use the host's Purge action and request again to expect a new MISS. Waiting past -TTL also refreshes. To compare with a non-cacheable route: - -```bash -curl -si -H 'Host: mock.localhost' http://127.0.0.1:8080/nocache -curl -si -H 'Host: mock.localhost' http://127.0.0.1:8080/nocache -``` - -Bodies should change and must not become HIT. A diagnostic MISS means lookup was -attempted, not that the response was stored: no-store/private/Set-Cookie may reject -storage after origin headers arrive. BYPASS indicates a request-side exclusion. - -## Endpoints - -| Path | Behavior | -|---|---| -| / | Small HTML page with links | -| /cache?ttl=30 | Random JSON; public max-age; Vary: Accept-Language | -| /nocache | Random JSON; no-store | -| /private | Private JSON; cannot enter shared cache | -| /cookie | Sets a cookie; must not enter shared cache | -| /large?size=131072 | Compressible bytes; public freshness; useful above 65535 bytes | -| /slow?ms=800 | Delayed no-store response | -| /health | 200/503 according to controlled health; delay is also applied | -| /state | Current health, delay, sequence and origin ID | -| /status/401, /status/403, /status/404, /status/503 | Origin error responses for page tests; 401 includes a challenge | -| POST /control | JSON with healthy:boolean and/or delay_ms:0..10000 | - -Compression needs enabled proxy compression and an appropriate Accept-Encoding. -The origin itself does not compress. Inspect both response headers and decoded body; -not all curl builds support Brotli or Zstandard. Example: - -```bash -curl -si --compressed -H 'Host: mock.localhost' \ - 'http://127.0.0.1:8080/large?size=131072' -``` - -## Failover, latency and recovery - -Control the origin directly, not through the proxy, especially after it is marked -down. Configure the health path /health (the default root page always returns 200). - -```bash -curl -s -H 'Content-Type: application/json' -d '{"healthy":false}' http://127.0.0.1:9001/control -curl -s -H 'Content-Type: application/json' -d '{"healthy":true,"delay_ms":1200}' http://127.0.0.1:9001/control -curl -s -H 'Content-Type: application/json' -d '{"healthy":true,"delay_ms":0}' http://127.0.0.1:9001/control -``` - -Watch health/RTT in the vhost list/editor and dashboard. Fall/rise thresholds and -probe interval delay transitions intentionally. Add origin-b as a second backend -for failover and balancing tests. Random origin data alone does not prove that the -Rust scheduler, compression or cache is correct; run the Rust acceptance tests. - -## Verification scope - -The mock server is a development fixture, not a proxy test. Exercise its endpoints -directly with curl when needed, then run the RustProxy suite separately with -`bash dev.sh test` on a host that has Cargo. diff --git a/docs/MONITORING.md b/docs/MONITORING.md new file mode 100644 index 0000000..274b408 --- /dev/null +++ b/docs/MONITORING.md @@ -0,0 +1,46 @@ +# Monitoring, metrics and health + +RustProxy has operational views in the authenticated panel and an optional standalone Prometheus exporter. They measure different things: dashboard sampling, process-lifetime counters and stored logs are not interchangeable. + +## What to watch + +| View | Main information | Interpretation | +|---|---|---| +| **Overview** | Active connections, request rate, errors, process CPU/RAM, protocols, backend state, optional charts | Dashboard samples are not long-term billing data. | +| **Vhosts → Backends** | Member health, weights, H1/H2 modes, current work, last observed upstream response | A `last_status` from normal proxy traffic is not an active health probe result. | +| **Web Server** | Requests/status classes, static/PHP traffic, response-header time, response bytes, PHP concurrency permits | Response header duration excludes streamed body transfer. PHP permits are not FPM child processes. | +| **Traffic** | Per-Vhost totals and top countries/IPs/paths/methods/agents/cache results | Bounded in-memory analytics, approximate top lists and possible sample loss under heavy load. | +| **Cache** | HOT/COLD occupancy, hit/miss/bypass, object metadata, selected invalidation | HTTP `Age` differs from cache residence age. | +| **Docker / Streams** | Agent status, dynamic Vhost health, TCP/UDP sessions and traffic | An agent can be waiting for first observation; Streams are non-HTTP. | +| **Logs** | Live requests, stored JSONL/error/protocol records | A log sink can be disabled while live memory view remains available. | + +Overview allows account-specific card layout and history controls; see [Dashboard](DASHBOARD.md). GeoIP/country analytics depend on the installed/update database and whether country capture is enabled. + +## Prometheus exporter + +The exporter is **disabled by default** and uses a separate HTTP listener, normally `127.0.0.1:9191`, at `/metrics`. It is not protected by the admin browser cookie; it has its **own** client-network and bearer-token policy. In **Settings → Prometheus** enable the endpoint and configure: + +- `listen`, `path`, `allowed_cidrs` and an optional environment-variable **name** `bearer_token_env` (the secret value is supplied separately to the service). +- `allow_trusted_network_without_token` only for a deliberately restricted monitoring subnet. `/0` CIDRs are not accepted in this mode. +- Collector switches for process/system, Vhosts, traffic, backends, Docker, cache domains, Streams and logging. + +A non-loopback exporter must have an actual token or an explicit token-free trusted-network exception. The exporter uses plain HTTP; keep it on loopback, trusted network or a protected TLS tunnel. `Forwarded`/`X-Forwarded-For` headers cannot grant exporter access because network restrictions apply to the transport peer. + +For bearer authentication, provide a 32–4096-character printable non-whitespace ASCII token in a **service environment variable** and set `bearer_token_env` to its name, e.g. `RUST_PROXY_METRICS_TOKEN`. Changing exporter activation/bind/token-variable usually needs restart; some collector/CIDR settings apply live. The exact allowed GET path must not have a query string. + +Example scrape configuration, after choosing the correct private target: + +```yaml +scrape_configs: + - job_name: rustproxy + static_configs: + - targets: ['127.0.0.1:9191'] +``` + +When using a bearer token, configure Prometheus `authorization` or `bearer_token_file` securely rather than embedding a clear token in an exported dashboard. Review `grafana/README.md` and `grafana/rustproxy-overview.json` for chart examples; `nagios/README.md` contains external check scripts. + +## Metric privacy and limits + +Prometheus series use bounded configured labels; arbitrary request URLs, raw client IPs, credentials, raw User-Agents and query strings are intentionally not exposed as unbounded labels. Dashboard traffic top lists can still contain sensitive operational metadata, so keep the panel authenticated. + +Metrics reset on process replacement, and some per-Vhost/backend records can reset when identity or config changes. Distinguish counters, rates and gauges; watch for traffic sampling gaps rather than interpreting every missing point as zero. Before deployment, verify collector reachability, firewall rules, CPU/RAM overhead and alert thresholds against real traffic. diff --git a/docs/NGINX_GAP_0.34.0.json b/docs/NGINX_GAP_0.34.0.json deleted file mode 100644 index 2ba50ad..0000000 --- a/docs/NGINX_GAP_0.34.0.json +++ /dev/null @@ -1,216 +0,0 @@ -{ - "kind": "HYPOTHETICAL_AMDAHL_SENSITIVITY_NOT_A_BENCHMARK", - "comparison": "Existing 0.33.8 measurements (5 runs, cache off) vs measured nginx", - "not_measured": "0.34.0 server performance and sendfile syscall/runtime correctness", - "conditions": [ - "Identical CPU and network assumptions to supplied 0.33.8 baseline", - "WWW zero-copy is limited to Linux, cleartext HTTP/1.1, GET >=64KiB, no cache, range, transform, compression or TLS", - "WWW low/middle/high: file-copy contributes 20%/40%/60% of total request time; modeled sendfile eliminates 50% of that component", - "Reverse proxy low/middle/high: removing response-frame mutex saves a hypothetical 1%/3%/5% of total service time", - "These fractions are chosen sensitivity parameters; not measured or inferred from profiling", - "No change is assumed for WWW 1040B or out-of-scope protocol paths", - "No network saturation, TLS record processing, queueing, origin latency, filesystem cache misses, CPU contention or concurrency effects modeled", - "No additional speedup due to cache-retention changes is assumed" - ], - "formula": "RPS_sim = RPS_0.33.8 / (1 - fraction_of_total_service_time_eliminated)", - "entries": [ - { - "kind": "reverse_proxy", - "bytes": 1040, - "baseline_version": "0.33.8", - "baseline_rust_rps": 29334.12, - "baseline_nginx_rps": 170610.16, - "baseline_of_nginx_pct": 17.19, - "factor_to_match_nginx": 5.82, - "sendfile_eligible_only_if_plain_linux_http1_1_no_cache_no_compression": false, - "scenarios": { - "low": { - "modeled_total_time_saved_pct": 1.0, - "simulated_rps": 29630, - "simulated_rps_gain_pct": 1.01, - "simulated_of_nginx_pct": 17.37, - "simulated_remaining_gap_to_nginx_pct_points": 82.63 - }, - "middle": { - "modeled_total_time_saved_pct": 3.0, - "simulated_rps": 30241, - "simulated_rps_gain_pct": 3.09, - "simulated_of_nginx_pct": 17.73, - "simulated_remaining_gap_to_nginx_pct_points": 82.27 - }, - "high": { - "modeled_total_time_saved_pct": 5.0, - "simulated_rps": 30878, - "simulated_rps_gain_pct": 5.26, - "simulated_of_nginx_pct": 18.1, - "simulated_remaining_gap_to_nginx_pct_points": 81.9 - } - } - }, - { - "kind": "reverse_proxy", - "bytes": 65536, - "baseline_version": "0.33.8", - "baseline_rust_rps": 20735.07, - "baseline_nginx_rps": 35393.29, - "baseline_of_nginx_pct": 58.58, - "factor_to_match_nginx": 1.71, - "sendfile_eligible_only_if_plain_linux_http1_1_no_cache_no_compression": false, - "scenarios": { - "low": { - "modeled_total_time_saved_pct": 1.0, - "simulated_rps": 20945, - "simulated_rps_gain_pct": 1.01, - "simulated_of_nginx_pct": 59.18, - "simulated_remaining_gap_to_nginx_pct_points": 40.82 - }, - "middle": { - "modeled_total_time_saved_pct": 3.0, - "simulated_rps": 21376, - "simulated_rps_gain_pct": 3.09, - "simulated_of_nginx_pct": 60.4, - "simulated_remaining_gap_to_nginx_pct_points": 39.6 - }, - "high": { - "modeled_total_time_saved_pct": 5.0, - "simulated_rps": 21826, - "simulated_rps_gain_pct": 5.26, - "simulated_of_nginx_pct": 61.67, - "simulated_remaining_gap_to_nginx_pct_points": 38.33 - } - } - }, - { - "kind": "reverse_proxy", - "bytes": 1048576, - "baseline_version": "0.33.8", - "baseline_rust_rps": 4461.19, - "baseline_nginx_rps": 2730.23, - "baseline_of_nginx_pct": 163.4, - "factor_to_match_nginx": 0.61, - "sendfile_eligible_only_if_plain_linux_http1_1_no_cache_no_compression": false, - "scenarios": { - "low": { - "modeled_total_time_saved_pct": 1.0, - "simulated_rps": 4506, - "simulated_rps_gain_pct": 1.01, - "simulated_of_nginx_pct": 165.05, - "simulated_remaining_gap_to_nginx_pct_points": -65.05 - }, - "middle": { - "modeled_total_time_saved_pct": 3.0, - "simulated_rps": 4599, - "simulated_rps_gain_pct": 3.09, - "simulated_of_nginx_pct": 168.45, - "simulated_remaining_gap_to_nginx_pct_points": -68.45 - }, - "high": { - "modeled_total_time_saved_pct": 5.0, - "simulated_rps": 4696, - "simulated_rps_gain_pct": 5.26, - "simulated_of_nginx_pct": 172.0, - "simulated_remaining_gap_to_nginx_pct_points": -72.0 - } - } - }, - { - "kind": "www", - "bytes": 1040, - "baseline_version": "0.33.8", - "baseline_rust_rps": 24906.2, - "baseline_nginx_rps": 351605.13, - "baseline_of_nginx_pct": 7.08, - "factor_to_match_nginx": 14.12, - "sendfile_eligible_only_if_plain_linux_http1_1_no_cache_no_compression": false, - "scenarios": { - "low": { - "modeled_total_time_saved_pct": 0.0, - "simulated_rps": 24906, - "simulated_rps_gain_pct": 0.0, - "simulated_of_nginx_pct": 7.08, - "simulated_remaining_gap_to_nginx_pct_points": 92.92 - }, - "middle": { - "modeled_total_time_saved_pct": 0.0, - "simulated_rps": 24906, - "simulated_rps_gain_pct": 0.0, - "simulated_of_nginx_pct": 7.08, - "simulated_remaining_gap_to_nginx_pct_points": 92.92 - }, - "high": { - "modeled_total_time_saved_pct": 0.0, - "simulated_rps": 24906, - "simulated_rps_gain_pct": 0.0, - "simulated_of_nginx_pct": 7.08, - "simulated_remaining_gap_to_nginx_pct_points": 92.92 - } - } - }, - { - "kind": "www", - "bytes": 65536, - "baseline_version": "0.33.8", - "baseline_rust_rps": 20856.67, - "baseline_nginx_rps": 203709.91, - "baseline_of_nginx_pct": 10.24, - "factor_to_match_nginx": 9.77, - "sendfile_eligible_only_if_plain_linux_http1_1_no_cache_no_compression": true, - "scenarios": { - "low": { - "modeled_total_time_saved_pct": 10.0, - "simulated_rps": 23174, - "simulated_rps_gain_pct": 11.11, - "simulated_of_nginx_pct": 11.38, - "simulated_remaining_gap_to_nginx_pct_points": 88.62 - }, - "middle": { - "modeled_total_time_saved_pct": 20.0, - "simulated_rps": 26071, - "simulated_rps_gain_pct": 25.0, - "simulated_of_nginx_pct": 12.8, - "simulated_remaining_gap_to_nginx_pct_points": 87.2 - }, - "high": { - "modeled_total_time_saved_pct": 30.0, - "simulated_rps": 29795, - "simulated_rps_gain_pct": 42.86, - "simulated_of_nginx_pct": 14.63, - "simulated_remaining_gap_to_nginx_pct_points": 85.37 - } - } - }, - { - "kind": "www", - "bytes": 1048576, - "baseline_version": "0.33.8", - "baseline_rust_rps": 2833.71, - "baseline_nginx_rps": 12611.49, - "baseline_of_nginx_pct": 22.47, - "factor_to_match_nginx": 4.45, - "sendfile_eligible_only_if_plain_linux_http1_1_no_cache_no_compression": true, - "scenarios": { - "low": { - "modeled_total_time_saved_pct": 10.0, - "simulated_rps": 3149, - "simulated_rps_gain_pct": 11.11, - "simulated_of_nginx_pct": 24.97, - "simulated_remaining_gap_to_nginx_pct_points": 75.03 - }, - "middle": { - "modeled_total_time_saved_pct": 20.0, - "simulated_rps": 3542, - "simulated_rps_gain_pct": 25.0, - "simulated_of_nginx_pct": 28.09, - "simulated_remaining_gap_to_nginx_pct_points": 71.91 - }, - "high": { - "modeled_total_time_saved_pct": 30.0, - "simulated_rps": 4048, - "simulated_rps_gain_pct": 42.86, - "simulated_of_nginx_pct": 32.1, - "simulated_remaining_gap_to_nginx_pct_points": 67.9 - } - } - } - ] -} diff --git a/docs/NOT_FOUND_HOSTS.md b/docs/NOT_FOUND_HOSTS.md deleted file mode 100644 index 1aa0e64..0000000 --- a/docs/NOT_FOUND_HOSTS.md +++ /dev/null @@ -1,24 +0,0 @@ -# Local-response / 404 hosts - -RustProxy 0.22.3 has a dedicated **404 hosts** page for domains that should be -owned by RustProxy but must not contact a backend. - -Each entry has the same frontend model as vhosts/redirects: - -- one or more hostnames, -- named listener selection, -- optional TLS certificate, -- independent HTTP/2 and HTTP/3 switches. - -The response can be: - -- the built-in `RustProxy is running` placeholder, -- an application-style built-in status page for 401, 403, 404, 429, 503 or 504, -- an empty/status-only response, -- bounded custom HTML. - -The status is selected rather than typed manually. Application-style pages use the bundled 400/401/403/404/408/410/413/429/500/502/503/504 templates. Placeholder, empty and custom-HTML modes expose the supported application error set: 400/401/403/404/408/413/429/500/502/503/504. HTTP/2 and HTTP/3 toggles are shown beside the listener controls because they apply to the selected frontend listeners. Host/listener ownership is -validated against vhosts and redirect hosts, so two active configuration objects -cannot claim the same frontend binding. - -All 404-host responses remain local and never open an upstream connection. They are not stored in RustProxy cache. Before the response leaves RustProxy, cache policy/validator headers and RustProxy `X-Cache*` diagnostics are removed, so 404 hosts do not advertise cache behavior on the wire. The built-in `RustProxy is running` placeholder also intentionally omits optional RustProxy server, diagnostic, security, compression and backend headers. diff --git a/docs/NPM_MIGRATION.md b/docs/NPM_MIGRATION.md deleted file mode 100644 index 80964a8..0000000 --- a/docs/NPM_MIGRATION.md +++ /dev/null @@ -1,120 +0,0 @@ -# Nginx Proxy Manager migration - -RustProxy ships `scripts/migration/migrate_npm.py` for a one-time, dry-run-first migration of -Nginx Proxy Manager (NPM) proxy hosts from its SQLite database. - -The tool reads NPM only. It does not modify the NPM database and it never imports -Let's Encrypt/private-key material. It logs in to the RustProxy administration API, -reads the current revision, merges generated vhosts into the existing configuration, -and writes only when `--apply` is supplied. - -`migrate_npm.py` is the CLI entry point and requires the sibling modules -`scripts/migration/migrate_npm_core.py` and `scripts/migration/migrate_npm_api.py`. They use only the -Python standard library, so no `pip` packages are required. If the migrator is copied -or mounted outside the RustProxy source tree, keep all three files in the same directory. - -## What is migrated - -For each NPM `proxy_host` row: - -- `domain_names` -> RustProxy vhost hostnames, including leading `*.` wildcards; -- `forward_scheme`, `forward_host`, `forward_port` -> one RustProxy backend; -- `allow_websocket_upgrade` -> vhost WebSocket policy; -- `enabled` -> vhost enabled state; -- `http2_support` -> vhost HTTP/2 policy; -- NPM hosts with a certificate/forced SSL -> RustProxy TLS listener. Certificate - files are not copied: the application-managed RustProxy default certificate is - used unless `--certificate-id` names an existing RustProxy inventory certificate; -- `ssl_forced` -> RustProxy `force_ssl=true`. When HTTP and HTTPS listeners exist, - both are attached so cleartext requests can receive RustProxy's `308` redirect to - HTTPS. If no TLS listener exists, an SSL-forced NPM host is skipped instead of being - imported with weaker semantics. - -Migrated backends use `allow_private=true` because NPM commonly proxies to LAN or -container origins. The migration tool follows RustProxy 0.22.1 and imports backend `tls_verify=false` unless you later enable verification explicitly. -Use `--npm-compatible-backend-tls` when an NPM HTTPS origin depends on -the common Nginx behavior of not verifying the upstream certificate; review that -exception after migration. - -The following are reported for manual review rather than guessed: NPM access lists, -advanced Nginx snippets, custom locations, NPM cache settings and `block_exploits`. -Existing RustProxy hostname ownership is never silently replaced; conflicting NPM -rows are skipped and shown in the report. - -## SQLite location - -The standard NPM SQLite path inside the container is `/data/database.sqlite`. On a -Docker host with a bind mount, pass the corresponding host path. For a named volume, -`docker volume inspect` shows its mountpoint. - -Read the live mounted database rather than copying only `database.sqlite` from a -running WAL-mode instance; copying without its WAL file can omit recent changes. - -## Dry run - -Set the password in the environment or omit it and use the interactive prompt: - -```bash -export RUSTPROXY_USERNAME=admin -export RUSTPROXY_PASSWORD='...' - -python3 scripts/migration/migrate_npm.py \ - --database /path/to/npm/data/database.sqlite \ - --rustproxy https://rustproxy.example.net:9090 -``` - -The default run is read-only. It creates `npm-migration-plan.json` with mode `0600` -and prints every imported/skipped NPM row plus manual-review warnings. - -If the administration panel intentionally uses the application self-signed -certificate and the script connects directly to it, add: - -```bash ---insecure-admin-tls -``` - -Prefer `--ca-file /path/to/ca.pem` or a hostname with a trusted certificate where -available. - -By default the first enabled HTTP listener and first enabled TLS listener are used. -For `ssl_forced` hosts both are selected when available. `--force-https` applies the -same Force SSL policy to every imported host. Select explicit listener IDs when needed: - -```bash ---http-listener public-http --https-listener public-https -``` - -## Apply - -After reviewing `npm-migration-plan.json`: - -```bash -python3 scripts/migration/migrate_npm.py \ - --database /path/to/npm/data/database.sqlite \ - --rustproxy https://rustproxy.example.net:9090 \ - --apply -``` - -The write uses the revision returned by `GET /api/config`; a concurrent RustProxy -configuration change causes the API compare-and-swap to fail rather than overwrite a -newer revision. - -## Running from a helper container - -If the Docker host does not have Python 3, mount the NPM data read-only and mount the -RustProxy source/script into a temporary Python container. For example, from the -RustProxy directory: - -```bash -docker run --rm -it \ - --network host \ - -v /path/to/npm/data:/npm:ro \ - -v "$PWD/scripts/migration:/rustproxy-migration:ro" \ - python:3.13-alpine \ - python /rustproxy-migration/migrate_npm.py \ - --database /npm/database.sqlite \ - --rustproxy https://127.0.0.1:9090 \ - --insecure-admin-tls -``` - -Use `--apply` only after the dry-run plan is reviewed. diff --git a/docs/PERFORMANCE_0.33.8.md b/docs/PERFORMANCE_0.33.8.md deleted file mode 100644 index 5409a9a..0000000 --- a/docs/PERFORMANCE_0.33.8.md +++ /dev/null @@ -1,353 +0,0 @@ -# RustProxy 0.33.8: performance and compatibility results - -RustProxy 0.33.8 substantially improves local static-file throughput in this -experiment: **5.23 times the rebuilt original for 64 KiB and 10.13 times for -1 MiB**. Small proxy and WWW responses improve by 15.71% and 10.41%, respectively. -Results are mixed for larger proxy responses: 64 KiB improves by 2.41%, while -1 MiB decreases by 2.77%. This is a first optimization package, with remaining -distance to nginx and visible memory and observability costs. It is not a -universal speedup, nginx-equivalence or production-capacity claim. [B1, B2] - -The changes preserve the LB panel, schema version 10, existing feature controls, -TLS/cache defaults and bounded pool recycling. Actual process tests verify the -release separately from throughput measurements. The complete package is the -unit of attribution; no experiment isolates the contribution of each edit. [F1, -F2, S1] - -## Executable and evidence identity - -The tested final executable is `rust-proxy 0.33.8`, SHA256: - -```text -24cc77a084ee1db4cd007f86c4b18b319bdc2e3f19afae7804739abff5e62a12 -``` - -The rebuilt original 0.33.7 uses the same Rust 1.99.1 compiler, locked dependency -versions and release profile. Its SHA256 is -`85a91318b41a405d1322cbfb84979e05490a7c2ad429ee7cb0c0836acca63899`. -The independently supplied 0.33.7 executable has SHA256 -`c68d63326ddf1e25911a583d994a213132dfb1b094425fc6cf358470299f0a92`. -Differences from the supplied executable therefore remain a separate comparison; -they cannot be assigned solely to source edits or solely to compiler provenance. -[B1, F1] - -The primary run finished at `2026-10-17T09:01:41Z`. All **2,077** files in its -sealed manifest were independently rehashed. The SHA256 of that `SHA256SUMS` -file is `5036d1f27a53dd6434b4be5a9be99a6e300f10821cffcc15b79f4da06ff147ee`; -`runs.json` is -`ebd9cc06d52496f776deefc48615598a64f018a10be5811ac89401b06e821e5e`. -This report was created after the release build; raw results identify the tested -binary independently of subsequent documentation. Evidence identifiers below -refer to workspace-relative paths in the accompanying verification package. [B2] - -## What changed, and what remains deferred - -The supplied PERF-00..08 plan is broader than this release. Its boundaries are: - -| Plan item | Implemented here | Remaining work | -|---|---|---| -| PERF-00: reproducible measurement | Real HTTP runners, interleaved comparison, resource/telemetry capture, exact payload checks, separate syscall tracing | Full concurrency sweep, remote generator/origin, allocator/cycle profiles and per-edit experiments | -| PERF-01: keep-alive tuning | Existing controls and default limits retained | Controlled idle-pool/recycle sweep; no claim from the old configuration-only experiment | -| PERF-02: compiled feature plan | Request-local cache eligibility plan, credited below | Broader immutable `CompiledRequestPlan` for all optional features | -| PERF-03: cache OFF path | Skip inapplicable cache-policy/key/header/revalidation preparation; retain bypass reasons and cache ON semantics | Further changes only with profiles and equivalent regression coverage | -| PERF-04: body/telemetry cost | Narrow compression-header copies, direct ID hex formatting, lazy idle timers, shorter history mutex section | Wider `Exchange`/analytics redesign and other locking changes | -| PERF-05: routing context | Existing validated snapshots retained | Shared validated route context and rewrite invalidation redesign | -| PERF-06: static I/O | Narrow filesystem job context, borrowed pinned root, reads capped at 64 KiB, precise local EOF | Buffer sweep, secure sendfile/io_uring and extra FD/content caches | -| PERF-07: pools/connections | Existing semantics tested with real peers | Pool/selection optimization; no LB UI additions | -| PERF-08: release gate | Listed laboratory checks and 30-minute soak completed; documented rollback | Memory plateau, deployment-specific 24-hour test and staged rollout | - -`CachePlan` avoids force-cache/cookie matching and request-policy work when -ineligible, while retaining diagnostic reasons, origin conditionals, privacy, -range/transform interactions and publication tickets. WWW requests move headers -into local serving when cache conditionals do not need a retained copy. -Compression copies only relevant header values when needed. Request IDs retain -fresh OS randomness, SHA-256 and their 64-character lowercase representation. -Idle deadlines and cancellation semantics remain even when timer allocation is -deferred until a pending read. [S1, F1] - -Filesystem jobs receive a narrow root context. Nonempty paths borrow the pinned -root descriptor for the first secure open, while root-directory operations keep -independent directory offsets. Local file bodies report EOF with their final -bounded DATA frame; short reads and cancellation do not publish complete cache -entries. Arbitrary upstream bodies are not declared complete from Content-Length -alone, preserving trailers and framing checks. [S1, F3] - -History entry cloning and destruction of evicted entries occur outside its -mutex. The 4,096-entry history, chronological retention, `try_lock` admission, -drop counter, live broadcast and configured sinks retain their contract. -Compatibility repairs also cover explicit Host ports, long-form `.htaccess` -Expires, expired cache inventory, reserved diagnostic headers, escaped-JSON -secret redaction and certificate authorization ordering. These are included in -package-level results, without separately assigned RPS gains. [F1, F4, S1] - -## Measurement method - -The primary matrix contains **120 measured samples**: proxy/WWW, three object -sizes, four executables and five interleaved repetitions. Each run has 15 seconds -of measurement after three seconds of excluded warm-up, using 64 actual -HTTP/1.1 keep-alive connections. Payloads are 1,040, 65,536 and 1,048,576 bytes; -cache is OFF and requests use identity encoding. [B1] - -Four frontend workers are pinned to CPUs 0–3, two origin workers to 4–5, and -four wrk threads share CPUs 6–8. All processes share one Linux host/kernel and -an eight-CPU cgroup quota. nginx 1.26.3 uses sendfile, access logging OFF, -upstream keep-alive 64 and `proxy_buffering off`. RustProxy retains its existing -history/analytics and request-policy work; nginx is a differently featured -reference. This is a warm local-file-cache experiment. [B1] - -Before measurement, the actual ACME/Python startup must finish: authenticated -status must be terminal, with no active jobs/configured issuers and one stable -RustProxy process for five seconds. The harness validates process identities, -nginx worker counts and affinity throughout sampling. A discarded early attempt -had incomplete nginx descendant accounting and is retained only as diagnostic -evidence. Earlier syscall traces without this quiescence gate are also excluded. -[B1, S1] - -Exact HTTP 200 and payload hashes are checked before and after every measured -run; aggregate transfer bytes receive a gross sanity check. wrk does not hash -every timed response and counts status errors above 399. All 120 runs passed -these checks, with **77,713,019 completed requests and zero reported errors**. -No completed sample was removed to improve the result. [B1, B2] - -## Throughput and repeatability - -The table reports median requests/second across all five runs. - -| Scenario | Supplied 0.33.7 | Rebuilt 0.33.7 | Final 0.33.8 | nginx | -|---|---:|---:|---:|---:| -| Proxy, 1,040 B | 27,341.75 | 25,350.63 | 29,334.12 | 170,610.16 | -| Proxy, 64 KiB | 20,485.54 | 20,246.28 | 20,735.07 | 35,393.29 | -| Proxy, 1 MiB | 4,428.31 | 4,588.06 | 4,461.19 | 2,730.23 | -| WWW, 1,040 B | 22,535.57 | 22,558.35 | 24,906.20 | 351,605.13 | -| WWW, 64 KiB | 3,960.03 | 3,990.22 | 20,856.67 | 203,709.91 | -| WWW, 1 MiB | 272.40 | 279.80 | 2,833.71 | 12,611.49 | - -Gains use `100 × (median final / median reference − 1)`. nginx attainment uses -`100 × median final / median nginx`. [B1, B2] - -| Scenario | Gain vs rebuilt | Gain vs supplied | nginx attainment | Paired gain range vs rebuilt | -|---|---:|---:|---:|---:| -| Proxy, 1,040 B | +15.71% | +7.29% | 17.19% | +6.40% to +17.52% | -| Proxy, 64 KiB | +2.41% | +1.22% | 58.58% | −4.54% to +14.40% | -| Proxy, 1 MiB | **−2.77%** | +0.74% | 163.40% | −2.77% to +5.48% | -| WWW, 1,040 B | +10.41% | +10.52% | 7.08% | +1.73% to +23.63% | -| WWW, 64 KiB | +422.70% | +426.68% | 10.24% | +385.45% to +477.68% | -| WWW, 1 MiB | +912.75% | +940.29% | 22.47% | +818.88% to +991.33% | - -The five final/rebuilt ratios, in scheduled repetition order, are: - -| Scenario | Rep 1 | Rep 2 | Rep 3 | Rep 4 | Rep 5 | -|---|---:|---:|---:|---:|---:| -| Proxy, 1,040 B | 1.0679 | 1.0767 | 1.0640 | 1.1752 | 1.0747 | -| Proxy, 64 KiB | 1.0160 | 0.9546 | 1.0789 | 1.0707 | 1.1440 | -| Proxy, 1 MiB | 0.9723 | 0.9770 | 0.9868 | 1.0548 | 0.9883 | -| WWW, 1,040 B | 1.0173 | 1.0210 | 1.0893 | 1.1092 | 1.2363 | -| WWW, 64 KiB | 5.3555 | 4.8545 | 5.7768 | 5.3480 | 5.2270 | -| WWW, 1 MiB | 10.2897 | 10.5518 | 9.1888 | 10.9133 | 9.2866 | - -Five pairs support the large WWW effect's direction and magnitude, but do not -provide a production confidence interval. Proxy 64 KiB has a small headline -improvement with one negative pair. Proxy 1 MiB is slower in four pairs, so the -modest regression remains explicit. For proxy-small, the paired median gain is -7.47%, different from the 15.71% ratio of variant medians; both statistics are -available. Its supplied comparison also contains one negative pair. Do not -average these scenario percentages into a universal speedup. [B2] - -## Latency, CPU and resources - -Values below are medians of per-run measurements; latency percentiles are not -pooled. Frontend CPU includes its threads and background work. [B2] - -| Scenario | p95 ms, rebuilt → final | p99 ms, rebuilt → final | CPU µs/request, rebuilt → final | -|---|---:|---:|---:| -| Proxy, 1,040 B | 4.093 → 3.591 | 5.066 → 4.707 | 147.05 → 127.41 | -| Proxy, 64 KiB | 6.188 → 5.921 | 36.594 → 37.245 | 182.66 → 178.44 | -| Proxy, 1 MiB | 29.206 → 30.222 | 49.315 → 50.747 | 861.28 → 885.37 | -| WWW, 1,040 B | 4.956 → 4.432 | 6.038 → 5.432 | 155.68 → 140.05 | -| WWW, 64 KiB | 27.929 → 5.217 | 35.301 → 6.393 | 657.26 → 171.85 | -| WWW, 1 MiB | 291.222 → 31.234 | 318.346 → 35.295 | 8,388.17 → 1,219.50 | - -WWW 64-KiB/1-MiB CPU cost per request falls 73.85%/85.46%, while p95 falls -81.32%/89.27%. Proxy 1-MiB CPU rises 2.80%, p95 rises 3.48% and p99 rises -2.90%. Proxy 64-KiB p99 also rises 1.78%; improvement is not uniform. [B2] - -| Scenario | Sampled peak RSS MiB, rebuilt → final | Peak FDs, rebuilt → final | -|---|---:|---:| -| Proxy, 1,040 B | 56.965 → 59.137 | 162 → 162 | -| Proxy, 64 KiB | 68.543 → 69.105 | 160 → 157 | -| Proxy, 1 MiB | 81.359 → 83.684 | 153 → 151 | -| WWW, 1,040 B | 77.441 → 77.727 | 124 → 123 | -| WWW, 64 KiB | 79.043 → 102.156 | 145 → 124 | -| WWW, 1 MiB | 78.473 → 118.664 | 145 → 145 | - -Large WWW responses cost **23.113 MiB and 40.191 MiB** additional sampled peak -RSS. Processes persist across scenarios, so this includes allocator/workload -history. The source-derived 7.5-MiB extra reader-capacity example is not total -measured memory cost. Candidate 1-MiB end RSS increases from 114.508 to 118.664 -MiB across five repetitions; these short runs do not prove a plateau. End FDs -remain 17–19 for large WWW, without an accumulating signal here. Peaks are -sampled, not absolute bounds; nginx process RSS can double-count shared pages. -[B2, B3] - -Only one cgroup throttle event occurs: 7,839 µs during nginx proxy-small; Rust -intervals record none. The nginx proxy-small origin consumes approximately -1.982 of its two CPUs, plausibly limiting that reference. nginx static 64-KiB -uses 2.573 of three generator CPUs. Rust proxy uses roughly 3.70–3.95 frontend -CPUs, with generator/origin aggregate use below their allocations. These bounds -and the shared kernel limit capacity interpretation. nginx static 64-KiB p95 -is 34.361 ms despite p50 0.215 ms; its corrected closed-loop histogram is not -a generic nginx latency verdict. [B2, B3] - -## Observability under saturation - -History still uses best-effort admission. Median rejected-history entries as a -percentage of wrk completions are shown separately from HTTP failures. [B2] - -| Scenario | Supplied | Rebuilt | Final | Final minus rebuilt, percentage points | -|---|---:|---:|---:|---:| -| Proxy, 1,040 B | 3.350984% | 3.325181% | 1.290544% | −2.034637 | -| Proxy, 64 KiB | 2.231502% | 2.203964% | 0.780042% | −1.423922 | -| Proxy, 1 MiB | 0.763138% | 0.765001% | 0.221602% | −0.543399 | -| WWW, 1,040 B | 3.908337% | 3.851348% | 1.504784% | −2.346564 | -| WWW, 64 KiB | 0.707405% | 0.700592% | 1.230427% | **+0.529835** | -| WWW, 1 MiB | 0.114600% | 0.120948% | 0.165351% | **+0.044403** | - -For WWW 64 KiB, every final fraction exceeds its paired baseline at 5.23-times -throughput. For 1 MiB, four fractions increase at 10.13-times throughput. These -are saturation comparisons, not fixed-arrival-rate experiments. The increases -must remain visible; this release does not guarantee zero history rejection. -History rejection does not itself reject live broadcast or external sinks. [B2, -F1] - -Analytics queue drops are zero in all Rust samples. Sink, error-file and -cold-cache queue drops are also zero, with those paths disabled in this primary -configuration; this does not prove their active throughput. Asynchronous -recorded analytics counts differ from wrk completions. Status/traffic snapshots -bracket load, postflight and possible in-flight work, making rate denominators -approximate. Raw counters, every repetition and aggregate weighted fractions are -retained; they are not presented as exact loss probabilities. [B1, B2] - -## Functional verification and inherited defects - -| Verification layer | Final result | Evidence | -|---|---|---| -| Rust suites | **706 passed**, zero failed, one optional ID timing microbenchmark ignored | F1 | -| Actual HTTP process regression | **152/152 cases passed** | F2 | -| Actual TLS/H2/H3/QUIC and H1 WebSocket tests | **111/111 assertions passed** | F3 | -| Two-origin LB behaviour | **17/17 cases passed** | F2 | -| Finite concurrency | **18 cases, 1,920 exact responses**, zero failures | F2 | -| Separate syscall experiment | **1,042 exact response checks** | P1 | - -The suites protect cache OFF/ON, privacy, Vary/scheme separation, purge barriers, -range/conditional/HEAD, compression/transforms, ACL/SSRF policy, administration -session/CSRF, streaming uploads, cancellation, non-replayed writes and leases -through EOF. History tests check exact fields, eviction accounting and real -file/live delivery during forced contention. SQL and disposable stopped-source -upgrade checks pass; UI checks remain simulated API tests. PHP coverage uses a -deterministic FastCGI peer, not a deployed PHP-FPM application. [F1–F4] - -Both original executables reproduced three live WWW failures: -`observability.static_complete_after_content_length`, `cache.static_hit` and -`cache.static_empty_hit`. All three pass on the final binary; the final expanded -original matrix retained 149 passes and those three failures. Precise local EOF -and initially empty-body handling restore complete-response accounting and cache -publication. Separately, the 22 original Rust failures were audited as six real -production defects, one missing embedded document and fifteen stale/incomplete -fixtures. Corrections retain privacy and invalidation guarantees. [F2, F4] - -The syscall experiment traced 500 measured static 64-KiB requests per binary, -plus warm-up/post-detach checks. Selected calls decreased from 60,374 to 7,502 -(87.57%); `read` decreased from 8,135 to 501. All 1,042 responses were exact, -and both traced processes detached and stopped cleanly. This supports a reduction -in observed system-call work; it is not an 87.57% throughput gain, allocation -profile or per-edit attribution. [P1] - -## Completed 30-minute mixed-traffic soak - -The final executable completed **1,800.000810 seconds** of paced HTTP/1.1 -proxy/static traffic at an offered 100 requests/s. All **180,000 workload -operations passed**, with zero response errors and zero scheduler-lateness -events. This includes **1,782 intentional client closes after 128 bytes**; -the remaining **178,218 operations** checked the complete expected response -body and status, including HEAD and ranges. A controlled close does not prove -that the server had not already sent the rest of the body. Every operation used -a new frontend TCP connection; this is not a long-lived keep-alive soak. [F5] - -All **14 cache toggles** passed **32 source/mode checks comprising 64 additional -GETs**, covering real HIT behaviour after enabling and bypass after disabling. -The fixture restored its base configuration, completed a 15-second cooldown, -and verified **backend active exchanges = 0**. All four recorded server/origin -processes and the disposable fixture directory were absent after teardown. -The final binary hash remained unchanged. [F5] - -| Observation | Final soak result | -|---|---| -| Analytics queue rejects | **0**; recorded-request delta **180,060**, including post-baseline cache probes and cooldown | -| History admission rejects | **31 observed**, from counter 0 at 0.001 s to 31 at 1,798.842 s; this counter was not resampled after cooldown | -| Upstream pool recycling | **1 observed rotation**; the normal bounded recycling policy remained active | -| Sampled RSS peak | **60.641 MiB** | -| RSS median, first minute after warm-up to final sampled minute | **52.273 → 60.016 MiB**, a **7.742 MiB increase** | -| Open file descriptors | **20 at start, 23 sampled peak, 18 after cooldown** | - -The final sampled minute includes the cooldown. RSS growth and the descriptive -linear fit of approximately 16.165 MiB/hour are observations over this short, -cache-toggling run, not an extrapolation or a diagnosis of a leak. This test -**does not establish a memory plateau**. There was no accumulating FD signal, -but low-rate correctness and resource observations do not establish stability -at the much higher saturation rates in the primary benchmark. There is no -matched baseline soak from which to attribute this RSS change to the patch. -The RSS/FD samples, both telemetry series and the independent interpretation -are retained. No 24-hour, TLS/H2/H3 or production-traffic soak was executed. [F5] - -## Reproduction and deployment boundary - -Use project entry points with the private umask they establish: - -```bash -./dev.sh check -CARGO_INCREMENTAL=0 ./dev.sh test -./dev.sh release -``` - -Set the executable/tool variables to reviewed absolute paths and use a fresh -results directory. Build the untouched baseline with the same toolchain/lock -before comparing it with the candidate: - -```bash -python3 -B scripts/performance/benchmark.py \ - --binary "supplied=$SUPPLIED_BINARY" --binary "before=$REBUILT_BASELINE" \ - --binary "after=$CANDIDATE_BINARY" --nginx "$NGINX_BINARY" --wrk "$WRK_BINARY" \ - --out "$RESULTS_DIR" --workers 4 --origin-workers 2 \ - --server-cpus 0-3 --origin-cpus 4-5 --client-cpus 6-8 --client-threads 4 \ - --concurrency 64 --modes proxy,static --sizes small,large,huge \ - --repeats 5 --duration 15 --warmup 3 -``` - -See `scripts/performance/README.md` for dependencies and separate regression, -transport, profile and soak commands. The offline analyzer refuses incomplete -or unsealed data. Its outputs include `comparisons.csv`, `paired-ratios.csv`, -`variants.csv`, `observability.csv` and `observability-paired.csv`. [B2] - -Remaining acceptance includes remote-network latency/loss, actual PHP-FPM, -Tinyauth and DNS infrastructure, H2/H3 WebSocket tunnels, deployment-specific -24-hour testing and staged rollout. No production deployment or service-manager -rollback was executed. The detailed script-specific upgrade/rollback review is -`verification/release-scope-and-rollback.txt`: current `update.sh` retains two -installed releases and three pre-update backups, conflicting with the older -installation document's no-pruning statement. Keep an independent recovery copy -outside automatic retention and restore matching code/state when needed. [S1] - -## Evidence index - -| ID | Workspace-relative evidence | -|---|---| -| B1 | `verification/benchmark-primary/`: `runs.json`, `schedule.json`, `environment.json`, `completion.json`, fixture configurations, raw responses/resources/telemetry and `SHA256SUMS` | -| B2 | `verification/analysis/benchmark-primary/analysis.json`, companion CSV/text files, and `verification/analysis/primary-review.txt` | -| B3 | `verification/static-primary-resource-review.txt` and `verification/primary-teardown-audit.json` | -| F1 | `verification/build/final-tests-summary.json`, `dev-test-final.log`; source `docs/TEST_REPORT.md` | -| F2 | `verification/finalrelease-summary.json`; `http-release/`, `lb-release/`, `concurrency-release/` reports beneath `verification/` | -| F3 | `verification/transport-release/release/report.json`; `verification/static-compatibility-audit.txt` | -| F4 | `verification/baseline-failure-audit.txt`; `verification/source_checks/results.json` | -| F5 | `verification/soak-release/`; `verification/soak-release-review.json` and `.txt`; `verification/soak-process-teardown.json` | -| P1 | `verification/syscalls-release/` and `verification/syscalls-release-analysis.txt` | -| S1 | `verification/release-scope-and-rollback.txt`; supplied `AGENT_PERFORMANCE_EXECUTION_PLAN.md`; implementation references listed in that review | diff --git a/docs/PHP_FPM.md b/docs/PHP_FPM.md index 1a382ce..8a0e429 100644 --- a/docs/PHP_FPM.md +++ b/docs/PHP_FPM.md @@ -1,417 +1,54 @@ -# PHP-FPM / FastCGI for the Web Server +# PHP-FPM with Web Services -**Separate pools for distinct Web Services:** Reusing the same PHP-FPM socket -with different `PHP_ADMIN_VALUE` maps can leak `open_basedir` restrictions into -a different virtual host. See the 0.29.7 guide -[PHP_FPM_ISOLATION.md](PHP_FPM_ISOLATION.md) for remediation and tests. +RustProxy's Web Server supports **FastCGI** forwarding to an existing **PHP-FPM** pool for configured script extensions. RustProxy does not include a PHP interpreter or run the PHP application itself. -RustProxy 0.25 can execute configured PHP extensions through a long-running PHP-FPM pool while preserving the reusable Web Service architecture. +## Before enabling PHP -## Architecture +Install and start PHP-FPM independently, configure the application files and create a **dedicated pool/socket for each application or security domain**. Set the correct Unix user/group, socket permissions, PHP version and pool-level configuration. Restrict a TCP FPM endpoint to a trusted private interface; never expose FastCGI directly to the Internet. + +In **Web Server → PHP-FPM profiles** create a reusable profile, or set PHP options in one Web Service. Select a socket such as `unix:/run/php/app.sock` or a reachable TCP endpoint (use the syntax accepted by the editor); enable PHP, choose extensions (default `.php`) and set request/connect timeouts and concurrency. Attach the profile to a Web Service and publish that service using a Vhost. + +## Important settings + +| Setting | Effect | +|---|---| +| **Endpoint** | Unix socket or private TCP address of a real PHP-FPM service. | +| **Extensions** | Which files are eligible for PHP execution. Confirm front-controller routes and direct PHP paths. | +| **`try_files` / PATH_INFO** | Resolve PHP front-controller or `script.php/...` paths, where supported, without accidentally executing an unintended file. | +| **Connect/request timeouts** | Bound waiting for FPM connection and application execution. Defaults in source include 1 s connect and 30 s request. | +| **Concurrent requests** | Local RustProxy FastCGI request limit (default 64), **not** the number of FPM worker processes. | +| **Authorization forwarding** | Optional preservation of eligible `Authorization` information (default enabled). Review application expectations. | +| **Custom FastCGI parameters** | Static application-specific settings. Reserved transport/identity parameters cannot be overridden arbitrarily. | +| **PHP values** | `PHP_VALUE` and `PHP_ADMIN_VALUE` options for the external FPM workers. | +| **Body/header limits** | Bound upload size, response headers and stderr exposed in logs. | + +## Pool isolation is mandatory for mixed applications + +PHP-FPM workers can retain `PHP_VALUE` / `PHP_ADMIN_VALUE` behavior across requests. **Two applications with different INI/admin settings must not share one FPM worker pool**, even when they have different RustProxy Vhosts. RustProxy may warn about conflicting shared endpoints but cannot isolate PHP workers inside FPM. + +Example separation: ```text -Request - -> existing listener / vhost / location / ACL / auth - -> Web Server -> Web Service - |- static file - `- configured PHP extension -> FastCGI -> PHP-FPM - -> existing error/header/logging/metrics/transport pipeline +Web Service: blog -> unix:/run/php/php_blog.sock +Web Service: webmail -> unix:/run/php/php_webmail.sock ``` -Vhosts and locations still reference only a Web Service. Each Web Service may -use local PHP settings or reference a shared `php_fpm_profiles` entry from the -normal revisioned configuration; its local settings are retained on detach. +Give each pool access only to its own application's files; configure `open_basedir` and other hardening at FPM pool level where possible. Restart the affected FPM pools after policy changes to retire existing workers. -### Shared PHP-FPM profiles (0.29.4) +## WordPress and Roundcube -In **Web Server → Edit Web Service → PHP-FPM**, select **Local configuration** -or a named profile. **Manage PHP-FPM profiles** opens a modal with an expandable -editor for every profile and an **Add profile** action. A profile contains the -same PHP execution, FastCGI and PHP_VALUE/PHP_ADMIN_VALUE settings as local -configuration. Changes to a shared profile affect all attached services after -validation and apply. In-use profiles cannot be deleted. +For **WordPress**, point the document root to the website directory, enable PHP for `.php`, use the appropriate front-controller (`index.php`) and verify that `wp-config.php` exists and is readable only where needed. A redirect to `setup-config.php` can indicate filesystem/FPM/open_basedir misconfiguration rather than proxy cache. -Configuration example: +For **Roundcube**, use its published web root and a separate FPM pool with the application's expected file access. Some Roundcube static script URLs use `PATH_INFO`; enable/test handling that resolves `static.php/...` without turning arbitrary strings into executable script names. -```json -{ - "php_fpm_profiles": [{ - "id": "php82", "name": "PHP 8.2 FPM", - "php": {"enabled": true, "endpoint": "unix:/run/php/php8.2-fpm.sock"} - }], - "web_services": [{ - "id": "site", "name": "Site", "document_root": "/var/www/site", - "php_profile_id": "php82" - }] -} -``` +These are routing examples; presets do not install either application or choose a safe OS-specific pool automatically. -Unspecified PHP fields inherit the safe serde defaults. A service without -`php_profile_id` uses its own `php` object as in earlier versions. +## Diagnostics -## Where to configure each PHP setting (0.29.7) +- `502/503/504`: check FPM health, Unix socket group permissions, endpoint path, capacity and configured timeouts. +- PHP errors/blank page: check FPM stderr, application logs and effective INI values. +- Incorrect site appears to inherit another site's `open_basedir`: use distinct FPM pools and restart them. +- Compressed PHP content truncated: verify current application response/framing and try the same request without compression; review FastCGI and response logs. +- Cache behavior differs from static files: PHP may ignore validators or send `private` / `Set-Cookie`, preventing shared cache storage. -Both editors expose the **same fields and validation**: - -1. **Web Server → Web Service → PHP-FPM → Local configuration:** set values on one service (`web_services[].php`). Select **Local configuration** in the profile selector first. -2. **Web Server → PHP-FPM profiles → Add/Edit profile:** set shared values on a profile (`php_fpm_profiles[].php`), then attach it in a Web Service's PHP-FPM tab (`php_profile_id`). Editing the profile changes every attached service. Per-service `php` values remain saved for later detach but do **not** override an attached profile. -3. **Actual PHP-FPM pool config** (`php-fpm.d/*.conf` or distribution equivalent): the pool must exist and listen on the configured socket. Isolate restrictive `open_basedir`/`php_admin_value` settings in *separate pools and sockets*, not by alternating request-time `PHP_ADMIN_VALUE` on shared workers. RustProxy does not launch PHP-FPM. - -| UI field (JSON property) | What to enter / sample | Notes | -| --- | --- | --- | -| Enable PHP-FPM / FastCGI (`enabled`) | Checked for PHP apps | Off means PHP is not executed. Never serve PHP source. | -| PHP-FPM socket (`endpoint`) | `unix:/run/php/php_wordpress.sock`, `tcp:127.0.0.1:9000`, `tcp:[::1]:9000` | Only local Unix sockets or TCP loopback **IP**, not hostnames. Socket ownership and permissions must allow RustProxy access. | -| PHP extensions (`extensions`) | `php, phtml` | Comma-separated, no leading dots; at most 16. Only listed extensions are executable. | -| Enable PHP PATH_INFO splitting (`path_info`) | Checked for Roundcube `/static.php/skins/...` | Splits an existing PHP script path and its trailing URL; off for ordinary WordPress unless needed. | -| Forward Authorization (`pass_authorization`) | Checked when API / Basic / Bearer auth is used | Sends request `Authorization` to `HTTP_AUTHORIZATION` in FastCGI. | -| Connection timeout (`connect_timeout_ms`) | `1000` = 1 second | Integer 50–60000 milliseconds. | -| Request timeout (`request_timeout_ms`) | `30000` = 30 seconds | Integer 100–3600000 milliseconds for the full FastCGI request. | -| Max simultaneous PHP requests (`max_concurrent_requests`) | `64` | Integer 1–4096 per Web Service; excess is rejected with HTTP 503. | -| Request-body limit (`max_request_body_bytes`) | `67108864` = 64 MiB; `0` = no additional limit | Integer 0–8589934592 **bytes**, independent of PHP `post_max_size`. | -| Response-header limit (`max_response_header_bytes`) | `65536` = 64 KiB | Integer 4096–1048576 bytes. | -| PHP-FPM STDERR log limit (`stderr_log_bytes`) | `4096`; `0` = disabled | Integer 0–65536 bytes collected per request. | -| Custom FastCGI parameters (`custom_params`) | `APP_ENV = production` | One static `NAME = value` per line. Names A-Z/0-9/underscore, beginning with a letter. No core CGI, HTTP_* or PHP control params. | -| PHP INI overrides (`php_values` / `PHP_VALUE`) | `memory_limit = 256M` | One php.ini `directive = value` per line. PHP decides which directives can change at this scope. | -| Administrator PHP INI overrides (`php_admin_values` / `PHP_ADMIN_VALUE`) | `open_basedir = /var/www/webmail/:/tmp/` | Trusted operator only; for security isolation prefer dedicated PHP-FPM pool configuration. Do not define the same directive in both INI maps. | - -**Syntax in the three multiline fields is not JSON:** use `key = value`, a separate line per entry; **do not** wrap it in `PHP_VALUE=...` or `php_admin_value[...]` inside the RustProxy editor. Those latter directives belong to the *native PHP-FPM pool configuration*, not the RustProxy FastCGI request fields. Empty values and control characters are rejected. Up to 64 entries per map, maximum 8192 bytes per value and 128 KiB combined. - -### Ready-to-use WordPress and Roundcube examples - -**WordPress — Web Service `/var/www/linuxiarz.pl/`, dedicated FPM socket:** - -```text -PHP enabled: checked -PHP PATH_INFO: unchecked -Endpoint: unix:/run/php/php_wordpress.sock -PHP extensions: php -PHP_VALUE: - memory_limit = 256M - upload_max_filesize = 64M - post_max_size = 64M - max_execution_time = 60 -PHP_ADMIN_VALUE: leave empty (or set security restrictions in WordPress's own FPM pool) -``` - -Use `index.php` in **Index files**, plus `try_files` lines `$uri`, `$uri/`, `/index.php?$query_string` in **Routing**. The WordPress pool must be allowed to read its `wp-config.php`, `wp-includes` and `wp-content` directories. - -**Roundcube — Web Service `/var/www/webmail/public_html`, separate FPM socket:** - -```text -PHP enabled: checked -PHP PATH_INFO: checked -Endpoint: unix:/run/php/php_roundcube.sock -PHP extensions: php -PHP_VALUE: - memory_limit = 256M - upload_max_filesize = 64M - post_max_size = 64M -PHP_ADMIN_VALUE: prefer the native Roundcube PHP-FPM pool instead; e.g.: - open_basedir = /var/www/webmail/:/usr/lib/php/:/tmp/:/etc/enigma -``` - -Add additional permitted external paths only if Roundcube's actual dependencies need them. Roundcube assets such as `/static.php/skins/elastic/...` require PATH_INFO splitting. Restart/recycle workers after separating pools; using a different RustProxy profile but the **same socket** does not isolate the worker state. See [pool isolation](PHP_FPM_ISOLATION.md). - -### FastCGI vs php.ini examples - -```text -# Custom FastCGI parameters — application-specific static environment: -APP_ENV = production -APP_DEBUG = 0 - -# PHP_VALUE — regular PHP-level options: -memory_limit = 256M -max_execution_time = 60 -error_reporting = E_ALL -log_errors = On -display_errors = Off -date.timezone = Europe/Warsaw - -# PHP_ADMIN_VALUE — privileged operator-level options: -open_basedir = /var/www/webmail/:/usr/lib/php/:/tmp/ -``` - -The `#` comments above are explanations separating fields; **do not paste them** into the corresponding form inputs. Boolean INI directives commonly accept `On`/`Off`, size directives can accept `K`/`M`/`G` and timeouts use seconds. The actual PHP/FPM version determines supported directives and permitted scopes. - -## Runtime model - -RustProxy implements the FastCGI protocol directly. It does not spawn `php`, `php-cgi` or shell processes for individual requests. - -Supported endpoint syntax: - -```text -unix:/run/php/php-fpm.sock -tcp:127.0.0.1:9000 -tcp:[::1]:9000 -``` - -Use the `unix:` prefix for a Unix-domain socket. Use `tcp:` followed by a literal loopback IP address and port for TCP. Hostnames and non-loopback addresses are rejected. Unix sockets are preferred for a local PHP-FPM installation; exposing FastCGI directly to an untrusted network is not supported by this configuration. - -## Web Service configuration - -```json -{ - "index": ["index.html", "index.php"], - "try_files": ["$uri", "$uri/", "/index.php?$query_string"], - "php": { - "enabled": true, - "extensions": ["php"], - "endpoint": "unix:/run/php/php-fpm.sock", - "connect_timeout_ms": 1000, - "request_timeout_ms": 30000, - "max_concurrent_requests": 64, - "pass_authorization": true, - "max_request_body_bytes": 0, - "max_response_header_bytes": 65536, - "stderr_log_bytes": 4096, - "custom_params": { - "APP_ENV": "production" - }, - "php_values": { - "memory_limit": "256M", - "upload_max_filesize": "64M", - "post_max_size": "64M" - } - }, - "symlink_policy": "deny" -} -``` - -The normal ordered `index` list handles `index.php`; there is no separate PHP-index option. - -### Additional PHP/FastCGI options - -- `path_info`: default-off secure script/path splitting. When enabled, `/index.php/foo/bar` may resolve the existing `index.php` script and pass `/foo/bar` as `PATH_INFO`. -- `pass_authorization`: forwards the inbound `Authorization` header as `HTTP_AUTHORIZATION`. Disable it when the application does not need that header. -- `max_request_body_bytes`: streaming request-body limit enforced before/during FastCGI forwarding. `0` means no additional RustProxy-side limit. A request exceeding the configured value receives `413 Payload Too Large`. -- `max_response_header_bytes`: maximum bytes accepted before the CGI response-header terminator. The allowed range is 4096..1048576 bytes. -- `stderr_log_bytes`: maximum PHP-FPM stderr bytes retained for one diagnostic request entry. `0` disables stderr capture; maximum 65536. -- `custom_params`: trusted static FastCGI parameters supplied by the operator. -- `php_values`: php.ini-style overrides encoded into one validated FastCGI `PHP_VALUE` parameter. - -### Custom FastCGI parameter syntax - -In the administration UI use one entry per line: - -```text -APP_ENV = production -APP_DEBUG = 0 -TENANT = main -``` - -Names are normalized to uppercase and are limited to letters, digits and `_` (the first character must be a letter). Values are static: there is no request-variable interpolation in this field. RustProxy rejects core CGI parameters, `HTTP_*`, `PHP_VALUE` and `PHP_ADMIN_VALUE`, so custom values cannot replace `SCRIPT_FILENAME`, routing data, HTTP-derived fields or PHP control parameters. Up to 64 custom FastCGI parameters and 64 PHP INI overrides are accepted, with at most 128 KiB of combined custom parameter/value data. - -### PHP INI (`PHP_VALUE`) syntax - -Use one `directive = value` entry per line: - -```text -memory_limit = 256M -upload_max_filesize = 64M -post_max_size = 64M -max_execution_time = 30 -``` - -RustProxy validates the lines and combines them with newline separators into the FastCGI `PHP_VALUE` parameter. PHP-FPM/PHP still decides whether a directive is changeable at this scope. `PHP_ADMIN_VALUE` is available separately through the validated administrator INI overrides for trusted PHP-FPM endpoints. Control characters/newlines inside names or values are rejected. - -These custom parameters are stored as normal Web Service configuration. Do not put secrets there unless normal configuration storage is an acceptable location for them. - -## `try_files` front controller - -A typical application can use: - -```text -$uri -$uri/ -/index.php?$query_string -``` - -Existing static files/directories are used first. The final path becomes the fallback and can therefore reach `index.php`. This resolution remains inside the already-selected Web Service and does not introduce a second vhost router. - -## Roundcube 1.7 static.php / PATH_INFO (0.29.5) - -Roundcube serves CSS, JavaScript and images through URLs such as -`/static.php/skins/elastic/styles/styles.min.css?s=123`. These paths are -**PHP-FPM requests to the existing** `public_html/static.php` **script**, not -static filesystem lookups. Configure the selected Web Service (or attached -shared PHP-FPM profile) with `php.enabled: true`, `php.path_info: true`, and -`php.extensions: ["php"]`. Point its root to the Roundcube `public_html` tree. - -You can keep the normal Web Service `try_files` (`$uri`, `$uri/`, -`/index.php?$query_string`). RustProxy 0.29.5 resolves a valid PHP script plus -PATH_INFO after security checks and rewrites, **before** falling back through -`try_files`. No regex rewrite or extra Web Service is required for these URLs -when the main vhost selects this Web Service. - -For `/static.php/skins/elastic/images/favicon.ico?s=123`, FastCGI receives: - -```text -SCRIPT_NAME=/static.php -SCRIPT_FILENAME=/static.php -PATH_INFO=/skins/elastic/images/favicon.ico -REQUEST_URI=/static.php/skins/elastic/images/favicon.ico?s=123 -QUERY_STRING=s=123 -``` - -PHP-FPM still needs filesystem access to `static.php` and the Roundcube -installation outside `public_html` that script reads. The PHP `PATH_INFO` -option is explicit and remains off by default for unrelated services. - -## Bounded execution - -Each compiled Web Service owns a bounded PHP concurrency semaphore. `max_concurrent_requests` limits simultaneous FastCGI requests. RustProxy does not create an unbounded wait queue: if no execution slot is immediately available it returns `503 Service Unavailable` with `Retry-After`. - -Connection establishment and complete FastCGI request execution have independent configured time limits. A connect failure produces a gateway error; a request timeout produces a gateway timeout. The outer normal RustProxy error-page processing remains able to replace these responses. - -Request bodies are split into bounded FastCGI records rather than collected into one large buffer. When `max_request_body_bytes` is non-zero, the limit is checked both from `Content-Length` when available and while streaming chunked/unknown-length bodies. PHP-FPM stdout is parsed and delivered through a bounded response channel so application output can stream to the client. - -## Secure script selection - -Only configured extensions are executable. Before PHP is selected, RustProxy resolves the request/`try_files`/index path using the Web Server filesystem confinement rules and verifies that it is a regular file below the document root. - -When PHP handling was selected: - -- PHP source is never returned as a fallback; -- an unavailable PHP-FPM endpoint produces 502/504 behavior rather than source disclosure; -- encoded traversal/separators and `..` remain rejected; -- the current implementation requires `symlink_policy=deny`. - -The last restriction is deliberate: PHP-FPM opens `SCRIPT_FILENAME` independently, so RustProxy does not claim an `inside_root` symlink guarantee that the external process cannot enforce atomically. - -## FastCGI parameters - -RustProxy supplies a controlled CGI/FastCGI parameter set including: - -- `REQUEST_METHOD` -- `SCRIPT_FILENAME` -- `SCRIPT_NAME` -- `PATH_INFO` when `path_info` is enabled and a trailing path was securely split from an existing PHP script -- `QUERY_STRING` -- `REQUEST_URI` -- `DOCUMENT_ROOT` -- `SERVER_PROTOCOL` -- `SERVER_NAME` -- `SERVER_PORT` -- `REMOTE_ADDR` -- `REMOTE_PORT` when available -- `HTTPS` -- `GATEWAY_INTERFACE` -- `SERVER_SOFTWARE` -- `REDIRECT_STATUS` -- `CONTENT_TYPE` -- `CONTENT_LENGTH` -- selected request headers as `HTTP_*` - -Request headers cannot become FastCGI `PHP_VALUE` or `PHP_ADMIN_VALUE` parameters. Operator-defined `custom_params` are allowed only through the validated Web Service configuration and cannot override core CGI/HTTP-derived fields. Operator-defined `php_values` are the only path that creates `PHP_VALUE`; `PHP_ADMIN_VALUE` is never generated. - -An inbound `Proxy` header is **never** translated into FastCGI `HTTP_PROXY` (httpoxy mitigation), regardless of the optional Web Service hardening switch. The **Block common attack probes** option additionally blocks high-confidence sensitive/probe paths before PHP-FPM dispatch. FastCGI output cannot control downstream `Content-Length`, transfer framing, connection headers or `Proxy-*` transport headers; RustProxy owns downstream response framing. - -`PATH_INFO` splitting is optional and disabled by default. When enabled, RustProxy searches for an existing configured PHP script prefix inside the document root, keeps `SCRIPT_FILENAME`/`SCRIPT_NAME` on that script, and supplies only the remaining suffix as `PATH_INFO`. `REQUEST_URI` remains the original request target. - -## Response parsing - -The FastCGI stdout parser understands standard CGI response headers, including `Status`. Hop-by-hop transport headers are stripped before returning the response through RustProxy. - -For `HEAD`, PHP-FPM can still execute normally but the client response body is empty. GET/POST/PUT and other methods can reach a PHP script once routing resolves to a configured executable extension. - -PHP stderr is bounded by `stderr_log_bytes` and recorded as a RustProxy `WARN` diagnostic. It goes to the configured error file when enabled, or the console/systemd journal otherwise; it is not sent to the client as page content. CGI response headers are separately bounded by `max_response_header_bytes`. - -## Caching - -PHP output does not use static file validators or the static-file OS representation path. Application response headers and the normal vhost/location response-header pipeline control client/intermediary caching. - -The Web Service static `cache_rules` apply to local static file responses, not -to PHP stdout. When global/vhost caching is enabled, explicitly cacheable -PHP-FPM responses may enter the shared RustProxy cache; responses with -`Set-Cookie`, `Cache-Control: private` or `no-store` cannot be stored. In -particular, do not enable forced caching of authenticated WordPress pages -without auditing the origin response policy. HTTP/2 and HTTP/3 `:authority` -requests populate the `HTTP_HOST` FastCGI parameter, retaining the original -host/port from the incoming request when provided. - -## PHP installation - -RustProxy does not bundle PHP. Install and maintain PHP-FPM using the operating system/package ecosystem and enable only the extensions required by the application. - -Operational requirements: - -- a running PHP-FPM 8.x service supported by the operator's platform; -- a dedicated Unix socket or loopback TCP listener reachable by the RustProxy service account; -- document-root permissions usable by both RustProxy and the PHP-FPM worker identity as intended; -- correct socket owner/group/mode when using Unix sockets; -- PHP-FPM limits such as `pm.max_children`, application execution timeout and upload/body limits sized consistently with the RustProxy concurrency limit. - -Extensions such as PDO drivers, mbstring, intl, gd, curl or opcache are application dependencies, not RustProxy dependencies. - -## Example PHP-FPM pool concept - -The exact paths/users depend on the operating system. For production, use a **separate PHP-FPM worker identity per site/application**. Never run PHP-FPM as `root` or as the `rustproxy` service account. A compromised CMS/plugin then must not inherit access to RustProxy's SQLite database, Secret Store, certificate private keys or backups. - -A local pool can expose a socket such as: - -```ini -user = site_example -group = site_example -listen = /run/php/rustproxy-example.sock -listen.owner = www-data -listen.group = rustproxy -listen.mode = 0660 -``` - -The exact socket owner may differ by distribution; the important properties are that the PHP worker user is **not** `rustproxy`, and only RustProxy plus the pool manager can reach the socket. Configure the Web Service endpoint as: - -```text -unix:/run/php/rustproxy-example.sock -``` - -Keep `/var/lib/rustproxy` mode/private ownership inaccessible to every PHP pool. Prefer separate writable upload/data directories from executable PHP source, and prevent the application from creating executable PHP files in upload areas. A CMS-level arbitrary file upload or vulnerable plugin can otherwise become PHP code execution even when the reverse proxy itself is correct. - -Do not expose a PHP-FPM TCP listener to the public network. - -## Verification targets - -The implementation includes source/unit coverage for: - -- PHP front-controller resolution through `try_files`; -- request body delivery to a FastCGI fixture; -- FastCGI response parsing; -- connection failure returning a gateway error without PHP source disclosure; -- bounded endpoint/time/concurrency configuration validation. - -Release-host acceptance should additionally exercise a real PHP-FPM runtime over Unix socket and loopback TCP and verify ACL/auth/custom errors/headers plus H1/H2/H3 client transports. - - -## Per-Web-Service PHP administrative INI overrides (0.29.2) - -`php.php_admin_values` is a validated operator-managed string map sent in a single -`PHP_ADMIN_VALUE` FastCGI parameter. Example: - -```json -"php": { - "enabled": true, - "path_info": true, - "endpoint": "unix:/run/php/php-fpm-mail.sock", - "php_admin_values": { - "open_basedir": "/var/www/webmail/:/tmp/", - "display_errors": "Off" - } -} -``` - -Use separate Web Services for distinct per-location PHP settings, then assign those -services to the corresponding Custom Locations. Each Web Service can refer to the -same trusted PHP-FPM socket. The setting does **not** isolate worker process -identities or file permissions. Never expose PHP-FPM FastCGI to untrusted clients. -HTTP headers cannot supply `PHP_ADMIN_VALUE`, and `php_values` must not repeat -an admin directive. Control characters and excessive values are rejected. - -Prometheus metrics use only configured Web Service IDs, never script paths, -URLs, PHP values, or dynamic user labels. `rust_proxy_php_requests_total` and -`rust_proxy_php_responses_total{class=...}` count responses after headers have -been received (including proxy-generated failures); the cumulative header -latency metric intentionally excludes streamed body time. - -The same snapshot also exports `rust_proxy_web_server_requests_total`, -`rust_proxy_web_server_responses_total{class=...}` and -`rust_proxy_web_server_response_header_duration_microseconds_total` for all -Web Server requests (PHP and non-PHP). +Inspect **Web Server → Live metrics**, [Logs](LOGS.md) and the application's own FPM logs. The proxy's PHP request semaphore is not a replacement for monitoring external FPM worker capacity. diff --git a/docs/PHP_FPM_ISOLATION.md b/docs/PHP_FPM_ISOLATION.md deleted file mode 100644 index 331a9ee..0000000 --- a/docs/PHP_FPM_ISOLATION.md +++ /dev/null @@ -1,31 +0,0 @@ -# Dedicated PHP-FPM pools for distinct Web Services - -PHP-FPM may retain request-supplied `PHP_VALUE` and `PHP_ADMIN_VALUE` directives in long-lived workers. When two applications use one PHP-FPM socket but different INI overrides, a setting such as `open_basedir` can be inherited by the next request, even when RustProxy routes the request to the correct Web Service. **RustProxy cannot provide process-level isolation inside a shared external PHP-FPM worker.** - -## Affected operator configuration (0.29.6) - -- WordPress Web Service `blog-linuxiarz-pl` uses document root `/var/www/linuxiarz.pl/` and endpoint `unix:/run/php/php_rustproxy.sock`, without an `open_basedir` override. -- Roundcube Web Service `webmail-linuxiarz-pl` uses document root `/var/www/webmail/public_html` and the **same** endpoint. Its FastCGI `PHP_ADMIN_VALUE` includes `open_basedir=/var/www/webmail/:/usr/lib/php/:/tmp/:/etc/enigma:...`. -- WordPress PHP-FPM stderr confirms the Roundcube `open_basedir` at the WordPress file check. As a result, `wp-config.php` is not visible and WordPress redirects to `/wp-admin/setup-config.php` before triggering fatal errors. This is **not** a cache MISS defect. - -## Required operator remediation - -1. Back up the current FPM configuration. Provision separate, access-restricted PHP-FPM pools under the PHP-FPM service manager used by your system (NixOS configuration when applicable). Use distinct Unix sockets, for example `/run/php/php_wordpress.sock` and `/run/php/php_roundcube.sock`. Ensure RustProxy's runtime user may connect to both. Do **not** expose raw FastCGI publicly. -2. Configure pool-level `php_admin_value[open_basedir]` for **Roundcube only**, with exactly the filesystem paths it requires, or keep its validated override in RustProxy on a dedicated pool. Keep WordPress in its own pool with appropriate policy. Pool-level directives are preferable to request-supplied administrator directives for security-sensitive settings. -3. In **Web Server > Web Services** set `blog-linuxiarz-pl > PHP-FPM endpoint` to `unix:/run/php/php_wordpress.sock` and `webmail-linuxiarz-pl` to `unix:/run/php/php_roundcube.sock`. Shared PHP-FPM profiles must also refer to the corresponding correct endpoint. -4. Apply PHP-FPM configuration and **restart** the affected PHP-FPM service/pools to retire workers with leaked directives. Save the RustProxy Web Service changes. Check that both web apps respond and that private files remain blocked. -5. Repeat requests in alternating order from both vhosts and inspect PHP-FPM warnings. Suggested tests: `curl -I https://blog.linuxiarz.pl/`, `curl -I https://blog.linuxiarz.pl/wp-login.php`, `curl -I https://webmail.linuxiarz.pl/`. The WordPress home page must no longer redirect to `/wp-admin/setup-config.php` due to `open_basedir`. - -If WordPress still redirects to setup-config.php with a **dedicated** pool, check whether `wp-config.php` really exists and is readable by the pool user, the configured pool's own php_admin_value restrictions, and database credentials. Do not fix the issue by using unrestricted `open_basedir=/` globally or by disabling protections on Roundcube. - -## New 0.29.7 protection - -During configuration compilation, RustProxy emits a warning identifying pairs of Web Services sharing an endpoint with unequal `PHP_VALUE`/`PHP_ADMIN_VALUE` maps. For backward compatibility, the existing configuration remains loadable; the warning **does not** automatically rewrite operator FPM configuration or claim that sharing becomes safe. - -## Per-backend health - -For an internal 404 response origin, leave `pool_eligible: false`, set `health_check_enabled: false`, and continue routing a named `internal` location to its `backend_id`. If a backend does require separate probes, use an optional per-member `health_check` object (same shape as vhost `load_balancing.health_check`). - -## HIT-only cache diagnostic headers - -Enable global `cache.expose_only_hits: true`, optionally override each vhost at `vhosts[].cache.expose_only_hits`. HIT-only mode filters RustProxy diagnostic cache headers for MISS/BYPASS without altering internal hit/miss metrics or upstream cache eligibility. `Via` is a separate proxy-header option and can be disabled using `cache.via_header: null`. diff --git a/docs/PROMETHEUS.md b/docs/PROMETHEUS.md deleted file mode 100644 index ecb5700..0000000 --- a/docs/PROMETHEUS.md +++ /dev/null @@ -1,241 +0,0 @@ -# Dedicated Prometheus endpoint - -RustProxy exposes Prometheus text exposition 0.0.4 on a dedicated read-only -listener. It is independent from administrator browser sessions and does not -export raw client IPs, request paths, query strings, full User-Agent strings, cookies, -credentials, backend URLs or ACME account secrets. - -## Configuration - -```json -{ - "prometheus": { - "enabled": false, - "listen": "127.0.0.1:9191", - "path": "/metrics", - "allowed_cidrs": ["127.0.0.0/8", "::1/128"], - "bearer_token_env": null, - "allow_trusted_network_without_token": false, - "include_system": true, - "include_vhosts": true, - "include_traffic": true, - "include_backends": true, - "include_docker_vhosts": true, - "include_cache_domains": true, - "include_streams": true, - "include_logging": true - } -} -``` - -The listener is plain HTTP. Keep it on loopback, a private monitoring network or -behind a protected tunnel. `allowed_cidrs` is always evaluated against the actual -transport peer (`PeerAddr`); `Forwarded` and `X-Forwarded-For` never grant metrics -access. - -### Bearer token - -Set `bearer_token_env` to an environment-variable name such as -`RUST_PROXY_METRICS_TOKEN`. RustProxy stores only the variable name. The value must -contain 32..4096 printable non-whitespace ASCII characters and must be present at -startup. - -### Trusted network without a token - -`allow_trusted_network_without_token=true` explicitly allows a peer already matched -by `allowed_cidrs` to scrape without a bearer token. This can be used for a private -Prometheus VLAN. An explicit IPv4/IPv6 `/0` is rejected in this mode. Keep the CIDR -list narrow; enabling this option is an authentication decision, not a forwarded-IP -feature. - -A non-loopback exporter requires either a bearer-token variable or this explicit -trusted-network bypass. Changing enabled/listen/token-variable requires restart; -path, CIDRs, collector switches and the trusted-network bypass apply live. - -Only `GET` on the exact configured path without a query string is accepted. A -wrong path is `404`, wrong method `405`, disallowed peer `403`, invalid/missing -required bearer `401`, and exporter concurrency exhaustion `429`. - -## Web Server and PHP-FPM metrics (0.33.0) - -Prometheus already exports core Web Server/PHP response counters by configured -`web_service` ID. The 0.33.0 additions below are enabled with the existing -Prometheus exporter (no new listener, credentials, options or open routes). - -| Family | Type | Labels | Meaning | -|---|---|---|---| -| `rust_proxy_web_server_requests_total` | counter | `web_service` | Header-ready WWW responses, including PHP | -| `rust_proxy_web_server_responses_total` | counter | `web_service`, `class` | HTTP 2xx/3xx/4xx/5xx | -| `rust_proxy_web_server_response_header_duration_microseconds_total` | counter | `web_service` | Cumulative time until headers | -| `rust_proxy_web_server_requests_active` | gauge | `web_service` | Requests still awaiting headers | -| `rust_proxy_web_server_request_kind_total` | counter | `web_service`, `kind` | `static`, `php`, `other` handler | -| `rust_proxy_web_server_methods_total` | counter | `web_service`, `method` | `GET`, `POST`, `PUT`, `DELETE`, `other` | -| `rust_proxy_web_server_status_code_total` | counter | `web_service`, `status` | Selected 200/206/304/403/404/500/502/503 | -| `rust_proxy_web_server_response_body_bytes_total` | counter | `web_service` | Body bytes delivered before final compression | -| `rust_proxy_web_server_compression_total` | counter | `web_service`, `encoding` | Downstream gzip/br/zstd/deflate operations | -| `rust_proxy_php_requests_total` | counter | `web_service` | PHP response headers processed | -| `rust_proxy_php_responses_total` | counter | `web_service`, `class` | PHP response status classes | -| `rust_proxy_php_response_header_duration_microseconds_total` | counter | `web_service` | Cumulative PHP time until headers | -| `rust_proxy_php_response_body_bytes_total` | counter | `web_service` | PHP body bytes emitted before compression | -| `rust_proxy_php_concurrency_available` | gauge | `web_service` | Free RustProxy PHP request permits | -| `rust_proxy_php_concurrency_limit` | gauge | `web_service` | Configured concurrent PHP request limit | -| `rust_proxy_php_concurrency_in_use` | gauge | `web_service` | Permits currently held by PHP requests | -| `rust_proxy_php_failures_total` | counter | `web_service`, `reason` | Fixed FastCGI/PHP failure classes | - -PHP `reason` is one of `capacity`, `connect_timeout`, `connect_error`, -`request_timeout`, `request_io`, `response_timeout`, `response_io`, -`invalid_response`, `body_io`. PHP concurrency is the admission semaphore in -RustProxy, not external PHP worker health or PHP-FPM child count. Status-code -labels are deliberately restricted to a stable, useful subset; arbitrary code, -request paths, IP addresses and untrusted method strings are not labels. -The exported byte counters are measured **before the final dynamic response -encoder**, so treat them as payload sizes rather than measured network transfer. -Precompressed static file delivery is not counted in the downstream compression -family. All time-to-headers metrics exclude streamed transfer duration. - -Useful Grafana/PromQL examples: - -```promql -sum(rate(rust_proxy_web_server_requests_total[5m])) -sum by (class) (rate(rust_proxy_web_server_responses_total[5m])) -sum by (kind) (rate(rust_proxy_web_server_request_kind_total[5m])) -100 * sum(rust_proxy_php_concurrency_in_use) / clamp_min(sum(rust_proxy_php_concurrency_limit), 1) -1e-3 * sum(rate(rust_proxy_web_server_response_header_duration_microseconds_total[5m])) / clamp_min(sum(rate(rust_proxy_web_server_requests_total[5m])), 1e-9) -sum by (reason) (rate(rust_proxy_php_failures_total[5m])) -``` - -Import `grafana/rustproxy-overview.json` and use the **Web service** variable -for the new Web Server, PHP-FPM, per-service and correlation sections. - -## Coverage audit in 0.26.0 - -The exporter represents all scalar fields currently sampled by `diagnostics::View` when available. The per-interface `network_interfaces` list added in 0.18.6 is intentionally dashboard-only so kernel-created interface names cannot create an uncontrolled Prometheus label set. RustProxy also exports the bounded operational data already available from traffic metrics, cache, vhosts, backend pools, streams, logging, GeoIP and ACME renewal observations. - -| Area | Exported diagnostics | -|---|---| -| Global traffic | requests/completions/active/rejects/errors, status classes, request/response bytes, exchange lifetime, accepted/active/rejected connections | -| Protocols | HTTP/1.1, HTTP/2 and HTTP/3 requests, H2/H3 connections/rejects, TLS connections/handshake failures, WebSocket tunnels/bytes/errors/lifetime, compression counters | -| Admin | request/status counters and time to control-plane response headers | -| Process/host | diagnostic source, runtime platform info, sampled timestamp, system/process CPU, logical CPUs, RAM total/used, system swap total/used, system uptime, RSS total/anon/file/shmem, virtual memory/limit, VmData, process swap, FDs/limit, threads, process start time, load 1/5/15, network totals/rates, process disk I/O, cgroup memory current/max | -| GeoIP | loaded state plus IPv4/IPv6 range counts | -| Cache | hot entries/bytes, global hit ratio and counters, cold entries/bytes and movement/error counters, fill bytes, cold queue bytes/items, active cold read/write bytes, combined transient bytes, configured-domain hot/cold occupancy, hit ratio and decision data | -| Vhosts | desired enable/domain/path/cache settings, per-vhost traffic/status/error/byte counters and cumulative exchange-duration histogram | -| Traffic analytics | independent live collector (`include_traffic`): capture-state gauges, per-vhost request/error/body-byte/duration totals; approximate bounded top countries; fixed User-Agent families; fixed HTTP method families; status classes; cache outcomes; serving source (`web_server`, `reverse_proxy`, `local_response`, `unresolved`); and currently configured route IDs | -| Built-in Web Server | configured Web Service count/info, PHP and directory-listing flags, vhost/route source metadata, plus actual request traffic through the shared vhost counters and Traffic serving-source metrics | -| Redirect/404 hosts | desired enable state, domain/rule counts and local-response status | -| Backends | pool readiness/policy info, bounded backend status, health/drain/backup, active work and configured request-concurrency/queue limits, requests/errors/bytes, weight, separate active/passive failure state, active/passive successes and health-failure totals, unconditional HTTP-5xx/timeout/connect/I/O/slow-response observation counters, DNS/TLS/header-timeout/read-timeout/send-timeout diagnostics, capacity rejection/queue-timeout counters, retry attempt/success totals, upstream-client generation/current-generation requests/generation age/rotation totals, plus `passive_http_5xx_total`, `passive_timeouts_total`, `passive_connect_errors_total`, `passive_io_errors_total` and `passive_slow_total` aliases, health transitions, last status, current/EWMA latency, last-probe/failure/ejection/recovery timestamps and traffic rate for ordinary vhosts | -| Docker vhosts | separate `rust_proxy_docker_vhost_*` and `rust_proxy_docker_backend_*` families: configured agents/assignments/domains, vhost traffic/status/duration plus backend readiness, health, requests/errors/bytes, latency/EWMA, separate active/passive failure state, unconditional observation counters and matching passive-health aliases, retry attempt/success totals, upstream-client generation/current-generation requests/generation age/rotation totals, transitions and probe results | -| Streams | desired enable state, active/session/reject/error counters, bytes/packets and max connections | -| Logging | structured sink queue capacity, sent/error/drop counters plus the global live-log contention drop counter | -| TLS/ACME | configured certificate count, auto-renew flag, bounded issuer metadata, latest ACME job state, renewal check/expiry timestamps, failure count and status | -| Inventory | listeners/protocol flags, vhost/redirect/404/stream/ACL counts, request capacity and available permits, config revision and restart-required state | - -Values unavailable on the running OS are omitted rather than fabricated. Series -labels are bounded by configured IDs/domains, finite states, fixed protocol/status/UA/method/source -families, or the finite two-letter country space. Per-request URLs, raw client IPs, full -User-Agent values and arbitrary Host input are never labels. - -This is complete for **currently implemented bounded diagnostic views**. It does -not intentionally expose secrets, raw configuration documents, access-log entries -or unbounded per-request values merely to make them visible to Prometheus. - -## Prometheus examples - -Loopback/private CIDR scrape without a token when the explicit trusted option is -used: - -```yaml -scrape_configs: - - job_name: rustproxy - scrape_interval: 15s - metrics_path: /metrics - static_configs: - - targets: ['127.0.0.1:9191'] -``` - -With bearer authentication: - -```yaml - authorization: - type: Bearer - credentials_file: /etc/prometheus/secrets/rustproxy-token -``` - -Vhost rate: - -```promql -sum by (vhost) (rate(rust_proxy_vhost_requests_total[5m])) -``` - -Web Server traffic rate: - -```promql -sum by (vhost) (rate(rust_proxy_traffic_source_requests_total{source="web_server"}[5m])) -``` - -Top country snapshot from bounded Traffic analytics: - -```promql -sum by (country) (rust_proxy_traffic_country_requests) -``` - -Country series are populated when **Settings -> Logging -> Capture country** is enabled; User-Agent family series follow **Capture User-Agent**. The exporter never turns those request-path captures on by itself. -Current capture state is exposed as `rust_proxy_traffic_country_capture_enabled` and `rust_proxy_traffic_user_agent_capture_enabled`. Configured-route snapshots use `rust_proxy_traffic_route_requests`; raw URL paths are never exported as labels. - -Fixed User-Agent families: - -```promql -sum by (family) (rust_proxy_traffic_user_agent_family_requests_total) -``` - -Vhost p95 exchange lifetime: - -```promql -histogram_quantile(0.95, - sum by (vhost, le) (rate(rust_proxy_vhost_exchange_duration_seconds_bucket[5m]))) -``` - -Docker-vhost request rate (kept separate from ordinary vhosts): - -```promql -sum by (agent, vhost, service) (rate(rust_proxy_docker_vhost_requests_total[5m])) -``` - -Docker backend availability and latency: - -```promql -rust_proxy_docker_backend_up -rust_proxy_docker_backend_ewma_latency_seconds -``` - -Backend client lifecycle and retry diagnostics: - -```promql -rust_proxy_backend_upstream_client_generation_requests -rust_proxy_backend_upstream_client_generation_age_seconds -rate(rust_proxy_backend_upstream_client_rotations_total[5m]) -rate(rust_proxy_backend_retry_attempts_total[5m]) -rate(rust_proxy_backend_retry_successes_total[5m]) -``` - -The same fields are exported for Docker-discovered backends under the `rust_proxy_docker_backend_*` prefix. - -Certificate expiry window: - -```promql -rust_proxy_certificate_renewal_not_after_timestamp_seconds - time() -``` - -The supplied dashboard is `grafana/rustproxy-overview.json`; Debian packages also -install it under `/usr/share/rustproxy/grafana/`. Its dedicated **Docker vhosts** row -uses only the separate Docker metric families and provides Docker-agent/service -filters so multi-agent installations can be inspected independently from ordinary -vhosts/backends. - -Reference: https://prometheus.io/docs/instrumenting/exposition_formats/ - -## Stream pool metrics (0.32.0) - -The existing global `include_streams` controls all Stream Prometheus families. Each configured Stream may set nullable `metrics_enabled` (default true); each backend may set its own nullable `metrics_enabled` (inherit from Stream). Detailed backend counters are emitted only when both flags are enabled. Basic per-Stream counters, backend active/drain and numeric health-state gauges remain observable with global Stream export enabled; routing and health monitoring never depend on these flags. Backend series use only configured `stream`, `backend` and `protocol` labels, never client IP or arbitrary UDP payload. `rust_proxy_stream_backend_health_state` encodes unverified=0, checking=1, healthy=2, unhealthy=3, disabled=4. Optional detailed families include bytes/packets, assignments, errors/rejects, sessions and health successes/failures. All counters reset on process startup. - -For v0.32.0 Stream pools, backend-level `metrics_enabled` overrides the Stream default; missing/null values inherit it. Detailed backend accounting defaults off, including for legacy `upstream` configurations. Global `include_streams` independently gates all Stream Prometheus export. diff --git a/docs/README.md b/docs/README.md index 54821ee..9bbd92f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,76 +1,41 @@ -# RustProxy documentation index +# RustProxy documentation -This is the canonical index for the current project documentation. The authenticated administration documentation page at `/docs` embeds these Markdown files in its **Reader** tab and keeps OpenAPI/Swagger in a separate **API** tab. +Practical guides for **RustProxy 0.34.0**. This index and the listed pages are also available in the authenticated panel at **`/docs → Reader`**. The adjacent **API** tab provides the embedded OpenAPI reference. Paths below are links to actual files in the source archive. -## Project and release +## Getting started -- [Project README](../README.md) - product overview, installation entry points and current release notes. -- [ROADMAP](../ROADMAP.md) - planned and completed release scope. -- [CHANGELOG](../CHANGELOG.md) - user/operator-visible changes by release. +- [Installation and updates](INSTALL.md) — packages, build, first administrator, systemd and updates. +- [Container deployment](CONTAINER.md) — image, persistent state, ports and isolation. +- [Settings and configuration](CONFIGURATION.md) — what to configure and which changes need restart. +- [Troubleshooting](TROUBLESHOOTING.md) — symptom-based checks and release validation. -## Functional documentation +## Publishing services -- [WEB_SERVER](WEB_SERVER.md) - reusable Web Services, static-file behavior, ranges/validators and document-root security. -- [PHP_FPM](PHP_FPM.md) - implemented FastCGI/PHP-FPM integration, security model and runtime requirements. -- [PHP_FPM_ISOLATION](PHP_FPM_ISOLATION.md) - separate PHP-FPM pools to prevent cross-vhost INI settings leakage. -- [VHOSTS](VHOSTS.md) - vhosts and backend/source behavior. -- [CUSTOM_LOCATIONS](CUSTOM_LOCATIONS.md) - location matching, rewrites and local responses. -- [VHOST_ACCESS](VHOST_ACCESS.md) - access rules applied to vhosts and locations. -- [CACHE](CACHE.md) - cache behavior and policy. -- [TRANSFORM](TRANSFORM.md) - response transformation. -- [STREAMS](STREAMS.md) - stream proxying. -- [REDIRECTS](REDIRECTS.md) - redirect hosts and rules. -- [NOT_FOUND_HOSTS](NOT_FOUND_HOSTS.md) - local 404 host handling. -- [DOCKER_DISCOVERY](DOCKER_DISCOVERY.md) - Docker discovery and generated vhosts. -- [ACME_PROVIDERS](ACME_PROVIDERS.md) and [RENEWAL](RENEWAL.md) - certificate automation. -- [LIVE_LOGS](LIVE_LOGS.md), [FILE_LOGS](FILE_LOGS.md) and [ERROR_LOGGING](ERROR_LOGGING.md) - independent in-memory live request viewing, bounded on-disk log exploration, protocol-error persistence and optional error-file logging. +- [Vhosts and backends](VHOSTS.md) — domains, listener bindings, balancing, health and redirects. +- [Custom locations](CUSTOM_LOCATIONS.md) — path routing, rewrites, local responses and error fallbacks. +- [Access and authentication](ACCESS.md) — ACL, country/DDNS, Basic Auth, Tinyauth and rate limits. +- [Static Web Server](WEB_SERVER.md) — document roots, downloads, rules and file delivery. +- [PHP-FPM](PHP_FPM.md) — PHP service profiles, security and application examples. +- [Cache](CACHE.md) — HOT/COLD storage, privacy, TTL and invalidation. +- [Response delivery](RESPONSE_DELIVERY.md) — compression, static transformations and response headers. +- [HTTP transport](TRANSPORT.md) — HTTPS/H2/H3, WebSockets, origin protocols and timeouts. +- [TCP/UDP Streams](STREAMS.md) — non-HTTP forwarding and its security boundaries. -## Administration and operations +## Integrations and operations -- [INSTALL](INSTALL.md) - installation and managed updates. -- [SETTINGS](SETTINGS.md) - listeners, administration and response defaults. -- [API](API.md) - administration API overview. -- [API_ACCESS](API_ACCESS.md) - API authentication, CSRF and `/docs` security model. -- [SECRET_STORE_BACKUP](SECRET_STORE_BACKUP.md) - encrypted secret storage and backup/restore. -- [PROMETHEUS](PROMETHEUS.md) - monitoring endpoint and metrics. -- [CONTAINER](CONTAINER.md) - container deployment. -- [NPM_MIGRATION](NPM_MIGRATION.md) and [MIGRATION](MIGRATION.md) - migration tooling and historical migration notes. +- [Certificates and automatic renewal](ACME_PROVIDERS.md) — PEM, HTTP-01, DNS-01, Certbot and external certificates. +- [Docker discovery](DOCKER_DISCOVERY.md) — agent installation, networking and generated Vhosts. +- [Other integrations](INTEGRATIONS.md) — Tinyauth, NPM migration, OPNsense and monitoring tools. +- [Dashboard](DASHBOARD.md) — metrics panels, presets and configuration history. +- [Monitoring](MONITORING.md) — Prometheus, traffic, backend and cache observations. +- [Logs](LOGS.md) — live logs, stored logs, error logs and searches. +- [Backups and recovery](BACKUP.md) — encrypted backups, restore and Secret Store. +- [Migration](MIGRATION.md) — managed upgrades and Nginx Proxy Manager import. +- [Security](SECURITY.md) — important trust boundaries and limitations. +- [Administration API](API.md) and [API access](API_ACCESS.md) — endpoints, sessions and restricted certificate credentials. -## Development and testing +## Release information -- [MOCK_SERVER](MOCK_SERVER.md) - local Python mock origin used by development/integration checks. -- [MOCK_AUTH](MOCK_AUTH.md) - local fake Tinyauth peer for development only. -- [MEMORY_STRESS](MEMORY_STRESS.md) - sustained-memory audit, offline load simulations, real RustProxy staging stress and RSS measurement procedure. +[Changelog](../CHANGELOG.md) tracks user/operator-visible technical changes; [Roadmap](../ROADMAP.md) separates current verification work from future ideas. Historical low-level investigations, audits and benchmarks are not part of the live Reader. Keep them separately if you need an audit trail. -## Technical documentation - -- [ARCHITECTURE](ARCHITECTURE.md) - process and request architecture. -- [SECURITY](SECURITY.md) - current security model and limitations. -- [HTTP3](HTTP3.md) - HTTP/3 transport behavior. -- [TRANSPORT](TRANSPORT.md) - transport background/contract notes. -- [CACHE_DIAGNOSTICS](CACHE_DIAGNOSTICS.md) - cache diagnostics and interpretation. -- [REFERENCES](REFERENCES.md) - implementation references. - - -The pre-cleanup developer-level changelog is retained as [CHANGELOG_TECHNICAL_ARCHIVE](CHANGELOG_TECHNICAL_ARCHIVE.md) when low-level historical detail is needed. The root `CHANGELOG.md` remains the operator-facing release record. -Completed roadmap history is retained separately as [ROADMAP_HISTORY](ROADMAP_HISTORY.md); the root `ROADMAP.md` contains the current release scope and future direction. - -## Historical or version-labelled context - -### Retained audits and historical context - -- [SECURITY_AUDIT](SECURITY_AUDIT.md) - security audit material. -- [CHANGELOG_TECHNICAL_ARCHIVE](CHANGELOG_TECHNICAL_ARCHIVE.md) and [ROADMAP_HISTORY](ROADMAP_HISTORY.md) - retained long-form technical history and completed milestones. -- [ACME_RFC2136](ACME_RFC2136.md) - historical DNS provider details. -- [INTEGRATIONS_040](INTEGRATIONS_040.md) and [MANAGEMENT](MANAGEMENT.md) - retained design and integration notes. -- [TEST_REPORT](TEST_REPORT.md) - verification history and release gates. - -The old per-version implementation notes and audits removed from this trimmed source archive are no longer required to build the application or use the documentation Reader. Only registered documents are embedded at compile time. - -## Maintenance rules - -- Put user/operator-visible release changes in `CHANGELOG.md`. -- Put future release scope and unresolved work in `ROADMAP.md`. -- Keep detailed implementation evidence in technical documents or version audits, not in CHANGELOG. -- When functionality changes, update the functional document, this index, Reader coverage and cross-links in the same change. -- Remove obsolete or duplicated documents when they no longer provide historical or technical value; retain useful audits with an explicit historical label. +When changing functionality, update the appropriate guide, this index, the registered Reader documents and any OpenAPI/schema references together. Describe actual code behavior, not unimplemented plans. diff --git a/docs/REDIRECTS.md b/docs/REDIRECTS.md deleted file mode 100644 index 278e02e..0000000 --- a/docs/REDIRECTS.md +++ /dev/null @@ -1,66 +0,0 @@ -# Redirect hosts - -RustProxy 0.13.0 adds first-class redirect hosts on the **Redirects** dashboard page. A redirect host owns one or more source hostnames and named HTTP/HTTPS listeners and answers locally without opening a backend connection. - -Each host contains ordered redirect rules. The longest matching path wins. Rules can match `http`, `https` or either scheme and can use exact or path-prefix matching. Targets can be: - -- `url`: an absolute `http://` or `https://` URL, -- `host`: a hostname/authority with the target scheme set to preserve, HTTP or HTTPS, -- `path`: an absolute path on the same request authority. - -Supported status codes are 301, 302, 303, 307 and 308. Use 307/308 when the client must preserve the HTTP method. Path handling can append the complete source path, strip the matched prefix before appending, or replace the path entirely. Source query parameters can be preserved or dropped. `cache_seconds=0` emits `Cache-Control: no-store`; a positive value emits a bounded public `max-age`. - -Redirect hosts use the same named listener inventory and certificate library as vhosts. The dashboard uses the same listener-selection component for both editors. A new redirect preselects the standard enabled HTTP :80 and HTTPS :443 listeners together when available, while existing bindings are preserved. The same hostname cannot overlap a vhost or another redirect host on the same socket. Global host ACL policy still runs before a redirect response. Redirect rules never make DNS/TCP/TLS connections to the target; the client follows the resulting `Location` header. - -Configuration lives under `redirects` and normal dashboard saves use `PUT /api/config/redirects`. - - -## Example - -```json -{ - "redirects": [ - { - "id": "legacy-site", - "name": "Legacy site redirects", - "frontend": { - "hosts": ["old.example.com"], - "listener_ids": ["http", "https"] - }, - "certificate_id": "old-example", - "rules": [ - { - "id": "application", - "source_scheme": "any", - "match_type": "prefix", - "path_prefix": "/app", - "target_type": "host", - "target": "app.example.com", - "target_scheme": "https", - "path_mode": "strip_prefix", - "query_mode": "preserve", - "status_code": 308, - "cache_seconds": 300 - }, - { - "id": "fallback", - "match_type": "prefix", - "path_prefix": "/", - "target_type": "url", - "target": "https://www.example.com/", - "path_mode": "replace", - "query_mode": "drop", - "status_code": 302, - "cache_seconds": 0 - } - ] - } - ] -} -``` - -Rules are evaluated by longest path first; exact matching wins over prefix matching at the same path. A disabled redirect host reserves its hostname/listener ownership just like a disabled vhost, so another frontend cannot silently take over that traffic. - -## Hostname editor - -The Redirect source-domain field uses the same chip editor as Vhosts: add one hostname/wildcard at a time, remove individual chips, and keep listener selection in the shared frontend control. diff --git a/docs/REFERENCES.md b/docs/REFERENCES.md deleted file mode 100644 index b7e05fe..0000000 --- a/docs/REFERENCES.md +++ /dev/null @@ -1,65 +0,0 @@ -# Implementation references - -Primary specifications and library documentation consulted while implementing the -transport code. These references are not evidence that this source package was built -or tested successfully against a resolved dependency graph. - -## HTTP and WebSocket - -- HTTP semantics and intermediary header handling: https://www.rfc-editor.org/rfc/rfc9110 -- HTTP/1.1 messaging: https://www.rfc-editor.org/rfc/rfc9112 -- HTTP/2: https://www.rfc-editor.org/rfc/rfc9113 -- WebSocket handshake and framing: https://www.rfc-editor.org/rfc/rfc6455 -- Extended CONNECT bootstrap semantics: https://www.rfc-editor.org/rfc/rfc8441 -- WebSocket over HTTP/3: https://www.rfc-editor.org/rfc/rfc9220 -- hpx-h3 2.5.19: https://docs.rs/hpx-h3/2.5.19/h3/ -- Hyper HTTP/2 server builder: https://docs.rs/hyper/latest/hyper/server/conn/http2/struct.Builder.html -- Hyper upgrades: https://docs.rs/hyper/latest/hyper/upgrade/index.html -- hyper-util legacy client: https://docs.rs/hyper-util/latest/hyper_util/client/legacy/struct.Client.html -- Resolver interface: https://docs.rs/hyper-util/latest/hyper_util/client/legacy/connect/dns/index.html -- Tokio bounded-buffer bidirectional copying: https://docs.rs/tokio/latest/tokio/io/fn.copy_bidirectional.html - -## TLS - -- HTTPS connector builder: https://docs.rs/hyper-rustls/latest/hyper_rustls/struct.HttpsConnectorBuilder.html -- rustls signing key/certificate validation: https://docs.rs/rustls/latest/rustls/sign/struct.CertifiedKey.html -- rustls server certificate resolver: https://docs.rs/rustls/latest/rustls/server/trait.ResolvesServerCert.html -- SNI certificate bindings: https://docs.rs/rustls/latest/rustls/server/struct.ResolvesServerCertUsingSni.html -- Development certificate generation: https://docs.rs/rcgen/0.13.2/rcgen/fn.generate_simple_self_signed.html - -## Persistence and security - -- SQLx SQLite connection model: https://docs.rs/sqlx/latest/sqlx/struct.SqliteConnection.html -- SQLite online backup API: https://www.sqlite.org/backup.html -- OWASP SSRF guidance: https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html -- OWASP password storage: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html -- Prometheus instrumentation practices: https://prometheus.io/docs/practices/instrumentation/ - -The next Docker stages must use actual published port bindings and reconciliation, -not infer exposure from EXPOSE alone. Start with the official engine networking and -events documentation, and record the API versions actually tested in that release. - - -## Cache and compression - -- HTTP caching semantics and Age/freshness: https://www.rfc-editor.org/rfc/rfc9111 -- Cache-Status response field: https://www.rfc-editor.org/rfc/rfc9211 -- tower-http streaming compression: https://docs.rs/tower-http/latest/tower_http/compression/ -- Compression implementation API: https://docs.rs/crate/tower-http/latest/source/src/compression/service.rs -- Compression predicates: https://docs.rs/tower-http/latest/tower_http/compression/predicate/ - -References describe upstream APIs/specifications, not successful compilation of the -unresolved 0.2.0 dependency graph. Consult the exact Cargo.lock when generated. - -## Vhosts, keepalive and negotiation - -- Client pool timers, idle limits and HTTP/2 pings: https://docs.rs/hyper-util/latest/hyper_util/client/legacy/struct.Builder.html -- DNS Name parsing interface: https://docs.rs/hyper-util/latest/hyper_util/client/legacy/connect/dns/struct.Name.html -- TLS ALPN server configuration: https://docs.rs/rustls/latest/rustls/server/struct.ServerConfig.html -- HTTP strict transport security: https://www.rfc-editor.org/rfc/rfc6797 - -The library documentation is reference material, not proof that a particular future -resolved graph compiles. The reported tower-http 0.6.11 `SizeAbove::new(u16)` call -has been removed entirely; the custom predicate uses the generic Predicate API. - -- Generic compression predicate API: https://docs.rs/tower-http/latest/tower_http/compression/trait.Predicate.html diff --git a/docs/RENEWAL.md b/docs/RENEWAL.md deleted file mode 100644 index ffad8ef..0000000 --- a/docs/RENEWAL.md +++ /dev/null @@ -1,165 +0,0 @@ -# Automatic certificate renewal - -## Scope and defaults - -RustProxy 0.7.0 renews **configured managed certificates** whose `issuer` metadata -identifies an existing HTTP-01, Cloudflare, OVH or RFC2136 Certbot profile. Issuance -and initial library import are still explicit, staging-first operations. Importing -a PEM pair alone does not create a managed lineage or authorize CA requests. - -For managed certificates, `issuer.auto_renew` defaults to `true`, including saved -0.6 metadata lacking that field. Explicit `false` opts out. Therefore upgrading -an already configured managed certificate can enable scheduled CA checks; review -this before starting the new binary. Missing profiles fail closed with an error; -there is no silent recreation, provider switch or production issuance. - -Certificates shows an independent enable/disable action, Check now and Dry run, -last check, next check, expiry and result. Expiry is populated after a worker check; -it is not a pre-existing inventory of every manually imported certificate. Renewal -is not a configurable new ACME account lifecycle or revocation feature. - -## Schedule and recovery - -Saved renewal observations are loaded from SQLite **before administration listeners are -exposed**, so last check, next check, expiry and result are available immediately after a -restart without pressing **Check now**. RustProxy then waits 5-10 minutes before the first -automatic CA check, avoiding a renewal burst during startup. Successful checks are scheduled -approximately every 12 hours with up to 30 minutes of additional jitter. Only one issue/renew -worker runs at once. Failures back off from one to six hours; a missing/invalid profile is -checked again after one hour. Observation rows contain non-secret status/timestamps only and -are stored only in `config.sqlite3`. RustProxy 0.15.0 no longer reads or imports historical JSON -observation sidecars at runtime. When SQLite has no observation row, the normal scheduler safely -reconstructs status on its next automatic check; this does not replace or regenerate certificate, -key, account or renewal-profile material. A manual dry run does not postpone a real due check. The -service must be running; this is not an independent timer that works while RustProxy is stopped. - -The helper runs `certbot renew --cert-name rustproxy` in the original isolated -lineage, reusing saved authenticator/account/credential settings. It does not use -`--force-renewal`, change the CA or run directory hooks. Do not add shell hooks -to the private Certbot renewal configuration: hooks already saved by an operator -are still part of Certbot's trusted configuration. Certbot decides when the -certificate needs renewal. A successful exit can mean nothing was due: actual -certificate and key bytes are compared with the currently installed generation. - -For changed material, the helper verifies validity times using OpenSSL and creates -private immutable, content-addressed staging files. Rust validates the certificate/ -key match and requested domain coverage, then validates a complete new snapshot -including current vhost assignments. Only a successful revisioned SQLite write -publishes that snapshot. New TCP and QUIC TLS handshakes use the new certificate; -existing connections are not restarted. A simultaneous manual replacement/removal -or automatic-renewal disable wins over an older automatic worker result. - -Worker failure, invalid PEM/SANs, expiry regression or configuration conflict leaves -the previously active generation intact. If Certbot succeeded but publication did -not, the next check compares files again and can retry the same staged generation. -Unchanged checks do not write a new configuration revision. Changed certificate publications -use the ordinary revisioned configuration path, but certificate inventory changes are not part -of cache compatibility and therefore do not discard otherwise compatible hot/cold objects. - -Private profile/file checks, single-worker and per-profile locks, bounded subprocess -timeouts and process-group cancellation constrain work. Original certificate files -and Certbot archives are retained, not automatically deleted. Immutable renewal -staging is limited to 1024 keys; initial issuance staging remains limited to 128. -Plan monitored disk space and controlled offline cleanup after verified backups; -there is no automatic certificate garbage collector or external expiry alert yet. - -## Provider-specific prerequisites - -HTTP-01 still requires public TCP port 80 to reach an active plaintext RustProxy -listener for every name. The reserved challenge handler exposes only bounded tokens -for the current worker's domains, before application auth/ACL/cache. It is not a -public file browser. Wildcard certificates use DNS-01, not HTTP-01. - -Cloudflare, OVH and RFC2136 need their original DNS permissions, credentials and -propagation settings. Secrets are stored in private files, not returned by the -renew endpoint or included in desired configuration. They are not encrypted at -rest. The helper, Certbot, plugins and OpenSSL must be usable by the service user. -Keep absolute profile paths stable; moving the Certbot directory requires explicit -migration of its stored paths. A system-wide timer should not also manage these -RustProxy-isolated profiles. - -For a certificate intentionally owned by a separate system Certbot instead of this managed -renewal subsystem, 0.28.2 can reference the Certbot lineage directly. Configure a manual -certificate id with both absolute paths, for example -`/etc/letsencrypt/live/example.com/fullchain.pem` and -`/etc/letsencrypt/live/example.com/privkey.pem`. RustProxy resolves the `live/` symlinks again on -every targeted reload, so a renewal wrapper only needs to call: - -```bash -curl --fail --show-error -X POST \ - -H 'Authorization: Bearer TOKEN' \ - -H 'X-RustProxy-Certificate-Key: certbot-main' \ - -H 'Content-Type: application/json' \ - --data '{}' \ - https://proxy-admin.example/api/certificates/shared-cert/reload -``` - -The service identity must be able to traverse the Certbot directories and read the files. RustProxy -0.28.2 accepts named POSIX ACL read access and does not require an exact `0600`/`0640` mode. -External certificate/key files must not be group/world-writable, and private keys must not be -world-readable. Parent directories must be root- or RustProxy-owned and not group/world-writable -(apart from root-owned sticky ancestors such as `/tmp`). The referenced files are not copied into -RustProxy backups. - -For a normal Certbot lineage, grant traversal without directory listing and read only the archive -files. A default ACL is useful, but a renewer may create a new private key with mode `0600` and -mask inherited ACL permissions. The reliable hook therefore reapplies the named read ACL to the -newly resolved generation before calling the reload API: - -```bash -setfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/ -setfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/ -setfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem \ - /etc/letsencrypt/archive/example.com/privkey*.pem -setfacl -m d:u:rustproxy:r-- /etc/letsencrypt/archive/example.com/ - -# Run these two resolved-file grants after renewal, before the API reload. -setfacl -m u:rustproxy:r-- \ - "$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)" \ - "$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)" -``` - -For external material below `/etc/ssl`, traversal of the protected root can be granted separately: - -```bash -setfacl -m u:rustproxy:--x /etc/ssl/ -``` - -If direct read access to the Certbot tree is undesirable, use the copy/deploy workflow instead: - -```bash -/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \ - --cert /etc/letsencrypt/live/example/fullchain.pem \ - --key /etc/letsencrypt/live/example/privkey.pem -``` - -That command resolves the Certbot `live/` symlinks locally, validates and stages a managed pair, -then publishes the existing id. Do not use deploy for an id configured with external absolute -paths, and do not point either external-manual workflow at a certificate carrying RustProxy -`issuer` metadata; one lineage must have one owner. - -Staging certificates are not trusted by browsers. A Dry run checks renewal -validation but does not deploy the staging result. Only an explicit production -issuance creates a production profile; renewal never upgrades staging implicitly. - -## API - -All routes below require the existing local administrator session and CSRF checks. - -- `GET /api/acme`: current job, provider names, nominal interval and renewal states. -- `POST /api/acme/renew`: `{"certificate_id":"site","dry_run":false}`; returns 202 - and a job. Only identity/options, no API credential resubmission. -- Certificate opt-out is a revisioned config update setting - `tls.certificates[].issuer.auto_renew` to `false`. - -`--dry-run` is **not** a claim of live CA validation in this release's test report: -packaging tests used a simulated Certbot. Run a real staging/production-lineage -validation on your server. Full runtime scheduler/publication acceptance is pending. - -## Primary references - -Certbot renewal behavior and flags: -https://eff-certbot.readthedocs.io/en/stable/using.html#renewing-certificates -https://eff-certbot.readthedocs.io/en/stable/man/certbot.html - -Challenge types: https://letsencrypt.org/docs/challenge-types/ diff --git a/docs/RESPONSE_DELIVERY.md b/docs/RESPONSE_DELIVERY.md new file mode 100644 index 0000000..0126857 --- /dev/null +++ b/docs/RESPONSE_DELIVERY.md @@ -0,0 +1,47 @@ +# Compression, transformations and response headers + +RustProxy can compress eligible responses and optionally transform supported static content before caching or delivery. These controls are **separate**: compression changes transfer encoding; transformation changes representation bytes. Always verify content and response headers with a real client after enabling either feature. + +## Response compression + +Open **Settings → Response / Compression** to configure the global codec ceiling. Vhosts and Web Services may further restrict enabled codecs; a local override cannot enable a codec forbidden globally. + +| Setting | Current default / behavior | +|---|---| +| `gzip` | Enabled globally | +| `br` (Brotli) | Enabled globally | +| `zstd` | Enabled globally; used only when the client accepts it | +| `deflate` | Disabled globally | +| `min_bytes` | 1,024 bytes by default, configurable from 32 to 1,048,576 | + +RustProxy negotiates encoding against the request's `Accept-Encoding` values and quality weights. An eligible reply with a preexisting `Content-Encoding` is **not compressed again**. Compression is skipped for range responses, HEAD/204/304, upgrades, event streams, `Cache-Control: no-transform` and other ineligible cases. Unknown-length streams are handled without buffering the whole body merely to compress it. + +The Web Server can additionally use precompressed `.br`/`.gz` file sidecars when eligible. Sidecar delivery is distinct from live response compression. Do not manually force a `Content-Encoding` header that does not match the actual body. + +## Static response transformations + +Under **Settings → Transform**, the engine is disabled by default. Even when enabled globally, **each Vhost must opt in**. The default globally enabled transform types, once the engine is turned on, are JavaScript and CSS. HTML, SVG and JSON transformations are individually optional. A Vhost may override the allowed types and extensions within global restrictions. + +The available resource controls include: + +- `max_body_bytes` — 2 MiB default per eligible response (1 KiB–32 MiB accepted). +- `memory_budget_bytes` — 128 MiB default process-wide budget (1 MiB–2 GiB accepted). +- `max_inflight` — 32 default concurrent transformation slots (1–1024 accepted). +- Presets in the panel can set suggested limits; the stored numeric settings determine actual limits. + +Transformation is a bounded operation for eligible, supported representations, **not a general rewrite engine**. Unsupported MIME types, larger or unknown-size bodies, non-transformable responses and resource-saturated periods are skipped or handled without changing the original body. Transforming JS/CSS/HTML can break applications that rely on specific syntax or content, so test before enabling site-wide. + +Transformations can run on eligible reverse-proxy and Web Server responses. When a transformed response is safely cacheable, the cache stores a representation compatible with its validators and later per-client encoding. Changing transform rules may invalidate affected cached content. See [Cache](CACHE.md). + +## Custom and security headers + +Configure global response headers, Vhost-specific policies and Web Server/location headers at the appropriate scope. Header rules can set, append or remove allowed names. Certain routing, forwarding, framing, cache diagnostics and security-sensitive fields are protected or subject to extra validation. Review especially HSTS, CSP, CORS and application-specific cookies before using a broad rule. + +For controlled rollout: + +1. Enable only the codec/types required, then test with and without `Accept-Encoding` and with ordinary browsers. +2. Compare response size, actual decoding, `Vary`, cache status and content hash for sensitive assets. +3. Check the **Transform** and **Cache** dashboards, response logs and browser console for errors. +4. Disable the particular Vhost or transform type if output changes unexpectedly; investigate before re-enabling. + +The Transform dashboard provides examined/transformed/skipped/failed counts, saved bytes and resource-limit observations. These are runtime diagnostics, not a guarantee that every asset was transformed or that end-user page loads improved. diff --git a/docs/ROADMAP_HISTORY.md b/docs/ROADMAP_HISTORY.md deleted file mode 100644 index 729dcd6..0000000 --- a/docs/ROADMAP_HISTORY.md +++ /dev/null @@ -1,714 +0,0 @@ -# Historical roadmap archive - -> Archived during the RustProxy 0.25 documentation cleanup. This file preserves completed historical roadmap entries. The active roadmap is [`../ROADMAP.md`](../ROADMAP.md). - -# RustProxy roadmap - -## 0.25.0 - -Current direction: 0.24.10 -> 0.25.0 - -- [x] Add independent reusable Web Service configuration with name, document root, index list and directory-listing switch. -- [x] Add Web Service CRUD through the dashboard/API and persist it through the existing revisioned configuration store. -- [x] Add `Reverse Proxy` / `Web Server` source selection to vhost fallback and custom locations without storing Web Server filesystem settings in vhosts. -- [x] Allow one Web Service to be referenced by multiple vhosts and one vhost to mix Web Server and Reverse Proxy locations. -- [x] Add `GET`, `HEAD`, MIME, `Content-Length`, streaming, index lookup, directory slash redirect and optional directory listing. -- [x] Add metadata-based `ETag`/`Last-Modified`, conditional `304`, single `Range`/`If-Range`, `206` and `416`. -- [x] Enforce decoded-path validation and document-root confinement; Unix traversal uses `openat` + `O_NOFOLLOW` and does not serve symlinks. -- [x] Keep Web Server inside the existing routing, ACL/auth, custom-header/error-page/security and H1/H2/H3 response pipeline; do not create separate routing/error/cache subsystems. -- [x] Add unit/source-contract coverage, OpenAPI/UI language coverage and Web Server documentation. -- [x] Clean 0.25 README/Docs/CHANGELOG release material so current user-facing changes are separated from historical audits. -- [x] Add an authenticated `/docs` Markdown Reader with a separate API/OpenAPI tab, canonical docs index, 0.25 specification audit and open-work TODO. - -## 0.24.10 - -Current direction: 0.24.9 -> 0.24.10 - -- [x] Make Error File a first-class revisioned configuration section and persist enabled/path/format across database reopen. -- [x] Remove the redundant Error File help sentence while retaining JSON/RAW controls and live apply. -- [x] Keep response-time history permanently expanded and add an accessible information popover matching Process. -- [x] Extend UI/API/source regressions and update release identity to 0.24.10. - -## 0.24.9 - -Current direction: 0.24.8 -> 0.24.9 - -- [x] Add the encrypted Secret Store and migrate ACME Cloudflare/OVH/RFC2136 credentials away from persistent plaintext files. -- [x] Add password-encrypted full backup/restore with manifest validation, integrity checks, staging and rollback-safe activation. -- [x] Add the optional bounded asynchronous ERROR file sink with JSON/RAW formats, shared secret redaction and external logrotate integration. -- [x] Extend offline source/release contracts and update release identity to 0.24.9. - -## 0.24.8 - -Current direction: 0.24.7 -> 0.24.8 - -- [x] Expand Cache RAM overview history with HOT usage/limit and transient total/fill/COLD queue/COLD I/O series. -- [x] Add COLD storage occupancy history without mixing disk bytes into process RAM. -- [x] Move the RSS/cache accounting caveat behind an accessible information control. -- [x] Extend offline UI/release contracts and update release identity to 0.24.8. -- [ ] Run compiler-enabled `./dev.sh check` / `./dev.sh test` and browser/live-cache acceptance on the release host. - -## 0.24.7 - -Current direction: 0.24.6 -> 0.24.7 - -- [x] Make the transform temporary-memory budget configurable and persisted with the normal SQLite-backed transform settings. -- [x] Add a process-wide maximum concurrent transformation limit with fail-open pressure behavior. -- [x] Add persisted RAM saver / Recommended / Performance / Uber performance resource presets while keeping transform types/extensions independent. -- [x] Add UI budget safety warnings and manual-edit -> Custom preset behavior. -- [x] Expose live transform RAM/concurrency, process/cache RAM accounting and COLD disk occupancy in the dashboard. -- [x] Clean externally reported cache/CSS compiler warnings without lint suppression. -- [x] Rotate cache compatibility identity for global transform representation/eligibility changes but not resource-only capacity metadata. -- [x] Extend OpenAPI, SQLite persistence, memory-contract and release/source regressions and update release identity to 0.24.7. - -## 0.24.6 - -Current direction: 0.24.5 -> 0.24.6 - -- [x] Persist selected HOT/COLD cache preset intent in the SQLite-backed configuration and restore it in Settings. -- [x] Add a Recommended COLD policy preset without overriding disk capacity/path choices. -- [x] Bound transform aggregation/decompression/concurrency memory and remove avoidable JSON/HTML/CSS allocation churn. -- [x] Bound stale revalidation, proactive refresh scheduling, COLD cleanup selection/concurrency and delete fallback tasks. -- [x] Audit the remaining long-lived/background workers for queue/task cardinality and cancellation ownership. -- [x] Add Rust-independent memory contract regression checks and update release identity to 0.24.6. - -## 0.24.5 - -Current direction: 0.24.4 -> 0.24.5 - -- [x] Resolve unknown-length, actually empty HTTP/3 bodies before HTTP/1.1 upstream framing. -- [x] Preserve streaming for non-empty H3 bodies and declared `Content-Length` requests. -- [x] Add source-level regression coverage for DELETE and other body-capable methods. -- [x] Bound HOT by allocation-aware admission, enforce `max_object_bytes` during fill, and add a separately bounded transient/COLD-queue memory model. -- [x] Preserve HOT -> COLD ownership semantics while charging demoted payloads until persistence drops them; expose logical/allocated/transient telemetry. -- [x] Make global cache disable authoritative over vhost overrides and remove the artificial 1 GiB HOT/object ceiling. -- [x] Add host/cgroup-aware conservative/recommended/performance cache presets and non-blocking memory/headroom warnings in Settings. -- [x] Update source/package/UI/OpenAPI identity and offline release checks to 0.24.5. - -## 0.24.4 - -Current direction: 0.24.3 -> 0.24.4 - -- [x] Make the Linux THP mitigation process-local with `PR_SET_THP_DISABLE` before Tokio worker creation. -- [x] Preserve the existing glibc arena limit and allocator-trim behavior. -- [x] Add source/release regression coverage for THP opt-out ordering and release identity. - -## 0.24.3 - -Current direction: 0.24.2 -> 0.24.3 - -- [x] Reduce Linux/glibc arena retention by adding `MALLOC_ARENA_MAX=2` to managed systemd units. -- [x] Remove redundant identity-body and JavaScript/CSS transformation allocations; release successful-transform temporaries earlier and trim large glibc transform churn at a bounded cadence. -- [x] Move HTTP/3 enablement to listener editing while keeping HTTP/3 tuning under HTTP connections. -- [x] Rename Proxy Process to Process and hide unsupported platform diagnostics dynamically. -- [x] Remove the stale unreferenced per-BSD parser source from the release archive. -- [x] Update source/package/UI/OpenAPI identity and Rust-independent release gates to 0.24.3. - -## 0.24.2 - -Current direction: 0.24.1 -> 0.24.2 - -- [x] Bump package/source/UI/OpenAPI release identity to 0.24.2 without functional changes. - -## 0.24.1 - -Current direction: 0.24.0 -> 0.24.1 - -- [x] Replace the duplicated BSD statistics implementation with shared cross-platform library APIs. -- [x] Preserve Linux-specific `/proc`/cgroup diagnostics as additive compatibility fields. -- [x] Populate additional portable system/process metrics exposed by the selected libraries where available. -- [x] Expose low-risk runtime metadata through the authenticated status API, including the resolved data/database path. -- [x] Make CLI installed-data discovery use Linux and BSD/OPNsense conventions without hardcoding the path in the browser. -- [x] Move Logging into Settings and retain legacy `/logging` navigation compatibility. -- [x] Fix the release-host compiler error in portable uptime sampling and remove the stale H2 import warning. -- [x] Remove artificial hostname-count ceilings from certificate bindings/issuance and hostname-list configuration while retaining syntax, payload and resource-safety bounds. -- [x] Update source/package/UI/OpenAPI identity and source-side regression contracts to 0.24.1. -- [ ] Generate and commit a resolver-produced `Cargo.lock`, then run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on the release host. - -The 0.24.1-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.24.0 - -Current direction: 0.23.3 -> 0.24.0 - -- [x] Graceful H2 idle retirement with activity/stream recheck, GOAWAY and bounded drain. -- [x] Independent H2 maximum connection lifetime and drain timeout settings. -- [x] Settings API/UI/OpenAPI contracts for all three H2 lifecycle limits. -- [x] Extract system statistics into platform backends without changing Linux metric semantics. -- [x] Add FreeBSD/OpenBSD/NetBSD native best-effort sampling and explicit unsupported/null metrics. -- [x] Surface platform in Proxy Process and Prometheus metadata. -- [x] Update source/package/UI identity and source-side regression contracts to 0.24.0. - -The 0.24.0-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.23.3 - -Current direction: 0.23.2 -> 0.23.3 - -- [x] Add opt-in per-vhost legacy WebSocket compatibility without changing ordinary HTTP behavior. -- [x] Force legacy-mode upstream WebSocket handshakes to HTTP/1.1 and force legacy WebSocket `Host` to the configured backend authority and align `Origin` with it. -- [x] Tolerate old `101` response framing while keeping the WebSocket accept proof and subprotocol validation strict. -- [x] Add dashboard/OpenAPI/language-pack controls and source regressions. -- [x] Update package/source identity and Rust-independent release gates to 0.23.3. - -The 0.23.3-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.23.2 - -Current direction: 0.23.1 -> 0.23.2 - -- [x] Let Cache and Transformation live charts retain 60, 120, 300 or 600 samples without changing the 3-second polling cadence. -- [x] Persist each chart history-window choice locally in the browser and keep history memory bounded. -- [x] Fix local CLI administration transport detection so HTTPS administration no longer traps `--livelogs` in a 308 reconnect loop. -- [x] Pin local CLI HTTPS to certificates from the private RustProxy certificate directory and reuse the transport for config/TLS CLI calls. -- [x] Update package/source identity and Rust-independent release gates to 0.23.2. - -The 0.23.2-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.23.1 - -Current direction: 0.23.0 -> 0.23.1 - -- [x] Add default-collapsed, full-width live activity charts to the Cache and Transform workspaces. -- [x] Plot selected-scope Cache HIT/MISS/BYPASS rates from runtime counters without adding proxy-path work. -- [x] Plot selected-scope Transform applied/cache-HIT/skipped/error rates from the existing runtime metrics endpoint. -- [x] Reuse the dashboard SVG/chart styling, live 3-second refresh and language-pack model. -- [x] Update package/source identity and Rust-independent release gates to 0.23.1. - -The 0.23.1-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.23.0 - -Current direction: 0.22.3 -> 0.23.0 - -- [x] Add scoped external certificate API authentication with Key ID + Bearer Token, hash-only storage, rotation/revoke/status and last-used metadata. -- [x] Restrict the external credential to certificate deploy/reload and require HTTPS outside loopback. -- [x] Add Settings → Certificate API UX and preserve direct production binary commands for local deploy/reload. -- [x] Add `rust-proxy --livelogs` with a bounded live stream, interactive structured filters, detailed request inspection and explicit lag reporting. -- [x] Split Settings → Cache into General, Resilience, Cold cache and Static & diagnostics sub-tabs without changing the atomic cache settings document. - -The 0.23.0-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.22.3 - -Current direction: 0.22.2 -> 0.22.3 - -- [x] Add HTTP/1.1 `Upgrade: h2c` on opt-in cleartext H2 listeners while preserving prior-knowledge h2c. -- [x] Validate Upgrade/HTTP2-Settings and reject malformed or body-bearing bootstrap requests before `101`. -- [x] Reconstruct the initiating H1 request as HTTP/2 stream 1 and retain the normal Hyper H2 path for later streams. -- [x] Add dedicated certificate hot reload without mutating desired configuration or revision. -- [x] Add external certificate deployment for Certbot/manual issuers with immutable generation staging and atomic multi-vhost switch by certificate id. -- [x] Add API/CLI/panel support plus audit/OpenAPI/documentation for certificate reload/deploy. - -The 0.22.3-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.22.2 - -Current direction: 0.22.1 -> 0.22.2 - -- [x] Add replay-safe failover retry with bounded policy and observability. -- [x] Correct explicit HTTP/2 upstream request versioning and retain h2c prior-knowledge regression coverage. -- [x] Add per-backend maximum concurrent proxy exchanges with bounded queue timeout / 503 behavior and metrics. -- [x] Add detailed upstream failure classification plus last failure and health ejection/recovery timestamps. -- [x] Consolidate backend operations data in the existing dashboard table: health, active/limit, requests/errors, latency, traffic, last failure and last recovery. -- [x] Parse and expose public-certificate issuer/SAN/validity/remaining-days/expiry status plus renewal status/last attempt/error. - -The 0.22.2-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.22.1 - -Current direction: 0.22.0 -> 0.22.1 - -- [x] Move selective PURGE/BAN and active BAN management into dedicated Cache modals with inline operation feedback. -- [x] Add per-vhost RFC 8441 HTTP/2 WebSocket Extended CONNECT beside RFC 9220. -- [x] Bridge H2 RFC 8441 WebSockets to H1 upstreams and preserve Extended CONNECT to explicit H2 upstreams. -- [x] Add opt-in cleartext HTTP/2 prior-knowledge (`h2c`) listeners. -- [x] Add stable active-tunnel inventory, per-tunnel kill, operator reason and audit logging. -- [x] Split WebSocket tunnel statistics by H1 / H2 RFC 8441 / H3 RFC 9220 on the main dashboard and Prometheus. -- [x] Preserve the 0.22.0 cache feature set and `Accept-Ranges` policy. -- [x] Update 0.22.1 source/package identity and Rust-independent release gates. - -The 0.22.1-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.21.0 - -Current direction: 0.20.0 -> 0.21.0 - -- [x] Shard Cold Cache storage and add bounded asynchronous GC plus selective cleanup. -- [x] Add RFC 5861 stale windows, request `max-stale`, correct request `max-age=N`, and correct revalidation-directive semantics. -- [x] Add the extensible decode -> transform -> canonical cache -> encode stage with transformed lengths/validators, strict `no-transform`, HEAD/fill safety and canonical `Vary: Accept-Encoding` handling. -- [x] Add global, per-vhost and Docker-discovery transformation controls and administration UI/API coverage. -- [x] Make Passive Backend Health explicit opt-in for ordinary and Docker vhosts while preserving independent active health checks and unconditional observability. -- [x] Keep route cache override tri-state and expose effective route/cache/transform diagnostics. -- [x] Close 0.21.0 with the current conservative JS/CSS transformers; defer parser-backed replacements to a later development cycle. -- [x] Finish release polish: backend timing header rename, human-readable uptime, transformed Live traffic series, Docker Passive Backend Health layout fix, warning cleanup and cache module split. -- [x] Keep package/source identity and OpenAPI at 0.21.0 and package the completed feature scope as the 0.21.0 release source. - -The 0.21.0-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md). - -## 0.20.0 - -Current direction: 0.19.4 -> 0.20.0 - -- [x] Split Certificates into Installed / Renewal / Generate tabs. -- [x] Move generation/import into the tabbed workspace and keep existing certificate actions compatible. -- [x] Improve ACME/renewal status readability and display exact Certbot log locations on failures. -- [x] Update package/source identity, OpenAPI and release checks to 0.20.0. - -## 0.19.4 - -Current direction: 0.19.3 -> 0.19.4 - -- [x] Normalize Docker-vhost Aliases field geometry with neighboring inputs and remove the extra inherited label spacing. -- [x] Retain `0.0 KiB/s` idle and `<0.1 KiB/s` positive low-rate backend traffic formatting with release regression coverage. -- [x] Add Cache domain-table filtering by resolved cache-enabled state without changing aggregate cache statistics. -- [x] Update package/source identity, OpenAPI and release checks to 0.19.4. - -## 0.19.3 - -Current direction: 0.19.2 -> 0.19.3 - -- [x] Preserve the exact panel and Settings tab through session expiry/re-login without shell flicker. -- [x] Canonicalize `/configuration` into persistent Settings tab routing. -- [x] Add shared save-button busy state and field-focused validation feedback. -- [x] Preserve certificate import file selections after failed saves. -- [x] Add per-Settings-tab unsaved-change indicators, including Manual config. -- [x] Add persisted dark/light administrator theme selection and consolidate theme-sensitive CSS tokens. -- [x] Keep Live Logs column selection compact with standard checkboxes and no internal scrollbars. -- [x] Add a fail-soft embedded-config release checker with hourly default cadence and CLI/environment overrides. -- [x] Keep the release checker compiler-compatible by mapping boxed HTTP body collection failures explicitly into `anyhow::Error`. -- [x] Reduce the footer to API latency/dropped logs and fix the last broad Live Logs checkbox sizing override. -- [x] Update package/source identity, OpenAPI and release checks to 0.19.3. - -## 0.19.2 - -Current direction: 0.19.1 -> 0.19.2 - -- [x] Restore administration-backend local-listener risk detection for startup-locked listeners and loopback aliases; make vhost discard restore the persisted draft baseline. -- [x] Use descriptive status names for default responses. -- [x] Add an on-demand 4096-entry live log buffer that lasts until the log window closes. -- [x] Align inline checkboxes and reduce Docker-vhost alias editor height. -- [x] Render idle backend traffic as `0.0 KiB/s` and positive sub-0.1 KiB/s traffic as `<0.1 KiB/s`. -- [x] Remove cache diagnostics/validators from locally generated placeholder/default pages and enforce `no-store`. -- [x] Allow safe vhost-ID rename with collision protection and operator warning. -- [x] Update package/source identity, OpenAPI and release checks to 0.19.2. - -## 0.19.1 - -Current direction: 0.19.0 -> 0.19.1 - -- [x] Repair Default responses section serialization/server routing. -- [x] Isolate cache objects by frontend scheme. -- [x] Keep a 256-entry live log window with 4096 retained entries available on demand. -- [x] Polish backend traffic units, checkbox/column UI and Docker-vhost Advanced layout/alias spacing. -- [x] Remove stale screenshots and index historical/version-labelled documentation. -- [x] Restore source-package test files/fixtures required by the release gates. -- [x] Update package/source identity, OpenAPI and release checks to 0.19.1. - -## 0.19.0 - -Current direction: 0.18.8 -> 0.19.0 - -- [x] Persist per-admin Live Logs column visibility in SQLite without affecting JSON export or proxy configuration revisions. -- [x] Remove the duplicate top-right version badge. -- [x] Carry the legacy X.509 `tls_verify=false` compatibility fix and expanded upstream diagnostics into the release. -- [x] Add schema-v4 migration, administration API coverage and update package/source identity to 0.19.0. - -## 0.18.8 - -Current direction: 0.18.7 -> 0.18.8 - -- [x] Use hostname-style chips for Docker-vhost aliases. -- [x] Add per-app HTTP-to-HTTPS redirect, WebSocket and cache switches for Docker vhosts. -- [x] Add an agent-level default ACL-profile selector without removing per-app ACL overrides/inline rules. -- [x] Remove the compression-codec explainer from Overview. -- [x] Update package/source identity, API schema and release checks to 0.18.8. - -## 0.18.7 - -Current direction: 0.18.6 -> 0.18.7 - -- [x] Keep Response time history collapsed by default without stopping its live samples. -- [x] Make per-interface RX/TX charts visually consistent with the aggregate network history. -- [x] Fix expanded Docker-vhost route/ACL field alignment from the 0.18.6 UI. -- [x] Update package/source identity and release checks to 0.18.7. - -## 0.18.6 - -Current direction: 0.18.5 -> 0.18.6 - -- [x] Add network-throughput diagnostics split by non-loopback host interface. -- [x] Re-audit compression counters and add q-weighted negotiation coverage for all four supported codecs. -- [x] Polish Docker-agent settings alignment and spacing around listener selection / Add agent. -- [x] Update package/source identity and release checks to 0.18.6. - -## 0.18.5 - -Current direction: 0.18.4 -> 0.18.5 - -- [x] Make compression counters track the codec actually applied by RustProxy and add independent per-codec regression coverage. -- [x] Split the Overview request-rate chart into total, HTTP/1.1, HTTP/2 and HTTP/3 series. -- [x] Add a proxied-app launcher to Docker vhosts and show their domains beside generated backend-pool IDs. -- [x] Remove the global Open proxy shortcut. -- [x] Make Compact density clearly tighter than Dense. -- [x] Give proxy timeout controls explicit safe defaults instead of blank inherited values. -- [x] Replace Docker-agent listener CSV entry with the named-listener picker and tighten the agent status-row UX. -- [x] Update package/source identity and release checks to 0.18.5. - -## 0.18.4 - -Current direction: 0.18.3 -> 0.18.4 - -- [x] Make TCP the default backend health-check mode without rewriting existing explicit HTTP/off configuration. -- [x] Make Docker vhosts summary-first so large container inventories do not render every routing/ACL/timeout/health control at once. -- [x] Simplify Settings -> Security with progressive disclosure for credential and host-policy editing. -- [x] Move Administration audit to a dedicated on-demand modal. -- [x] Update package/source identity and release checks to 0.18.4. - -## 0.18.3 - -Current direction: 0.18.2 -> 0.18.3 - -- [x] Export Docker vhosts/backends under dedicated Prometheus metric families rather than ordinary vhost/backend families. -- [x] Add a separate live Docker-vhosts metric-group switch in Prometheus settings. -- [x] Cover Docker vhost traffic/duration and backend health/latency/probe/runtime observations with bounded labels. -- [x] Add regression checks for ordinary-vs-Docker metric isolation. -- [x] Add configurable separate access-log files per vhost and preserve bounded asynchronous sink behavior. -- [x] Lock startup-owned HTTP/HTTPS/admin binds while keeping non-startup listeners editable. -- [x] Polish compact UI, 404 response statuses, redirect summaries, global TLS cipher visibility and managed-certificate lifecycle UX. -- [x] Default WebSocket proxying to HTTP/1.1 Upgrade and make RFC 9220 HTTP/3 Extended CONNECT an explicit experimental vhost opt-in. - -## 0.18.2 - -Current direction: 0.18.1 -> 0.18.2 - -- [x] Serve the standalone Docker agent over HTTPS by default using a persistent self-signed ECDSA certificate. -- [x] Add SHA-256 certificate pinning for self-signed Docker agents while retaining normal public-CA validation when no pin is configured. -- [x] Add per-service Docker backend health-check settings backed by the normal bounded backend health machinery. -- [x] Show Docker backend health/runtime diagnostics and agent TLS/observation state in the Docker vhosts UX. - -## 0.18.1 - -Current direction: 0.18.0 -> 0.18.1 - -- [x] Add a standalone, token-authenticated Docker discovery agent that reports only running containers with non-loopback published TCP ports. -- [x] Add multi-agent endpoint/token/base-domain/listener/default-IP settings and keep observed Docker inventory separate from desired persisted assignments. -- [x] Detect multiple usable Docker-host addresses and preserve every public Docker bind for each discovered service. -- [x] Add a Docker vhosts panel with short names, arbitrary FQDNs, aliases and per-service backend-IP selection; reconcile assignments into dynamic ephemeral vhosts without revision writes. -- [x] Move the cache Live toggle to the right edge of the cache action toolbar. -- [x] Remove all dedicated `X-Rust-Proxy-Revision` handling; it is no longer generated, stripped, protected or migrated. -- [x] Replace production password recovery help with the installed `rust-proxy admin reset-password` command. - -## 0.17.0 - -Current direction: 0.16.1 -> 0.17.0 - -- [x] Add Debian/systemd/logrotate packaging and local APT-repository generation. -- [x] Export all currently implemented bounded process/OS diagnostics and extend safe Prometheus coverage for backend, GeoIP, certificate renewal, stream and logging state. -- [x] Add explicit trusted-CIDR Prometheus scraping without a token and preserve actual-peer-only ACL evaluation. -- [x] Ship a Grafana operations dashboard. -- [x] Add dedicated certificate/listener-aware local-response / 404 hosts and a dashboard editor. -- [x] Keep the built-in welcome placeholder `no-store` and strip optional response metadata. -- [x] Match Redirect source-domain editing UX to Vhost hostname chips. - -## 0.16.1 - -Current direction: 0.16.0 -> 0.16.1 - -- [x] Keep revision CAS as an internal concurrency mechanism but stop showing revision numbers in successful configuration-save feedback. -- [x] Preserve all 0.16.0 proxy/cache/H3/admin/UX behavior and bump release identity to 0.16.1. - -## 0.16.0 - -- [x] Keep the main vhost backend as an internal fallback separate from Custom locations, with legacy synthetic-root normalization. -- [x] Preserve the reserved administration session cookie only for an explicitly allowed, TLS-protected self-admin reverse-proxy route; ordinary upstreams remain isolated. -- [x] Complete backend WebSocket takeover before HTTP/3 Extended CONNECT success and add phase/error/reset/upstream diagnostics to access logs. -- [x] Split process-memory and cache-memory reporting into RSS categories plus hot/transient fill/cold-queue/cold-I/O gauges while retaining the existing allocator-release paths. - -## 0.15.1 - -Current direction: 0.15.0 -> 0.15.1 - -- [x] Repair EN/PL Custom locations/help translations and reject misplaced language-pack keys in release checks. -- [x] Rework visual header-rule editing and add a live effective-behavior preview to each location. -- [x] Expand in-panel location documentation with structured sections and common examples. -- [x] Add combined HIT/MISS/BYPASS activity and Proxy process CPU/RSS charts. - -## 0.15.0 - -Current direction: 0.14.0 -> 0.15.0 - -- [x] Add Nginx-style Custom location matching, rewrite and local-response controls with inline documentation and a visual header-rule builder. -- [x] Add reusable Nagios/Icinga API checks for control-plane latency, backend availability/latency and dropped-work counters. -- [x] Move per-vhost timing/Cloudflare diagnostics to the main frontend configuration and add source IP to live logs. -- [x] Add dashboard history charts for RAM cache, network throughput and response latency plus compact API/footer diagnostics. -- [x] Add separate idle-age and total-hot-residency cold-cache policies. -- [x] Remove the runtime renewal-observation JSON importer and quarantine obsolete state only during a stopped source upgrade. -- [x] Accept repeated literal path separators by normalizing them for routing/ACL decisions while preserving the original backend target. - -## 0.14.0 - -Current direction: 0.13.2 -> 0.14.0 - -- [x] Accept explicitly allowlisted browser-facing administration DNS authorities behind a trusted reverse proxy without requiring the internal admin port. -- [x] Keep direct IP/localhost administration access on the configured admin port and derive same-origin/cookie security from trusted external proxy metadata. -- [x] Implement RFC 9220 WebSocket Extended CONNECT over HTTP/3 without a side-channel transport or HTTP/1.1 bootstrap fields on the client-facing H3 request. -- [x] Move the H3 stack to `hpx-h3 2.5.19` with integrated Quinn transport and advertise Extended CONNECT support. -- [x] Rename the requested dashboard sections/actions in EN/PL and update release contracts/documentation. - -## 0.13.2 - -Current direction: 0.13.1 -> 0.13.2 - -- [x] Honor a valid administrator session on direct panel refresh and revalidate stale browser-history login documents. -- [x] Force hot-cache handoff to enabled cold storage during managed updates and provide a longer controlled-stop budget. -- [x] Store automatic-renewal observations in SQLite schema v3 and restore them before the administration listener starts, with one-time legacy JSON import. -- [x] Move the raw configuration document editor under **Settings -> Manual config** while preserving `/configuration` as a compatibility alias. -- [x] Update documentation, OpenAPI, language help, offline regressions and release identity. - -## 0.13.1 - -Current direction: 0.13.0 -> 0.13.1 - -- [x] Extend administrator browser sessions to a configurable absolute 7–30 day lifetime, default 30 days. -- [x] Keep password change, logout and revoke-all as immediate session invalidation boundaries. -- [x] Allow deliberate local-listener backends, including port 9090, only behind explicit per-backend risk acceptance while retaining private/special-address policy. -- [x] Add panel confirmation, OpenAPI fields, EN/PL strings, documentation and regressions. - -## 0.13.0 - -Current direction: 0.12.1 -> 0.13.0 - -- [x] Add first-class redirect hosts and dashboard navigation. -- [x] Support scheme/path matching, URL/host/path targets, 301/302/303/307/308, path/query and redirect-cache controls. -- [x] Share named listeners and certificate inventory with redirects while rejecting conflicting frontend ownership. -- [x] Default backend HTTPS `tls_verify` to false when omitted; keep explicit opt-in verification. -- [x] Compact certificate inventory cards, render DNS SANs as vhost-style badges and inspect legacy/manual public PEM files when stored coverage metadata is absent. -- [x] Update OpenAPI, docs, translations, regressions and release identity. - -## 0.12.1 - -Current direction: 0.12.0 -> 0.12.1 - -- [x] Split ordinary configuration writes into revisioned section endpoints and retain atomic full-document publication for intentional multi-section changes. -- [x] Stop unrelated control-plane saves from rotating cache compatibility identity. -- [x] Preserve dashboard subpage routing across browser refresh/back-forward navigation. -- [x] Add independently selectable cold-cache idle/LRU/shutdown persistence policies. -- [x] Flush hot cache objects to cold storage during graceful shutdown and release RAM after bulk demotion where the allocator supports it. -- [x] Expose certificate DNS SubjectAltName coverage separately from configured SNI bindings. -- [x] Improve HTTPS backend TLS validation guidance while preserving secure-by-default omitted configuration and the explicit debug preset. -- [x] Update OpenAPI, regression checks and release identity to 0.12.1. -- [ ] Compile and execute the Rust test suite on a Rust 1.88+ staging host. - -## 0.12.0 - -Current direction: 0.11.5 -> 0.12.0 - -- [x] Move Layer-4 TCP/UDP stream management out of Settings into a dedicated main-navigation page directly below Vhosts. -- [x] Give Streams the vhost-style searchable card + modal workflow with explicit active-versus-desired restart state. -- [x] Add fixed-cardinality per-stream connection/session, reject, error, byte and UDP packet telemetry without database work on the data path. -- [x] Add browser-session RX/TX rate charts derived from monotonic backend stream counters. -- [x] Restyle named listener bind addresses with the same chip interaction used for vhost hostnames. -- [x] Extend offline source/release regression gates for Streams navigation, authenticated assets, UI contracts and L4 metrics source invariants. - -- [x] Stop successful unsafe methods from globally purging otherwise unrelated cached GET objects. -- [x] Use Host + URL as the logical primary cache identity and response `Vary` for representation variants instead of hashing unrelated request headers/backend members. -- [x] Share cache objects across backend-pool members and GET/HEAD requests. -- [x] Preserve resident age through 304 revalidation and hot/cold movement; persist the additional cold metadata in format 2. -- [x] Add variant-schema safety and cache regression coverage for the new behavior. -- [x] Make Force SSL require a real HTTP ingress and choose port 443 deterministically when several TLS ports are selected. -- [x] Add a graphical ACL-profile rule editor and resilient bulk-rule import with exact line errors. -- [x] Hide RustProxy cache diagnostics when a vhost explicitly disables cache. -- [x] Distinguish disabled vhost pools from unhealthy backends in the dashboard. -- [x] Add per-vhost client-IP forwarding control without weakening RustProxy ACL/GeoIP/rate-limit/Tinyauth identity. -- [x] Add a compact vhost search/filter for large installations. -- [x] Keep the checked-in Cargo lockfile stable and correct the Quinn manifest version to the resolved release. -- [x] Make the standard JSONL log directory writable under the hardened systemd service. -- [x] Accept HTTP/2/H3 `:authority` on the HTTPS administration panel without requiring a duplicate legacy Host header. -- [x] Reject ambiguous requests where legacy Host conflicts with protocol-native authority, while preserving strict HTTP/1.x origin-server semantics. -- [x] Add administration H2 authority and `/healthz` regression tests. -- [x] Add per-vhost Force SSL with an HTTP 308 redirect to the configured TLS frontend while preserving path/query. -- [x] Preserve NPM `ssl_forced` in the SQLite migrator as RustProxy Force SSL with usable HTTP+HTTPS listener bindings. -- [x] Fix managed update/rollback health checks for persisted administration HTTPS and changed admin bind/Host policy. -- [x] Add a dry-run-first SQLite migration tool for Nginx Proxy Manager proxy-host names and backends, with generic RustProxy TLS certificate fallback and manual-review warnings for unsupported NPM features. -- [x] Add offline migration-planner and HTTPS health-target regression coverage. -- [x] Add reusable ACL Profiles with country allow/block and IP/CIDR policy, attachable per vhost while retaining cumulative inline vhost and route ACL controls. -- [x] Add chip-style hostname editing and leading `*.` wildcard domains with exact-host and longest-wildcard precedence. -- [x] Add compact browser-session request-rate charts to vhost cards with progressive lifetime compaction. -- [x] Add the built-in HTTP 401 error page and treat benign post-upgrade WebSocket closes as normal tunnel completion. -- [x] Redirect plaintext requests arriving on the HTTPS administration socket and accept valid DNS Host values when no explicit admin hostname allowlist is configured. -- [x] Allow one named frontend listener policy to bind up to 16 explicit local addresses while forbidding wildcard binds in multi-address mode. -- [x] Align the Logging format builder with the Vhost configuration modal and reposition its field-configuration action. -- [x] Make source ZIP generation deterministic and verify reproducibility in release smoke tests. -- [x] Add bounded hot RAM and optional persistent cold disk cache tiers with asynchronous demotion/promotion, restart recovery, integrity metadata and separate hot/cold statistics. -- [x] Add cold-tier settings for path, disk/object budgets and idle demotion time while preserving global hot RAM limits. -- [x] Add structured-log format presets plus a visual field selector/renamer with JSON preview and advanced raw mapping fallback. -- [x] Normalize Logging sink cards to the shared application card styling instead of release-specific fieldset CSS. -- [x] Add baseline multi-stage Docker/OCI packaging with an unprivileged runtime user and persistent data volume. -- [x] Document browser hard-reload cache revalidation semantics and expose the exact request revalidation reasons in the cache inspector. -- [x] Redesign vhost cards around listener/protocol/certificate/cache identity plus live pool health, requests, active exchanges, errors, latency and traffic rate. -- [x] Add bounded structured JSON access-log sinks for JSONL files, Logstash TCP and Loki HTTP(S), with configurable field mapping/static fields and sink counters. -- [x] Expand cache object inspection with body bytes, resident bytes, overhead and aggregate stored-object memory counters. -- [x] Add optional `X-Cache-Resident-Age` and expose resident age for stored HITs. -- [x] Add administration HTTPS using the application-managed default certificate or a selected certificate, with restart-aware persisted bind address and hostname allow list. -- [x] Permit non-loopback administration binding when HTTPS is enabled; keep plain remote HTTP behind the legacy explicit risk gate. -- [x] Add global TLS protocol/cipher policy plus per-vhost TLS 1.2/1.3 and cipher overrides for TCP TLS. HTTP/3 vhosts inherit the global TLS 1.3 policy. -- [x] Make Settings tabs fill the available width and add dedicated Administration/TLS policy pages plus a separate Logging navigation page. -- [x] Enable shared cache by default while keeping explicit vhost/route disable controls. -- [x] Implement fresh conditional HIT for `If-None-Match` / `If-Modified-Since`. -- [x] Implement backend validator revalidation and cached-body reuse on backend 304. -- [x] Treat request/response `no-cache` as validation semantics rather than no-store; preserve `no-store` as hard exclusion. -- [x] Retain stale validators for bounded revalidation without stale serving. -- [x] Raise maximum object configuration to 1 GiB and remove eager object-sized fill allocation. -- [x] Add diagnostics, real-binary smoke scenarios, unit regressions and 0.8.1 release documentation. -- [x] Per-vhost H2/H3 frontend opt-outs next to named-listener selection, capability-gated by Settings listeners. -- [x] Global static-cookie cache defaults for common assets, visible global/per-vhost controls and response-side privacy guards. -- [x] Listener editor uses page scrolling without a nested scrollbar. -- [x] Per-vhost fixed-window request rate limiting with burst allowance. -- [x] Per-country rate overrides and block/allow-only country access rules backed by verified install-time GeoIP data. -- [x] Cloudflare client IP mode gated by direct-peer Cloudflare CIDRs. -- [x] Per-vhost H2/H3 frontend opt-outs and Server-Timing diagnostics. -- [x] Named listener selection in vhosts; listening IP/ports managed in Settings. -- [x] Health-check `off` disables active and passive classification. -- [x] HTTPS-by-IP backend debugging defaults and warnings. -- [x] Global static-cookie cache defaults plus per-vhost inheritance/overrides with response privacy guards. -- [x] Corrected cache Age calculation. -- [x] Updated deployment regressions, docs and 0.8.0 release identity. -- [x] Release CLI can generate the one-time setup token before first server start. -- [x] Explicit two-flag risk gate for wildcard remote administration binding. -- [x] Numeric Host validation for the wildcard panel; DNS hostnames remain rejected. -- [x] Linux/systemd install defaults enable 80/443; explicit `--no-https` opt-out. -- [x] Updated deployment regressions, docs and 0.7.3 release identity. -- [ ] Build/check/test the Rust binary and exercise remote-panel/systemd behavior live. -- [x] Explicit release identity, without overwriting the 0.7.1 artifact. -- [x] Swagger asset default independent of environment presence/Cargo config lookup. -- [x] Embedded assets prepared by our build, without an adapter's old-version fallback. -- [x] Offline local ZIP, bounded member validation, optional SHA-256 and safe caching. -- [x] Executable build-helper regressions and updated build/migration instructions. -- [ ] Build/check/test real Rust and render the actual upstream Swagger distribution. -- [x] Per-location visual proxy-request and client-response header rules. -- [x] Minimal anonymous login document and filtered extensible language bundle. -- [x] Exact default-deny admin route/method policy including private assets and docs. -- [x] Protected OpenAPI/Swagger, cookie session and CSRF-confirmed mutations. -- [x] Generic public discovery/errors and cross-tab UI revocation cleanup. -- [x] Source audit, access probe and regression tests with explicit execution limits. -- [ ] Compile and run router/proxy tests, inspect real Swagger rendering, and run the - anonymous probe against the built release before production acceptance. -- [x] Scheduled renewal for HTTP-01, Cloudflare, OVH and RFC2136 managed profiles. -- [x] Validated revisioned hot certificate publication with unchanged/failure safety. -- [x] Per-certificate opt-out, manual check, dry run, next/last/expiry observations. -- [x] Separate install/update scripts; isolated service, full data backup and rollback. -- [x] Vhost Basic Auth with bounded Argon2id checks and optional credential forwarding. -- [x] Per-vhost WebSocket toggle and graphical cumulative CIDR ACL editing. -- [x] Visual prefix locations with pool exclusion for dedicated origins. -- [x] Neutral welcome page, opt-in protocol fixture, shared square modal/Settings style. -- [x] Independent proxy and local administrator control plane; no Docker dependency. -- [x] SQLite documents, revision CAS, immutable snapshots, audit and private backups. -- [x] Accounts, bounded sessions, setup capability, origin/CSRF protections. -- [x] Host/path routes, aliases, explicit private-origin permission and CIDR ACLs. -- [x] Streaming bodies and full-exchange limits; bounded logs, SSE and Prometheus. -- [x] Public TLS/SNI, verified HTTPS upstreams, guarded DNS, HTTP/2 and WebSocket source. -- [x] Whole-source upgrade, legacy auth-module repair and rollback on copy failure. -- [x] Persist public HTTP/HTTPS bind addresses and ports, including 80/443 presets. -- [x] Distinguish active listeners, desired listeners and startup defaults; show restart pending. -- [x] Allow remote origins on ordinary 80/443 without allowing local listener loops. -- [x] RAM cache with TTL/Age, entry/object/byte limits, LRU eviction and route bypass. -- [x] Bounded streaming fills; do not publish partial, failed, oversized or trailer bodies. -- [x] Privacy exclusions, conservative Vary identity and ACL-before-cache ordering. -- [x] Optional X-Cache, X-Cache-Hits and Cache-Status; counters and authenticated purge. -- [x] Invalidate old fill generations on purge, config publication and successful unsafe methods. -- [x] Streaming gzip, Brotli, Zstandard and deflate negotiation with privacy/no-transform safeguards. -- [x] Preserve/hide/custom Server policy; client-response and upstream-request header editors. -- [x] Denser responsive dashboard; original logo retained. -- [x] OS/process CPU, RAM/RSS, load, descriptors, threads, network, cgroup and cache diagnostics. -- [x] Compile-time language discovery from langs/*.json; metadata, fallback and RTL. -- [x] Extra language files survive managed source updates. -- [x] Log filtering by host/path/cache outcome and bounded JSON export. -- [x] Unit and real-binary integration test source; 49 simulated-API frontend checks. -- [x] Example unprivileged systemd service with low-port capability. -- [x] Remove reported compression threshold type mismatch without narrowing or panic conversion. -- [x] Explicit frontend IP/port/TLS ownership inside a vhost; actual-destination isolation. -- [x] Multiple backend members, weights, backup and drain modes. -- [x] Weighted round-robin, least active exchanges, least traffic, adaptive and IP hash. -- [x] Connection keepalive/reuse limits and timers, not a Keepalived/VRRP integration. -- [x] Bounded TCP/HTTP health checks, slow-response exclusion, fall/rise and recovery. -- [x] Opaque cookie/IP affinity; compatible edit continuity, safe cache bypass. -- [x] Full-exchange backend leases; no automatic replay of failed writes. -- [x] Per-vhost response/security headers, TLS-only HSTS and explicit preload policy. -- [x] HTTP/2 TLS preference retains HTTP/1.1 for clients and HTTPS origins. -- [x] Vhost editor, atomic legacy migration, health/traffic/latency dashboard and API metrics. -- [x] All new user-facing text in discovered language packs; logo unchanged. -- [x] Rust regression and real-binary acceptance test source; 63 simulated-API UI checks. -- [x] Complete source update, examples, migration documentation and evidence report. -- [x] Split oversized status JSON expansion instead of masking it with a larger recursion limit. -- [x] Optional per-vhost certificate upload/selection with bounded private staging and validated SNI mappings. -- [x] Independent vhost cache behavior, inherited defaults and shared resident/object ceilings. -- [x] **Selective cache invalidation by vhost**, promoted from Cache evolution; invalidate pending fills too. -- [x] Compact manager list and tabbed vhost dialog, visual header rules and live health/latency. -- [x] Dashboard density presets and top alignment, without redesigning the logo. -- [x] Separate modal/docked live logs with persistent controls, vhost filters, pause and scoped export. -- [x] Global/vhost error pages, maintenance/static response and direct-IP/unknown-host behavior. -- [x] Security shortcut within Settings; new labels remain in discoverable language packs. -- [x] Python-only random-data mock origin for cache, health, delay, compression and error-page development. -- [x] Regression test source for new runtime policies; executed frontend/helper checks in TEST_REPORT.md. -- [x] Canonical Forwarded/X-Forwarded-For/X-Real-IP with explicit bounded proxy trust; keep destination-IP routing independent. -- [x] Per-vhost Tinyauth auth subrequest, sanitized identity headers, restricted login redirects and fail-closed handling before cache/static/upgrades. -- [x] Separate certificate inventory tab with upload, per-vhost selection and metadata-only read API; leave ACME as future work. -- [x] Named multiple-IP listeners, descriptive scope, reusable vhost references and explicit legacy-route permission. -- [x] HTTP/3 UDP adapter using Quinn/h3 with TLS 1.3, same SNI/policy pipeline, Alt-Svc, requested limits and bounded drain; retain TCP H2/H1. -- [x] Request ID/revision off by default, independent per-vhost controls, optional mock-origin removal and effective per-vhost Server policy. -- [x] Actual frontend socket in cache identity; streaming-safe empty request detection for H2/H3. -- [x] Toast above active modal backdrop, including browser top-layer/inertness handling. -- [x] Python fake-auth peer plus Rust regression sources for auth, headers, extra-frontend cache, listeners and real QUIC/TCP. -- [x] Dedicated bounded cache panel: resident objects/bytes, per-domain effectiveness, TTL/Age/hits, reasons and vhost filters. -- [x] Remove the reserved administrator cookie before cache eligibility; preserve application-cookie privacy. -- [x] Handle empty terminating DATA frames and expose fixed X-Cache-Reason / Cache-Status detail diagnostics. -- [x] Replace special mock-origin hiding with explicit revision controls; keep general header rules. -- [x] Default HTTP/3 for TLS while persisting explicit opt-out and TCP fallbacks. -- [x] Diagnose overlapping sockets with addresses, ownership, protocol/limit differences and remedies. -- [x] Classify unavailable/invalid/timed-out upstreams as 503/502/504 respectively. -- [x] Basic Let's Encrypt DNS-01/RFC2136 job and library-import workflow through Certbot; private TSIG/account state and staging-first consent. -- [x] Offline adapter tests, browser fixtures and Rust regression sources; unchanged brand assets. -- [x] Vhosts-only runtime/UI and one-way legacy import preserving path/backend and IP ownership. -- [x] HTTP-01 active-job/domain-scoped challenge serving with private webroot storage. -- [x] Certbot Cloudflare and OVH API DNS-01 adapters alongside RFC2136. -- [x] Unified provider/import dialog, masked credentials, explicit consent and issuer metadata. -- [x] Dedicated opt-in Prometheus endpoint, scrape CIDRs and environment-token authentication. -- [x] Application/admin/listener/vhost/backend/cache/system collectors and exchange histograms. -- [x] Functional Settings tabs with preserved dirty fields, keyboard navigation and no Security sidebar. -- [x] Offline provider regressions, simulated-API UI checks and unexecuted Rust acceptance tests. -- [ ] Validate real HTTP-01, Cloudflare/OVH/RFC2136 DNS updates, CA issuance, cancellation cleanup and certificate hot-deployment. -- [x] Add the managed renewal scheduler and audited validated deployment source. -- [ ] Verify real renewal/publication and add external expiry alerts, revocation/account lifecycle. -- [ ] Resolve and retain Cargo.lock; run check, test and all real-binary suites. -- [ ] Verify real Tinyauth integration, revocation, trusted forwarding and independent HTTP/3 client interoperability. -- [ ] Run an independent RFC 9114/QUIC conformance suite, malformed-stream/load tests and actual TLS/UDP production deployment checks. -- [ ] Execute vhost isolation/weighted balancing/health/recovery/keepalive/affinity acceptance against a real Rust binary. -- [ ] Verify compression output with independent Brotli/Zstandard decoders. -- [ ] Run real TLS/HTTP2/WebSocket/cache concurrency, cancellation and graceful-drain tests. -- [ ] Benchmark HIT/MISS/streaming RSS and CPU under slow clients and origin failures. -- [ ] Fuzz malformed HTTP/cache directives and language-pack validation. -- [ ] Exercise real non-root 80/443 bindings and systemd service confinement. -- [ ] Verify native browser HTTP/CSP/cookie enforcement (navigation blocked in this environment). -- [ ] Validate the dedicated scrape endpoint/token with a real Prometheus server and production network policy. -- [x] X-Cache-Resident-Age - implemented in 0.9.0 - -## Optional module follow-ups - -### Docker discovery - -Implemented in 0.18.1 through the standalone `docker-agent/` provider boundary. -Future work can add provider-side filters, mTLS and DNS-provider automation; the core remains useful without Docker and never opens the Docker socket itself. - -### Cache evolution - -Conditional revalidation is implemented. Stale-while-revalidate/stale-if-error, request collapsing, -tag-based invalidation and richer per-route policy remain open. Persistent hot/cold storage is implemented in 0.10.0. -Selective invalidation by vhost is implemented in 0.3.0; the domain/object inspector in 0.5.0; tags are not. No VCL engine, -distributed cache, ESI or Varnish-protocol compatibility is implemented. - -### Operations - -Container/OCI packaging is implemented in 0.10.0. Certificate alerting/revocation and account lifecycle, reproducible releases, -performance baselines and cross-platform diagnostics remain open. Remote HTTPS administration is implemented in 0.9.0; -loopback remains the default and plain remote HTTP remains explicitly risk-gated. diff --git a/docs/SECRET_STORE_BACKUP.md b/docs/SECRET_STORE_BACKUP.md deleted file mode 100644 index 5f4fc4c..0000000 --- a/docs/SECRET_STORE_BACKUP.md +++ /dev/null @@ -1,59 +0,0 @@ -# Secret Store and encrypted full backup/restore - -RustProxy keeps application credentials in a central encrypted Secret Store. Provider and ACME secrets are not stored as plaintext configuration values in SQLite. - -## Secret Store - -Secret records live in the `secret_store` SQLite table and contain an identifier, provider/type, encryption version, nonce, ciphertext, metadata and timestamps. Values are encrypted with AES-256-GCM using a random local data key stored at: - -```text -/secrets/master.key -``` - -The key file is created with mode `0600`; its directory is private. The application loads it during normal startup, so normal operation does not require an interactive password. - -Existing ACME DNS credential files are migrated on startup for Cloudflare, OVH and RFC2136. After successful import, the legacy plaintext credential file is removed. Certbot-compatible credential files are recreated only as short-lived private files around the external operation and removed afterwards. - -Do not copy only `config.sqlite3` as a Secret Store backup: the local key material is also required. Use the encrypted full-backup command below. - -## Create a full backup - -`backup create` may run while RustProxy is serving traffic. If the service is stopped, the CLI takes the data-directory lock; if the service is running, it creates a consistent SQLite snapshot and stages durable filesystem state without taking ownership of that lock. The output file must be outside the RustProxy data directory and must not already exist. - -```bash -rust-proxy --data-dir /var/lib/rustproxy backup create \ - --output /private/rustproxy-backup.rpb -``` - -When run from a TTY, RustProxy asks for the backup password without putting it in process arguments or shell history. Press Enter at the password prompt to generate a strong random password. A generated password is printed exactly once and is required for restore. - -For non-interactive `backup create`, RustProxy generates the password automatically and prints it once to standard output. Protect that output as a secret. - -The encrypted backup contains the durable application state needed for recovery, including: - -- a consistent SQLite snapshot, -- configuration and Secret Store records, -- backup-wrapped Secret Store key material, -- certificates and private keys, -- ACME state and durable provider data, -- other durable RustProxy data accepted by the backup manifest, -- validated internal relative symlinks, including Certbot `live/` links into the same profile's `archive/` tree. - -It excludes rebuildable/runtime data such as cache, logs, temporary files, sockets, PID/lock files, SQLite WAL/SHM sidecars and the managed Certbot Python environment (`acme-python`; the `acme_python` spelling is excluded as well). Certificate/ACME durable state inside the RustProxy data directory remains included; the virtual environment is rebuilt when needed. Manual certificates configured with absolute external paths (for example `/etc/letsencrypt/live/...`) are intentionally **not** copied into the backup: only the configured path remains in SQLite, so the external files or mounts must be restored separately. Symlinks inside the backed-up RustProxy data tree are accepted only when their target is relative and resolves inside that staged tree; absolute, escaping or dangling links fail backup/restore. Restore recreates accepted in-tree links as links rather than dereferencing them. - -The outer container derives a backup key with Argon2id and encrypts authenticated chunks with AES-256-GCM. The backup password is independent from the local Secret Store key. - -## Restore - -Restore is CLI-only, requires RustProxy to be stopped, and requires an interactive TTY for the password: - -```bash -rust-proxy --data-dir /var/lib/rustproxy backup restore \ - /private/rustproxy-backup.rpb -``` - -Before replacing the destination, RustProxy verifies the encrypted container, password/authentication tags, manifest, checksums, backup format, RustProxy/schema compatibility, restored SQLite database and decryptability of all Secret Store records. - -Files are first restored into a private staging directory. Only after validation succeeds is the staged data directory atomically activated; activation failure attempts to roll back to the previous directory. Sensitive restored files use restrictive permissions. - -A lost backup password cannot be recovered by RustProxy. diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 56b148a..1f1e798 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -1,225 +1,42 @@ -# Security model and limitations +# Security model and deployment boundaries -This is a development source release with no executed Rust integration results in -the packaging environment. Do not expose it as a production security boundary until -the actual build, protocol tests, deployment review and an appropriate security -assessment have passed. +RustProxy provides several security controls, but it is not a complete Web Application Firewall, PHP sandbox or network firewall. Safe deployment depends on frontend TLS, trusted origin connectivity, application updates, least-privilege operating-system access and release testing. -## Administration +## Administration and private state -Administration binds to loopback HTTP by default. Settings can enable a dedicated -administration HTTPS listener, choose the application-managed certificate or a -certificate from the inventory, persist a non-loopback bind and allow explicit DNS -Host/Origin names. Remote persisted binding is accepted only with administration TLS. -The normal installer, one-line bootstrap and reference service keep the plain-HTTP admin listener -on loopback by default. An explicit startup `--allow-remote-admin` can expose plain HTTP on a -non-loopback address; that path should remain behind a trusted firewall/private network boundary and -should be replaced by Administration HTTPS for direct remote use. -Passwords use salted Argon2id; password work has admission limits and runs off the -network I/O path. Setup requires local filesystem authority or the one-time random -capability. No default administrator/password is created. +- Keep the separate admin listener on **loopback** unless explicitly configuring trusted administration HTTPS and restricted reachability. Use an SSH tunnel for simple remote management. +- Create the first administrator locally. There are **no default passwords**. Protect CLI recovery operations, sessions, session revocation and API token creation. +- Keep the SQLite database, Secret Store encryption material, certificate keys, `.env`, ACME profiles, external credentials and backups readable only by trusted operators. `.env` and service configuration may be trusted executable/input material. +- Never expose stored logs, OpenAPI, detailed status, full config or backup files anonymously. Keep public Vhosts away from the administration listener except in a carefully reviewed explicit-risk topology. -Sessions have HttpOnly/SameSite=Strict cookies, idle and absolute expiries and -credential-generation revocation. Mutations require scheme-aware Host/Origin checks, -the request marker and a CSRF token. Local CLI reset changes the hash and invalidates -all existing sessions without editing SQLite manually. Administration HTTPS marks the -session cookie Secure; HTTP cannot. The admin certificate and bind changes are -restart-required. A dedicated metrics credential is separate from browser sessions. +## Origin and request trust -The data directory and SQLite files are private. The local OS user with data-directory -access is a recovery authority and can create short-lived CLI sessions. Protect that -account, filesystem and backup copies; browser authentication does not defend against -a compromised process owner. `.env` is trusted shell code, never untrusted input. +- Enforce IP/country/DDNS policies, rate limits and HTTP method rules **before** connecting to backends. A Custom location cannot undo a Vhost-level denial. +- Private and local backend addresses require deliberate exceptions. Routing to the local administration listener can expose secrets or create routing loops. +- HTTPS to a backend is not proof of identity unless upstream certificate/hostname verification is enabled; current backend defaults do **not** enable verification automatically. +- Honor forwarding identity headers only from correctly configured trusted peers. A client can forge arbitrary `X-Forwarded-*` and User-Agent headers. +- Restrict Basic Auth to HTTPS and protect Tinyauth endpoints. Authentication traffic should never be stored in public shared cache. -## Backend and routing boundary +## Content and cache privacy -Only configured HTTP/HTTPS origins may be dialed. Absolute-form client targets and -CONNECT cannot turn the proxy into an unrestricted forward proxy. ACL is evaluated -before new TCP/TLS connections and before reuse of an existing upstream connection. -Host and route restrictions combine; route allow cannot override host deny. -Forwarded headers are rebuilt from socket identity by default. An explicit -trusted-proxy CIDR policy can accept a bounded existing chain and derive the client -IP for ACL/affinity; see INTEGRATIONS_040.md. Routing still uses the actual local -destination, never any forwarded address or host. +- Shared cache must refuse private/no-store content and unsafe authenticated representations. Test behavior with cookies, `Authorization`, `Set-Cookie`, `Vary` and conditional requests. +- HTTP BAN/PURGE is disabled by default and must use a narrow allowlist if enabled. Never grant untrusted clients cross-site invalidation rights. +- Rewrites/transforms and custom headers can change semantics; verify that CSP, HSTS, CORS, validators and `Cache-Control` remain appropriate. -Private/loopback destinations require explicit approval. DNS answers pass the same -policy and optional per-backend CIDR restriction; an unsafe answer fails the entire -resolution. Validated results are used without a second DNS lookup. Special-address -blocks include common metadata/link-local targets and normalize mapped IPv6. -Listener self-target restrictions prevent simple loops and control-plane access by -default. A privileged administrator can deliberately set `accept_local_listener_risk=true` -on one backend; private/loopback destinations additionally require `allow_private=true`. -This is intended for consciously accepted local-listener use (including port 9090) and -can create a request loop or expose administration through a public vhost. It does not -relax metadata/link-local/special-address filtering or CIDR restrictions. Keep network -egress and frontend ACLs as defense in depth. +## Web Server and PHP -No backend `Location` redirect is followed internally. HTTPS transport encryption remains -available, but backend trust/hostname verification is off by default. Set `tls_verify: true` -for origins where certificate-chain and hostname authentication is required. With -verification disabled, a network attacker able to intercept that route may impersonate -the origin. Address/DNS restrictions still apply; the local-listener exception exists only -when explicitly accepted as above. Operator-supplied backend configuration is privileged -configuration, not a field that an unauthenticated caller can edit. +- Put credentials, source secrets and backups **outside document roots**. Directory listing and symlink relaxation are disabled by default; change only when deliberate. +- Run PHP-FPM as a separate controlled service. Give unrelated PHP applications distinct FPM pools to prevent cross-application INI/worker leakage. +- Treat `.htaccess` support as a restricted compatibility feature, not arbitrary Apache configuration. RustProxy hardening rules supplement but do not replace application security updates. -## TLS and tunnels +## Integrations and transport -Private key files are size-bounded, regular files, owner-only on POSIX and opened -without following final-component symlinks. Named files cannot escape the certificate -directory by using path separators. Keep the entire directory tree private as well. -Keys and certificate name coverage are validated before publication. Generated -self-signed certificates are development material, not automatic public trust. +- Docker agents have read-only inventory endpoints but their local Docker socket permission is highly privileged. Restrict the host account, port, Bearer token and pinned certificate. +- Prometheus has its own endpoint/network credential policy and no admin browser session. Keep it private or behind a secure tunnel. +- TCP/UDP Streams do **not** inherit HTTP ACL/auth/cache/TLS termination. Protect protocol endpoints separately. +- HTTP/3 needs UDP and should be tested with firewall, NAT and TLS behavior on the actual deployment. +- Managed ACME and external certificate reload integrations require least-privilege DNS/file/token access. -Existing TLS sessions/tunnels are not revoked by changing a certificate or ACL. -Policies apply to new requests and handshakes. Restart/stop the relevant process for -immediate termination until a scoped administrative close operation is implemented. - -WebSocket payloads remain opaque byte streams. The proxy validates upgrade metadata, -not application-level authorization or message semantics. Frame/message limits on a -backend remain the backend's responsibility. Proxy buffers, tunnel/connection counts -and inactivity timers bound proxy-side resource usage. - -Web Server PHP-FPM integration never forwards the client `Proxy` header as CGI `HTTP_PROXY` -(httpoxy protection), regardless of optional Web Service hardening. FastCGI response framing and -hop-by-hop headers are discarded and RustProxy owns downstream framing. PHP-FPM endpoints are -limited to Unix sockets or loopback TCP. Optional custom FastCGI parameters are validated static -operator values and cannot replace core CGI, `HTTP_*`, `PHP_VALUE` or `PHP_ADMIN_VALUE`; php.ini -overrides use a dedicated validated `PHP_VALUE` map. Configurable request-body, response-header -and stderr bounds keep the PHP connector resource usage explicit. - -## Information and resource controls - -Access logging does not collect credential headers, query values, cookies, bodies -or WebSocket payloads. Hostnames and paths are logged; never put secrets in them. Do not enable third-party verbose tracing or application logging without -reviewing their output separately. Access rings, viewer slots, auth work, updates, -DNS workers and request/connection admission are bounded. A slow log viewer does not -create an unbounded delivery queue. - -Backups include TLS private keys and credential/session state. Restore matching -SQLite and certificate data together, then revoke restored sessions. Never commit -`.env`, the data directory, backups or generated certificates to a public repository. - -## Not a completed security feature - -Docker socket access controls, service-account metrics access, -2FA, roles, provider secret storage, certificate -expiry automation, a full special-address registry policy and independent fuzz/load -assessment remain roadmap work. Feature availability is not evidence of a completed -security audit or a performance guarantee. - - -## Cache, response transforms and ports (0.1.0) - -Cache is enabled by default in 0.8.1 and ACLs precede lookup. Cookie (outside the -configured static-asset exception), Authorization, Range, unsafe preconditions and -request/response `no-store` remain hard shared-cache exclusions. Conditional validators -can be satisfied from a fresh object or revalidated against the origin. Response -`no-cache` with a usable validator may be retained only for mandatory validation before reuse. Origin privacy is -checked before custom header removal, and generated request IDs are not cached. Bounded -fill permits, object/entry/byte limits and generation checks protect cancellation, -update and purge paths. Purge is authenticated, CSRF-protected and audited, not a -public PURGE/BAN capability. - -Compression is opt-in, with authenticated/private/no-transform exclusions. Response -rules cannot overwrite protected framing/identity fields. For compatibility with 0.26.6, -custom `Age` rules remain accepted. Response-side `Age` is deferred to the final wire stage, -after RustProxy's cache freshness/revalidation logic, so it cannot alter stored age, TTL or -cache eligibility. Server hiding is cosmetic and -must not be considered an access-control or vulnerability mitigation substitute. - -Standard public 80/443 ports are supported without globally forbidding remote -origins using those ports. Local listener targets remain guarded. Review SETTINGS.md -for interface-enumeration and restart semantics. A Linux service can grant only -CAP_NET_BIND_SERVICE to a dedicated unprivileged account; the application does not -acquire capabilities itself. Public listener controls do not implicitly expose the separate admin -API. The provided systemd unit is an unexecuted example, not an audit result. - -Language packs are trusted build inputs with size/type/metadata bounds. The browser -renders strings as text and falls back per key. Translations are not executable -plugins, and no directory browsing or runtime arbitrary file loading is exposed. - -## 0.2.0 frontend and pool boundaries - -An explicit vhost binding is checked using the accepted stream's real local IP, -port and TLS state. Host, SNI and forwarded headers cannot assert a different local -address. A shared wildcard TCP listener does not grant every vhost access on every -accepted IP. Legacy routing does not inherit additional frontend sockets. Conflicting -hostname ownership is rejected, including disabled declarations. - -Binding a private IP is not an authorization mechanism or firewall. NAT, port -forwarding or another proxy can make that destination reachable. Configure network -restrictions and application authentication as well; no trusted-proxy identity or -Keepalived/VRRP service is introduced. New unavailable IP bindings fail startup. - -Health probes share literal/DNS/allowlist/loop protections, including actual old -listeners and desired new listeners, and cannot follow redirects to arbitrary -origins. They are bounded and do not use user cookies or credentials. Probes can -still hit an operator-selected GET path; select a side-effect-free health endpoint. - -Affinity uses opaque random tokens but is not authentication, CSRF protection or -session replication. Tokens survive compatible edits, not process restarts. Cookie -mode bypasses cache; the dedicated cookie is stripped before origin forwarding and -cannot be overridden by an origin Set-Cookie. No failed request, especially a write, -is replayed on another member to obtain a successful response. - -Dedicated per-vhost security headers also cover matching-host denials/errors and -cache hits. HSTS is removed on plaintext; includeSubDomains/preload remain explicit -opt-ins. Preload directives do not enroll a domain in a browser list. Arbitrary CSP -and Permissions-Policy values are encoding-validated, not semantically audited. -Old configurations retain origin headers unless an explicit policy overrides them. - -## Certificate uploads and custom pages (0.3.0) - -Certificate upload is an authenticated local-control-plane mutation protected by -Origin and CSRF validation, body/per-file bounds and two-worker admission. PEM pairs -are parsed, key-matched and checked against supplied DNS names. Private immutable -files use generated simple names, mode 600 and the existing no-symlink/directory -lock protections. Public API responses/config/audit contain metadata, not key PEM. -JavaScript clears file controls after use, but does not claim secure erasure of -browser memory. Only configuration publication activates an inventory selection. -No new listener or public certificate-upload endpoint is created. - -Custom HTML is fully trusted administrator-authored content served on the chosen -public vhost. It may contain executable HTML/scripts there; it is never previewed -or inserted as HTML in the admin DOM. Do not upload untrusted markup. The fixed -welcome page interpolates only the software version, not Host, URL or user input. -HTML is bounded to 32 KiB per page and by the overall 256 KiB configuration limit. -Responses use no-store; old representation encoding/length/cookies are discarded -when replacing an origin error. Authentication challenges, Allow and Retry-After -are retained, then administrator response/security rules are applied. - -ACLs run before static/fallback/direct-IP pages. Even a custom status 200 for a -blocked request never authorizes an upstream fetch. Such overrides can confuse -clients and monitoring; retaining the original 401/403/5xx status is recommended. -Direct-IP HTTPS still needs a completed TLS handshake and a suitable certificate; -an HTTP welcome page cannot remove a TLS certificate-name error. - -## Integration boundaries in 0.4.0 - -Tinyauth runs after host/path ACLs and before any cached/static/origin response or -WebSocket upgrade. Incoming identity headers are stripped, including after generic -header rules, and only configured fields from a successful verifier are copied. -The verifier gets no request body or admin session cookie. Its DNS/private-network -and TLS checks match the origin safety boundary. Authentication failure cannot be -turned into a 200 by custom pages. Denials and protected responses are no-store; -there is no auth-result or protected-content shared cache. Existing upgraded -sessions are not reauthenticated continuously. - -Only configured login-origin redirects are accepted. Do not expose the fake-auth -helper or treat its test cookie as an actual login. H3 uses the same routing, -auth, request limits and response policies; 0-RTT is disabled. Transport resource -limits do not replace rate limiting, firewalls or independent security/conformance -testing. H3 is uncompiled/unexecuted in this packaging environment. - -Diagnostic response fields are hidden by default, but origin secrets still need -explicit response rules. In 0.5.0 the dedicated controls expose request ID and -revision independently; the obsolete hide_mock_origin field is not accepted. X-Mock-Origin can be -removed using ordinary response-header rules. Scope labels on listeners never -enforce network restrictions. See ACME_RFC2136.md for private TSIG/account storage -and the separate backup requirement for manual DNS-01 issuance. -## User-Agent blocklists - -RustProxy can apply an optional global User-Agent blocklist and an additional per-vhost blocklist. Matching is case-insensitive; plain text matches a substring and `*` is a wildcard. Policies are bounded and disabled by default. They are evaluated before opening an upstream; a match returns HTTP 403. Requests without a User-Agent are not denied by this rule. - -User-Agent is client-controlled and easy to spoof, so this is an operational noise/blocking control rather than an authentication or identity boundary. Keep ACLs, authentication and rate limits as the authoritative controls. +## Release and operational validation +The provided 0.34.0 source includes changes without completed native compilation, all-protocol regression or production performance measurement. Do **not** treat source-code inspection as proof of security or performance. Before using it at an Internet edge, test on the target OS, run the automated suite, verify TLS/H2/H3/WebSocket handling, access and cache privacy, backup/restore, logging and failure recovery. Keep monitored off-host backups and a rollback plan. diff --git a/docs/SECURITY_AUDIT.md b/docs/SECURITY_AUDIT.md deleted file mode 100644 index 5792211..0000000 --- a/docs/SECURITY_AUDIT.md +++ /dev/null @@ -1,78 +0,0 @@ -# Limited administration exposure audit - 0.7.1 - -## Scope and evidence - -Reviewed the supplied 0.7.0 source and this patch's admin router/middleware, -authentication endpoints, sessions, API assets, dashboard login/logout, Swagger -integration, proxy header rule precedence and deployment readiness consumer. -This is a manual source review plus static/browser-script regression tests. It -is **not** a penetration test of the user's installed binary or a complete audit -of proxy/TLS/QUIC/ACME/operating-system/dependency security. No Rust compiler was -available; runtime enforcement and complete vendor rendering remain unverified. - -The reported unauthenticated operational JSON disclosure was not reproduced on -an installed server. In the input source, `/api/config`, status, logs, metrics, -backend/cache and certificate endpoints already passed through a session guard. -The concrete findings were public version/mode metadata, unnecessarily public -operational HTML/JS/full language assets, broad path-only method exceptions and -incomplete browser-state minimization. Do not infer that a user's older build, -reverse-proxy front or a different exposed endpoint had the same behavior. - -## Findings and source changes - -| Observation | 0.7.1 change | Verification limit | -|---|---|---| -| `/healthz` disclosed version; auth discovery disclosed version/mode/password policy | Health reduced to one status field; auth discovery to setup-required boolean | Source/contract tests; actual router tests supplied, not run | -| Full operational HTML/modules/language bundle could be fetched anonymously | Root selects minimal login or authenticated document; full modules/bundle are private | Login fetch/DOM checks use mocked fetch, not real HTTP | -| Public route exemptions were not a strict method/path matrix | Explicit allowlist; private and unknown requests denied before JSON/query parsing | Six additional async Rust test functions supplied, unexecuted | -| Anonymous JSON rejection could include extractor input detail | Login/setup map parser failures to a fixed message, preserving 400/413/415 status semantics | Source and unexecuted router tests | -| Mutation marker/CSRF duplicate values were not explicitly rejected | Require one exact Origin, marker and session-bound CSRF value | Existing guards retained; added unexecuted duplicate-header tests | -| Stale DOM/form values can outlive a visible logout screen | Scrub inputs/state, notify other tabs, reload root; docs clear schema/results on session change | Chromium script tests with simulated location/storage | -| New API docs would otherwise create another disclosure surface | Protect document, schema and every necessary asset with existing guard; no wildcard asset route | Route/schema contracts; vendor-renderer acceptance outstanding | -| Interactive docs can carry credentials into requests | Fixed same-origin schema, per-action session verification, no external validator/persisted auth, writes opt-in + CSRF | Custom interceptor tests use a vendor stub; server guard is the authority | -| Updater used public version in `/healthz` | Locally verify systemd MainPID executable against staged release after minimal readiness | Temporary-file and simulated-systemd tests; real deployment not run | - -## Deliberate public minimum - -Anonymous login assets, favicon/shared CSS, setup-needed boolean, minimal health -status, and guarded login/one-time setup POSTs remain. These are necessary for -sign-in, initial administration and local readiness. Static CSS may contain UI -selectors; it contains no runtime configuration or secrets. Invalid Host/Origin -still returns generic 403 instead of bypassing the boundary. - -Login/setup POST success is allowed to create/return session information **only -after credential/capability validation**. No API Bearer token or setup token was -added as an alternative authority for operational reads. Setup remains one-time, -local-capability-based; password checking/rate limiting/session invalidation were -not weakened or replaced by JavaScript-only checks. - -## Separate Prometheus listener - -Admin `/metrics` and `/api/metrics` require the browser/admin session. The optional -standalone Prometheus listener remains independent, disabled by default, with -configured source CIDRs and its existing bearer-token policy. Non-loopback export -requires a token by the existing Rust validation. A loopback exporter can be -intentionally configured without a browser login. Do not treat it as an overlooked -admin API exception, and do not place it on an untrusted network without reviewing -its token, CIDRs and protected transport. This patch does not verify its runtime. - -## Outstanding acceptance and operational caveats - -- Build and execute `dev.sh check` / `dev.sh test`; run `admin_audit.py` against - the actual upgraded service. Compare 401/no-store responses without cookies. -- Open real `/docs` in a normal browser; verify rendering, actual cookie+Origin+CSRF, - logout, CSP console output and failure after revocation. Vendor assets were not - downloaded/rendered here, and build dependency compatibility is unverified. -- Test real install/update restart/rollback; local MainPID and `/proc` verification - now fail closed when the expected executable cannot be established. -- Retain loopback-only administration and use a protected tunnel. Cookie/session - security does not protect a compromised local account, malicious browser extension, - an administrator sharing exported configuration, or every possible web attack. -- No RBAC/read-only administrator role, general API token scopes, dependency CVE - scan, fuzzing or proof of vulnerability absence is delivered by this small patch. -- Review a real resolved Cargo.lock and the pinned Swagger artifact before shipping - binaries. Source review is not a substitute for release acceptance. - -For the exact exposure matrix and test/build commands, read [API_ACCESS.md](API_ACCESS.md). -The review uses OWASP REST/session guidance as a checklist, not a certification: -https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md deleted file mode 100644 index c9c18d0..0000000 --- a/docs/SETTINGS.md +++ /dev/null @@ -1,674 +0,0 @@ -# Listener and response settings - -## Administration appearance preferences - -**Settings -> Appearance** contains dashboard-only preferences. They are stored per administrator in the existing `admin_preferences` SQLite document and do not create a proxy configuration revision. The preferences are theme (`dark`/`light`), language (`auto` or a language-pack id), global interface scale (70-125%), timestamp format (`24h`/`12h`), chart style (`default`, `soft`, `technical`, `high_contrast`, `neon`, `monochrome`, `warm` or `cool`), button style (`normal` or `compact`), table/log density (`normal`, `compact`, `ultra`), dashboard density (`normal`, `compact`, `ultra`), Overview card order and Overview card visibility. `auto` language follows the browser language when a matching pack exists and otherwise falls back to the configured default pack. - -The scale applies to the complete authenticated administration surface, including navigation, workspace content, dialogs, overlays and the draft dock. Text fields, selects, textareas, checkboxes and radios also receive a small proportional control-size adjustment on top of global zoom so field proportions track the selected scale more visibly. Button style is independent: **Normal** keeps the refreshed control sizing and **Compact** restores the smaller source-style sizing, including generated entity actions and tab buttons inside dialogs. Table/log and dashboard density are additional layout controls and do not change the whole-interface scale. The dashboard editor can hide/show and reorder the telemetry cards at the top of Overview; this changes presentation only, not metric collection. Stream card explanations appear on hover and keyboard focus instead of expanding the card list. Stream cards default hidden but, once switched on, remain present even when no Stream is active (neutral no-data state). The selected time format is shared by logs, backend/runtime diagnostics, audit/session timestamps, certificate views and configuration-history timestamps. Chart presets change colors, surfaces, grid and line presentation only; they do not change sampled data, and the palette selector exposes the main colors for each preset. Each Appearance subsection has its own reset action, and **Reset appearance** restores all documented Appearance defaults without touching unrelated account preferences. A small local cache is retained only to avoid a theme/scale flash before authentication, while the authenticated SQLite preference remains authoritative. - -The **Corner style** choice applies to standard dashboard/metric cards, vhost -cards, entities, notices, backend protocol badges and inputs independently from -color themes; the default preset retains the original appearance. On narrow -screens the Overview uses two small metric tiles per row rather than one. -Status refreshes update values without resetting widget order or scroll. - -The Vhosts list shows one upstream protocol badge per configured reverse-proxy -backend: the configured mode (H1, Auto, H2 compatible or H2 only) plus the last -HTTP/1.1 or HTTP/2 response version observed by RustProxy while proxying a -request. An unknown or stale observation (over five minutes old) appears as `?`, -including in Auto mode; no new probe is issued, and parallel connections can -use different negotiated versions. Hover/focus displays the observation note -and aggregate response-version counters. These are authenticated live-status -observations, not the configured frontend HTTP version. - -Unfinished Web Server, Vhost, Redirect and Stream editors remain session-only drafts. Their bottom-right dock uses square corners, can collapse to a single title bar, shows the age of each suspended draft and does not persist draft form values to SQLite. - -The Settings page includes a local search field that filters category tabs using translated category labels and page content. Filtering never rebuilds settings forms, so unsaved values remain in the DOM. Category navigation remains sticky on desktop and marks categories containing unsaved forms. - -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. - -## Certificate API credentials (0.31.9) - -Open **Settings → Certificate API** (`/settings?tab=certificate-api`) to manage external certificate automation credentials. The credential list now has **View settings**, **Edit settings**, **Regenerate token** and **Revoke** actions. The preview shows Key ID, non-secret comment, allowed certificate IDs, reload-only/deploy scope and creation/last-use times. Existing bearer token values cannot be viewed: only hashes are stored. - -**Edit settings** loads the current credential into a form with fixed Key ID. You may update its comment, list of 1–32 explicit certificate IDs, and **Also allow deploy** permission. **Save settings** changes only this metadata; the current secret stays valid and is never shown or replaced. Legacy `*` certificate grants must be replaced with explicit IDs before saving an edit. - -**Regenerate token** requires confirmation. It generates a new random bearer token, preserves the Key ID/comment/permissions/allowed IDs, immediately invalidates the old token and reveals the new token only once. Copy it into the external automation before closing the page. Regeneration updates the token's creation/rotation timestamp and clears last-used metadata. The same existing session + Origin + CSRF checks protect both operations; bearer-only external clients cannot read or change credentials. - -The existing **Generate / rotate token** form still creates new Key IDs (and retains its previous ability to rotate a Key ID by supplying it). Use **Edit settings** to modify settings without rotating, and **Regenerate token** to rotate without changing any settings. See [API](API.md) and [access control](API_ACCESS.md). - -## Compact Overview card controls (0.32.6 development branch) - -**Settings → Appearance → Dashboard layout** now puts Dashboard density on a -compact row and gives the Overview card controls the full width of the section. -The card selector is a single horizontally scrollable strip by default, with -left/right browsing buttons and accessible keyboard focus. **Show all cards** -expands the same 62 visibility/reorder controls into a wrapped grid. Use the -existing visual layout editor for drag-and-drop arrangement. Reordering and -visibility still save through `/api/preferences` per account; this UI-only -change does not alter server polling, proxy routing or backend metrics. - -### Overview controls and density (0.32.7 development follow-up) - -**Refresh now**, **Arrange Overview**, **Unlock layout**, **Visual Overview editor** and **Show all cards** inherit the same Normal/Compact button size rules as other buttons. Responsive placement still adapts to narrow screens, but no independent font-size, padding or height override applies. Dashboard density more clearly adjusts numeric tile sizing, inline chart height and the padding/title size of non-table Overview panels; the common tile gutters are unchanged. Button style and dashboard density remain independent per-account preferences. - -### Overview header actions (0.32.8) - -On the Overview page, **Traffic, in focus.** remains on the left and actions -align to the right on the same row when space permits. Buttons wrap to the right -on very narrow displays instead of being compressed into equal-width columns. -The existing Normal/Compact button preferences, editor confirmation actions, -layout locking and account-specific saved card layout are unchanged. - -## Refresh preferences (0.31.0) - -**Settings -> Appearance -> Data refresh** saves an additional `refresh` object in the existing `admin_preferences` document for the signed-in account. It does not modify proxy configuration or create any migration. Older documents, including partially populated `refresh` objects, use defaults. Browser and server enforce exact allowed values: - -| Preference | Default | Allowed seconds | -|---|---:|---| -| `refresh.live_enabled` | `true` | `true`, `false` | -| `refresh.overview_seconds` | 3 | 1, 2, 3, 5, 10 | -| `refresh.traffic_seconds` | 5 | 0, 3, 5, 10, 30 | -| `refresh.cache_seconds` | 5 | 0, 3, 5, 10, 30 | -| `refresh.system_info_seconds` | 60 | 0, 15, 30, 60, 300 | - -`live_enabled=false` disables automatic SSE **and all polling**, including system information. `0` disables only that source's automatic polling. Overview and System information have **Refresh now** buttons; Traffic and Cache retain their own buttons, filters, charts and pagination. The per-view Auto checkboxes for Traffic/Cache/Transform and the Docker live switch remain independent opt-outs, and none changes another administrator's preferences. **Reset section** restores refresh defaults without changing theme or unrelated preferences. - -The main `/api/events?interval=N` connection (allowlisted 1/2/3/5/10, default 3 seconds) carries compact live status, cached CPU/RSS/aggregate and per-interface network chart samples from the server's existing background OS collector, plus the existing bounded log snapshot. The server checks the session on every event and retains its heartbeat, viewer cap and normal disconnect cleanup. On an SSE error the browser polls `/api/status?view=live` and `/api/logs` until snapshots resume, not concurrently during healthy streaming. The old full `/api/status` response is kept for API clients and initial diagnostics. Process/System detail values still update only after the separate authenticated `/api/system-info` read at `refresh.system_info_seconds`, while CPU/RSS and network **charts** follow the Overview live cadence (3 seconds by default). The `/api/system-info` endpoint reuses cached diagnostics and ACME metadata, never runs diagnostic subprocesses per request, and is fetched automatically only for Overview or the relevant Settings pages. A hidden browser tab pauses the channel and all automatic polling; returning revalidates its session before normal operation. All interval changes take effect immediately, without reloading or changing any server-global cadence. - -## System information (0.30.1) - -**Settings -> System information** (`/settings?tab=system-info`) is a read-only view of the authenticated `/api/system-info` endpoint (and the initial, backwards-compatible full `/api/status` response). 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. - -## Rules and headers (0.31.7) - -**Settings -> Rules & headers** (`/settings?tab=policies`) now groups the existing **Global User-Agent blocklist**, **Global custom headers** and **Client response** forms as persistent sub-tabs, following the Cache settings layout. There is no migration: each form continues to update its previous configuration section. **Settings -> Security** retains administrative hostname, Origin, session and password policies. - -**Vhosts -> Security -> Traffic policy** also supports optional `rate_limit.user_agent_limits` in each vhost, for example: - -```json -{ - "rate_limit": { - "enabled": true, "requests": 120, "window_seconds": 60, "burst": 30, - "user_agent_limits": [ - {"pattern": "*scanner*", "requests": 10, "window_seconds": 60, "burst": 0}, - {"pattern": "BadBot", "requests": 2, "window_seconds": 60, "burst": 0} - ] - } -} -``` - -The first case-insensitive match wins. A plain pattern searches for a substring and `*` matches any sequence. A matching User-Agent uses its **own bucket for the vhost, rule and client IP** and overrides that request's standard/country throttle; nonmatching requests continue using existing rate rules. Rules also work with `enabled=false` for the base limiter. The User-Agent header is untrusted, so avoid treating these limits as authentication or identity enforcement. - -The **Revision history** detail shows only changed leaf fields, scrolling locally when there are many. **Full JSON comparison** is directly below Restore in the action group and opens a separate standard dialog. ACL profile *Attached to* details are collapsed by default. Listener HTTP/2 and HTTP/3 controls have additional spacing. - -## Configuration and update semantics - -GET `/api/config` returns a revisioned envelope. PUT the entire document with -`expected_revision`. Defaults apply when fields are omitted. Unknown fields are -rejected; failed validation/persistence leaves the old snapshot active. Existing -backend/route/TLS/ACL data must be retained when changing settings. The panel does -this and preserves dirty settings forms across status/language refreshes. - -```json -{ - "listeners": { - "endpoints": [ - {"id":"http", "name":"HTTP", "scope":"public", "enabled":true, - "binding":{"address":"0.0.0.0:80", "tls":false, "http2":false, "http3":null}}, - {"id":"https", "name":"HTTPS", "scope":"public", "enabled":true, - "binding":{"address":"0.0.0.0:443", "tls":true, "http2":true, - "http2_settings":{"idle_timeout_seconds":300, "max_connection_lifetime_seconds":0, "drain_timeout_seconds":10}}} - ] - }, - "server_header": {"mode":"custom", "value":"RustProxy"}, - "compression": {"gzip":true, "br":true, "zstd":true, "deflate":false, "min_bytes":1024}, - "headers": { - "request": [{"op":"set", "name":"x-proxy-name", "value":"RustProxy"}], - "response": [ - {"op":"remove", "name":"x-powered-by"}, - {"op":"set", "name":"x-served-by", "value":"RustProxy"} - ] - } -} -``` - -This is a settings fragment, not a replacement for the existing routing/vhost document. -A complete opt-in example is in `examples/cache-dashboard.json`. - -The dashboard exposes the complete raw JSON editor under **Settings -> Manual config**. -The same card exposes **Revision history**, which reads the newest 512 retained configuration revisions from the configuration-history table introduced in SQLite schema 8 (current database schema 9) and groups them by month/day. Selecting a revision shows timestamp, actor and only the changed fields against the previous retained revision; the separate Full JSON comparison dialog contains complete JSON documents. An older retained revision can be restored from the same dialog; RustProxy validates it through the normal configuration update path and saves it as a new revision, preserving the prior history. -The historical `/configuration` browser route is retained only as a compatibility alias and -opens that Settings block; it is no longer a primary-navigation item. Prefer the structured -section editors for routine changes so unrelated sections are not republished. - -## GeoIP updates - -**Settings -> GeoIP updates** controls the background country-database refresh and is enabled by default. The worker starts only after all server sockets have bound successfully, attempts the first refresh immediately, and then refreshes every 24 hours by default. Sources are direct HTTPS file URLs rather than coupled release/checksum base URLs. Publisher SHA-256 verification is optional per file; every downloaded payload is still size-bounded, streamed to a private staging file and parsed as the selected database format before publication. - -Two formats are supported. **CSV ranges** uses separate IPv4 and IPv6 files and defaults to the direct `sapics/ip-location-db` `user-country-ipv4.csv` and `user-country-ipv6.csv` assets. **MaxMind DB (MMDB)** uses one `country.mmdb` file for both address families and defaults to the combined sapics `user-country.mmdb` asset. Standard MaxMind country databases are read from `country.iso_code`, then `registered_country.iso_code`; custom country MMDBs may expose top-level `country_code` or a root two-letter country string. To manage a private MMDB outside the updater, place it at `/geoip/country.mmdb`, select MMDB, disable automatic updates and leave the MMDB source URL empty; **Download now** then validates/reloads the local file instead of fetching it. - -When a checksum URL is empty, RustProxy deliberately skips publisher-checksum comparison. It still computes a local SHA-256 for the validated download and compares it with the installed file, so unchanged content is not republished or hot-reloaded. This avoids coupling a moving `latest` data URL to a separately moving checksum release. If a checksum URL is supplied, a mismatch rejects the staged file. The configured maximum is 1 MiB..256 MiB and the interval is 60 seconds..31 days. Failures are warnings only; proxy traffic continues and the worker retries. Manual UI refresh, the background worker and managed upgrades share `.geoip-update.lock`. After publication the `ArcSwap` database is replaced atomically: requests already holding a snapshot may finish on it, while subsequent country ACL/rate-limit/logging lookups and status/metrics reads use the new database without a restart. - -```json -{ - "geoip_updates": { - "enabled": true, - "format": "csv_pair", - "ipv4_url": "https://github.com/sapics/ip-location-db/releases/download/latest/user-country-ipv4.csv", - "ipv6_url": "https://github.com/sapics/ip-location-db/releases/download/latest/user-country-ipv6.csv", - "mmdb_url": "https://github.com/sapics/ip-location-db/releases/download/latest/user-country.mmdb", - "ipv4_checksum_url": null, - "ipv6_checksum_url": null, - "mmdb_checksum_url": null, - "max_bytes": 268435456, - "interval_seconds": 86400 - } -} -``` - -## Administration HTTPS - -`administration` is separate from public proxy listeners. It defaults to the startup -loopback address and plain HTTP. Persisted address/certificate changes are validated -immediately but take effect after process restart. A non-loopback persisted bind is -accepted only when `administration.tls` is true. Plain remote HTTP remains available only when process startup explicitly allows it with -`--allow-remote-admin`. - -```json -{ - "administration": { - "listen": "0.0.0.0:9090", - "tls": true, - "certificate_id": "admin-example", - "allowed_hosts": ["admin.example.com"], - "public_origin": null, - "session_days": 30, - "panel_compression": {"zstd": true, "br": true, "gzip": true} - } -} -``` - -### Fixed public origin for a reverse-proxied panel (0.31.4) - -For a **panel** reached through a trusted reverse proxy, set the optional -`administration.public_origin` in **Settings → Administration** to the exact -browser-facing HTTPS origin (for example `https://panel.example.test`). -**Do not duplicate that hostname in `administration.allowed_hosts`.** Its DNS -hostname is implicitly included in the runtime panel Host allowlist. **Restart -RustProxy**; this is a restart-bound access-control setting. Omit it (`null`) for -direct administration or when forwarded headers are canonical. This setting -is not a wildcard, does not accept HTTP or a URL path, and requires a DNS -hostname rather than an IP address. SQLite schema 9 remains unchanged. - -The advanced **Additional allowed DNS hostnames** field controls independent, -extra hostnames only. Older configurations containing both values are -accepted; on editing/saving the redundant duplicate is omitted from the -persisted list. If `public_origin` changes or is cleared, its old hostname is -no longer implicitly allowed (unless explicitly retained as an additional host). - -Example for the default **private loopback** administration listener behind an -HTTPS-terminating proxy: - -```json -{ - "administration": { - "allowed_hosts": [], - "public_origin": "https://panel.example.test" - } -} -``` - -The last hop must connect from loopback or an existing explicitly trusted proxy -CIDR. If it is not loopback, configure **only the actual proxy peer CIDR** under -Forwarding → Trusted proxies; be aware the general trusted-proxy setting also -applies to public traffic and must never include untrusted clients. The -administration listener should not be reachable directly from external clients. -Do **not** grant trust to public networks to work around Origin failures. - -The pinned URL, not an element of `X-Forwarded-Proto`, defines the panel -scheme/authority. A trusted, bounded proxy chain `https, http` (HTTPS on the -browser side, HTTP on an internal hop) is permitted in this explicit mode. -An `http` first hop, malformed/too-long protocol chain, duplicate/conflicting -`X-Forwarded-Host`, different `Origin`, `Sec-Fetch-Site: cross-site`, and missing -CSRF protection for state-changing requests still result in HTTP 403. The -public proxy/vhost data plane is **not changed**, except that an already opted-in -self-admin vhost can recognize the pinned hostname for its reserved panel session -cookie; other upstreams continue to strip it. Credential-bearing certificate -automation retains its original TLS/forwarding checks. - -Prefer fixing the proxy's forwarding headers rather than configuring the pin -when practical. For a TLS-only public entry, the closest trusted hop should -**replace**, not append, forwarded headers using known values, for example: - -```nginx -proxy_set_header Host panel.example.test; -proxy_set_header X-Forwarded-Host panel.example.test; -proxy_set_header X-Forwarded-Proto https; -``` - -Only use that `https` setting on an entry that actually verifies external TLS; -never trust an arbitrary inbound `X-Forwarded-Proto`. If multiple intermediate -proxy hops must append protocol values, the pinned mode provides an explicit -server-side comparison target without accepting a client-supplied origin as -authoritative. Confirm deployment using a real mobile browser and the safe -`administration origin rejected` logs before production rollout. - -**Administration → Panel content compression** (next to the admin listener/TLS settings) -controls Zstd, Brotli and gzip for the authenticated HTML document, JavaScript, -CSS and language bundles. Each browser request is negotiated using -`Accept-Encoding` quality values: the highest positive `q` wins, and ties -prefer **Zstd → Brotli → gzip**. Codecs can be enabled independently; the -identity representation is returned when none is acceptable or all are off. -The source is encoded only once per embedded asset and shared across visitors. -Settings persist in the existing revisioned administration document, without -additional CLI flags or a separate environment/config variable, and take effect -without restart. Each compressed response includes `Content-Encoding`, -`Vary: Accept-Encoding` and the representation-specific `Content-Length`. -The response compression of proxied websites remains independent; API responses, -SSE and WebSocket streams are not affected. - -**Reverse proxy:** an upstream may remove or replace `Accept-Encoding`. RustProxy -negotiates from the header it actually receives, not the original browser request. -A reverse proxy normally passes through already encoded responses instead of -recompressing them; if the reverse should exclusively manage encoding, configure -it to strip `Accept-Encoding` on requests upstream, or turn the panel codecs off. -This provides compatible HTTP negotiation but does not guarantee identical -compression with every reverse proxy configuration. - -`certificate_id: null` uses `data/certs/default.pem` and `default.key`, generated by -the application. A selected inventory certificate is loaded from the same private -certificate directory. `session_days` controls the absolute browser-session lifetime -and must be 7..30 (default 30). It applies without restart; changing listener/TLS/certificate -settings still requires restart. Session cookies are HttpOnly/SameSite=Strict and receive -the `Secure` attribute on HTTPS. When HTTPS is enabled, accidental plain HTTP received on the same administration -port receives a permanent redirect to the same authority/path over `https://`; the -plaintext path contains no administration handlers. An empty `allowed_hosts` accepts -valid DNS Host values on the configured HTTPS port as well as valid numeric destination -addresses. Once `allowed_hosts` contains entries it becomes a strict DNS allowlist for -browser-facing DNS authorities. An explicitly listed hostname may arrive from a trusted -reverse proxy on an external port such as 443 even when the private admin socket is 9090. -Direct IP/`localhost` access is still accepted only on the actual configured admin port. -For HTTPS termination in front of a plain local admin socket, the trusted proxy must pass -its external Host and scheme; `X-Forwarded-Host` is accepted only from loopback/configured -trusted proxy peers and `X-Forwarded-Proto` additionally follows the forwarded-proto trust -policy. Origin/CSRF comparison and the session cookie Secure flag use that external origin. -Use a firewall and normal network policy even with HTTPS. - -## Structured access logging - -The **Settings -> Logging** page is split into **Outputs**, **JSON format** and **Error log** subsections, using the same in-card sub-tab layout and keyboard interaction as Cache. Outputs controls optional external access-log sinks while the existing bounded live ring remains available to the panel. Sink I/O is performed by a bounded background queue; request processing only performs a non-blocking enqueue. Supported destinations: - -- JSONL file append, either one combined file such as `/var/log/rustproxy/access.jsonl` or - one file per resolved vhost under a configurable directory (default `/var/log/rustproxy/vhosts`). - Unmatched traffic uses `_unmatched.jsonl`. The managed systemd service provisions - `/var/log/rustproxy` for the service account and keeps that directory writable under - `ProtectSystem=strict`. Other custom paths still need matching service permissions. -- Logstash TCP using `host:port` (IP or DNS). Each event is one JSON line. -- Loki HTTP(S) push. Optional bearer credentials are read from an environment variable - named by `bearer_token_env`; the secret itself is not stored in configuration. - -`logging.format.fields` maps output field names to standard event fields; an empty map -emits the complete standard event. The panel provides Standard, Nginx-style, ECS-style -and Minimal presets plus a visual field selector/renamer with JSON preview. Advanced raw -mapping remains available for aliases not representable by the simple builder. Supported mapped sources include `vhost`, `route`, `cache_effective_enabled`, `transform_effective_enabled`, `backend`, request/response metadata and cache outcome fields. `static_fields` adds constant string fields. For low-level `protocol_error` events, RustProxy always preserves the required diagnostic envelope (`event`, error kind/detail, time, client/local/TLS/protocol fields, status and outcome) even with a custom mapping, so malformed-traffic records remain searchable in the built-in Logs explorer. Loki labels are separately configurable. -`/api/status.logging.sinks` reports queue capacity, dropped events, successfully delivered -events and sink errors. - -## Hot and cold cache tiers - -`cache.max_bytes` and `cache.max_entries` bound the HOT RAM tier. `max_bytes` is charged from retained body backing allocation plus entry metadata rather than only payload length. `cache.transient_max_bytes` separately bounds in-progress fills and full bodies moving between HOT and COLD; `cache.cold_queue_max_bytes` additionally caps queued demotions in bytes, and `cache.hot_eviction_max_objects` bounds one LRU admission attempt. The source defaults are 256 MiB HOT, 16 MiB maximum object, 64 MiB transient and 32 MiB COLD queue. The Settings panel calculates conservative/recommended/performance presets from the lower of host RAM and a finite cgroup memory limit without changing the COLD disk budget. It also warns when HOT + transient is a large fraction of effective RAM, when the additional configured cache budget is large compared with current host/cgroup headroom, when the COLD queue exceeds transient RAM, or when the object ceiling cannot fit in transient RAM. Automatic presets are disabled below 256 MiB effective RAM. On a 2 GiB effective-memory VM the recommended profile selects approximately 400 MiB HOT, 96 MiB transient, 16 MiB maximum object, 48 MiB COLD queue, 6400 HOT entries and a 64-object LRU admission limit. The optional persistent -cold tier is controlled by `cold_enabled`, `cold_path`, `cold_max_bytes`, -`cold_max_entries`, `cold_gc_interval_seconds`, `cold_gc_max_delete`, -`cold_after_seconds`, `cold_on_lru`, `cold_on_idle` and `cold_on_shutdown`. -`cold_gc_interval_seconds` defaults to 60 and `cold_gc_max_delete` to 256. Pressure GC -starts at roughly 90% of the configured byte/entry budget and cleans toward roughly 80% -without deleting more than the configured per-pass cap. `cold_path` is relative to the RustProxy data directory and cannot -contain parent traversal or an absolute path. The two demotion switches independently -select LRU-pressure and idle objects; shutdown flush is a best-effort direct write during -a graceful restart. Demotions use a small bounded queue and release hot accounting before -disk persistence so cold storage does not retain an unnecessary second body buffer in RAM. -Cold metadata is readable JSON and bodies are raw `.body` files; reads are size/SHA-256 -checked before promotion to RAM. Since 0.21.0 objects are sharded by the first two SHA-256 -hex characters (`cache/cold/00/...` through `ff/...`), with shard directories created on -first write. The previous flat Cold Cache is intentionally not migrated. Purge operations -clear both in-memory indexes and schedule disk cleanup; selective cleanup uses the same -GC/eviction path and runs asynchronously. - -Version 0.22 adds foreground fill collapsing (`collapse_enabled`, -`collapse_wait_timeout_ms`, `collapse_timeout_fallback`), detailed stale-if-error policy -(`stale_if_error_timeout`, `stale_if_error_connection`, `stale_if_error_dns`, -`stale_if_error_tls`, `stale_if_error_statuses`) and opt-in proactive refresh -(`proactive_refresh_enabled`, `proactive_refresh_before_expiry_seconds`, -`proactive_refresh_percent`, `proactive_refresh_max_inflight`). Defaults preserve the -previous stale-if-error behavior; proactive refresh is disabled by default. Selective -PURGE/BAN is available in the authenticated API/panel, and HTTP BAN/PURGE has an optional, separately stored CIDR allowlist (`cache.http_invalidation_allowed_cidrs`, empty by default). See -[CACHE.md](CACHE.md) for request-collapse, Range and invalidation semantics. -The adjacent **info (`i`) popover** in Settings → Cache shows ready-to-copy -`curl -X PURGE` and `curl -X BAN` examples in English and Polish, including -the explicit `BAN /` confirmation. These public methods authorize by the -source IP/CIDR only; they do not accept a browser admin session as a bypass. -See [security audit](SECURITY_AUDIT.md) for reverse-proxy trust and tests. - -## Static-response transformation - -`transform` is a formal identity-response stage between the upstream response and shared -cache storage. `transform.enabled` is the global kill switch, but every vhost must also -set its own `transform.enabled=true`; vhosts stay disabled by default even when the global -engine is enabled. Global defaults enable JavaScript and CSS transformation and leave -HTML, SVG and JSON off. `extensions` controls extension-based detection and -`max_body_bytes` (1 KiB..32 MiB, 2 MiB default) bounds aggregation/decoding. - -The pipeline is `origin -> decode supported Content-Encoding -> transform -> cache -identity representation -> client compression`. JavaScript/CSS can be enabled normally; -HTML minification, SVG optimization and JSON compaction are independent optional formats. -Recognition uses safe configured extensions and known Content-Type values. Binary/image -formats outside those explicit transform types are not transformed. - -Origin `Cache-Control: no-transform` always disables transformation. When bytes change, -RustProxy removes representation-specific length/digest/encoding metadata and creates a -new strong `rustproxy-*` ETag for the transformed representation. The original origin -ETag/Last-Modified are kept separately and used for origin revalidation, so a proxy ETag -is never sent upstream as though it validated the original object. The transformed -identity body is what enters cache, preventing repeated minification on every HIT. - -## Global TLS protocol and cipher policy - -`tls.policy` is the global baseline inherited by every TLS host. It controls TLS 1.2, -TLS 1.3 and optional rustls cipher-suite names. An empty `cipher_suites` list uses the -ring provider defaults. Settings shows both the currently effective cipher suites and -the complete selectable suite inventory. At least one protocol version and -one compatible effective suite are required. This policy is also used by administration -HTTPS. HTTP/3 requires TLS 1.3 and a TLS 1.3 suite. Vhosts can narrow TCP TLS with -`tls_policy`; when a vhost has HTTP/3 enabled on a selected H3-capable listener it must -match the global policy because QUIC TLS is shared per UDP listener. Available suite names are exposed in authenticated status for -the Settings editor. - -## Listeners: saved intent, not hot rebinding - -When an HTTP, HTTPS or administration address was explicitly supplied by systemd/CLI -(or the corresponding service environment variable), that exact startup bind is a -process contract: the panel shows it as locked and configuration updates cannot remove, -disable or change it. If an address was not explicitly supplied at startup, listener -entries remain freely addable/removable/editable subject to normal validation. - -`listeners.endpoints` is the only persisted frontend socket configuration. Each -endpoint has a stable id/name, an informational scope label, an enabled flag and one -primary binding containing IP:port, TLS, HTTP/2 plus its optional lifecycle settings, and optional HTTP/3 settings. Optional -`additional_addresses` reuse that same transport policy on up to 15 further addresses. -When more than one address is configured every address must be explicit; `0.0.0.0` and -`::` are rejected so a multi-IP selection cannot silently mean every interface. IP/port -and transport capability belong here, not inside a vhost. IPv6 uses `[address]:port`. - -A vhost references endpoints through `frontend.listener_ids`. Listener changes are -restart-required because they alter TCP/UDP sockets or TLS ALPN capability. The -listener editor owns the HTTP/2/H2C and HTTP/3 enable/disable switches. An H2/H2C row -links to **HTTP connections -> HTTP/2** for idle, absolute-lifetime and graceful-drain -limits; an enabled H3 row links to **HTTP connections -> HTTP/3** for advertisement, -connection, stream and idle-timeout limits. Both tuning pages are listener-scoped and -do not own protocol enablement. Vhost H2/H3 switches only narrow the protocols already -enabled by the selected endpoints; they never create a protocol that Settings disabled. - -The administration listener is independent and cannot share a frontend port. -Bindings must be valid local unicast/wildcard addresses; conflicting overlapping -endpoints are rejected rather than silently widened. - -## Server and other client headers - -`server_header.mode` is `preserve`, `hide`, or `custom`. Fresh installations default to `custom` with the exact value `RustProxy`; existing saved installations retain their configured policy. Custom values are -nonempty, bounded to 256 bytes and validated against header injection. Preserve -means retain the result of origin/global/route rules; it does not add a missing -header. Hide/custom take final precedence, including proxy-generated errors. - -Global and route `headers.response` apply to client responses; `headers.request` -apply only upstream. Each scope/direction supports up to 32 ordered set/append/remove -rules. Remove has no value. Global rules precede route rules. Multiple Set-Cookie -values remain distinct. Admin-session cookie names are never forwarded or set by -public proxy rules. Transport, framing and trusted forwarding/identity fields are -protected. Cache diagnostic fields are owned by the cache controls, not generic rules. - -Global response rules also cover generated errors; route-specific rules apply only -after an origin route response exists. Client policy changes use hot configuration -publication and invalidate the RAM cache. In-flight exchanges retain their old -snapshot; no mixed-version configuration is applied midway through a response. - -## Compression - -`zstd`, Brotli and gzip default ON; deflate defaults OFF. `min_bytes` defaults to 1024 -(valid 32..1048576). The streaming middleware keeps its default content-type safety -predicate plus the RustProxy minimum-size predicate. Unknown-length eligible bodies can -be streamed without aggregating the complete response merely to discover its size. - -RustProxy parses `Accept-Encoding` quality weights before invoking the encoder. Client -`q` values win. When two or more enabled codecs have equal quality, the deterministic -server preference is `zstd -> br -> gzip -> deflate`. An existing `Content-Encoding` is -always passed through and never recompressed. `no-transform`, request ranges/Content-Range, -SSE, upgrades, HEAD/1xx/204/304 and explicitly skipped internal responses remain excluded. -`Cookie`, `Authorization`, `Set-Cookie`, `Cache-Control: private` and `no-store` do not by -themselves disable compression; cacheability and dynamic compression are separate policies. -Applications that place secrets and attacker-controlled reflection in the same compressed -response can disable compression at the vhost or Web Service scope when their threat model -requires BREACH-style mitigation. - -The global Response panel selects available codecs and the minimum size. Each ordinary -vhost and Web Server service may inherit, enable or disable dynamic compression and independently -inherit/enable/disable zstd, Brotli, gzip and deflate. Docker vhosts intentionally expose only one -Compression checkbox: when enabled they use the globally enabled codec set and priority, with no -per-Docker-vhost codec selectors. Global codec switches remain the hard ceiling: a local `true` -override cannot enable a codec disabled in the global Response panel. `min_bytes` remains global. - -`Vary: Accept-Encoding` is added when compression is negotiated; content length is removed -for the transformed body. Strong ETags are weakened, and identity-body content digests and -`Accept-Ranges` are removed. Encoders use their fastest preset; there is no panel -compression-quality slider in this release. - -## Diagnostics and units - -Samples run off the traffic path roughly every two seconds. CPU and network rates -need two successful samples. Host CPU is aggregate; process CPU uses 100% per busy -logical CPU and can exceed 100%. Neither is normalized to a container CPU quota. -Host memory is MemTotal minus MemAvailable. RSS/FDs/threads and process I/O come -from procfs. Network rates sum non-loopback interfaces in the process network -namespace, not just RustProxy traffic. Load average is OS scope, not latency. - -The process's cgroup v2 memory files are read at the standard mount location when -available. Linux keeps the existing `/proc`/cgroup metric names, units and behavior. -FreeBSD/OpenBSD/NetBSD use native system interfaces where the same metric is available; -unknown or unsupported values remain null rather than being fabricated. Snapshot timestamps and sample age are shown; -cache counters and proxy request metrics are separate from OS resource metrics. -The dashboard history is browser-local and resets on reload/sign-out. No time-series -database, disk utilization chart or Windows/macOS OS sampler is implemented. - -## HTTP/2 compatibility and per-vhost settings - -HTTP/2 lifecycle settings are listener-scoped under `listeners.endpoints[].binding.http2_settings`: - -- `idle_timeout_seconds` (default 300) retires only a truly unused H2 connection; active streams and current frame I/O postpone it. -- `max_connection_lifetime_seconds` (default 0) is an independent absolute lifetime; `0` disables this limit. -- `drain_timeout_seconds` (default 10) bounds how long accepted streams may finish after retirement begins. - -Idle and lifetime retirement both send GOAWAY, stop accepting new streams, drain already accepted streams and then close TCP. RFC 8441 WebSocket/H2 Extended CONNECT streams are counted as active until their response body/tunnel ends. The same settings apply to TLS H2 and cleartext H2C on that named listener. - -For backward compatibility, the top-level `http2` object is still accepted as the fallback for an H2/H2C listener that has no explicit `binding.http2_settings`. The structured UI writes explicit per-listener settings; operators should treat the top-level object and `PUT /api/config/http2` as legacy compatibility surfaces rather than the preferred configuration path. - -A named TLS listener with `binding.http2: true` advertises H2 capability and always -retains HTTP/1.1 fallback. `binding.http2: false` makes that listener HTTP/1.1-only. -Cleartext H2 uses `binding.h2c: true`. Changing listener transport capability or H2 lifecycle settings requires restart. - -A vhost can additionally set `http2_enabled: false`. RustProxy reads TLS ClientHello -and SNI before committing to ALPN, so a vhost that disables H2 gets HTTP/1.1-only -ALPN even while another hostname on the same listener keeps H2. `http3_enabled` is -the equivalent per-vhost H3 opt-out. The panel enables these switches only when a -selected listener provides the protocol. No failed application request is replayed -to downgrade. - -Vhosts have their own backend pool, health/keepalive/affinity controls, ACL and -security/custom response headers. See VHOSTS.md. Global Server policy still takes -final precedence. Per-vhost compression can filter individual globally enabled codecs. - -The reported `SizeAbove(u16)` compilation error is removed by a custom u64 predicate, -not a cast or panic-producing conversion. Compression thresholds retain the full -32..1048576-byte setting range. Known body length/Content-Length is compared exactly; -unknown streaming lengths are eligible without buffering the full body. Actual -compression execution still requires the compiler-enabled acceptance suite. - -## Management in 0.3.0 - -Settings contains a direct Security shortcut (accounts, sessions and host ACLs) -and a vhost shortcut (SSL, headers, cache and pages). Compact is the default density; -Dense and Comfortable change spacing/control sizes without browser zoom or logo -redesign. The preference is local to the browser, not a global proxy setting. - -Global Pages configures error overrides, direct-IP and unknown-host responses. -See MANAGEMENT.md for inheritance, allowed statuses and security implications. -Access logs have moved out of the dashboard into a separate modal/docked window. -Filters stay above the scrollable rows; counts and exports use the same selection. - -## Named listeners, forwarding and diagnostics (0.4.0) - -As of 0.8.0, the vhost editor no longer asks for a free-form listener IP. Configure -listener IP/port/protocol once in this Settings section, then select named endpoints in -the vhost. The pre-production inline/global listener format is no longer accepted. This separates -socket intent from host routing and avoids duplicating listening IP configuration across -vhosts. - -`listeners.endpoints` is the only proxy-listener configuration. Each record has id, -name, scope, enabled and a binding with address, tls, http2, optional http2_settings, h2c and optional http3. Scope -is only a label. A vhost uses `frontend.listener_ids`; every reference must exist. -Conflicting protocols on shared sockets are rejected. -Changes to effective transport plans need restart, including H2 lifecycle and H3 limits. The UI -shows saved named entries and actual bound transports separately. - -`forwarding` controls trusted CIDRs and forwarded-proto acceptance. -`diagnostic_headers` exposes only `request_id` (false initially). Vhosts can override the diagnostic -policy and Server policy; absent/null means inherit. Runtime/cache/static/error -responses pass through the same final header policy. - -See INTEGRATIONS_040.md and HTTP3.md for complete JSON, limits and security details. - - -## 0.5.0 changes (override earlier baseline examples) - -A named TLS listener with omitted http3 uses default H3 limits. Explicit null is -persisted as disabled. Overlapping named sockets must match protocols AND H3 limits; -the error identifies owners and remedies. A changed binding requires restart, including -UDP protocol/limit changes. Check UDP port availability before upgrading. - -The diagnostic control is `request_id` (false by default). The removed hide_mock_origin field is not accepted; use normal response-header removal -for X-Mock-Origin. Existing custom Server values remain unchanged. - -Cache decision/object inspection is under Cache, separate from Overview. Basic -manual certificate issuance is under Certificates; TSIG is never a configuration -field. See CACHE_DIAGNOSTICS.md and ACME_RFC2136.md for exact scopes and limits. - -## 0.6 navigation and exporter - -The Settings page now has eight switching cards: Listeners, Response, Cache, -Headers, Forwarding, Pages, Prometheus and Security. Unsaved values remain in -place while switching; switching alone never writes configuration. Security is -not a separate sidebar item. Use arrow keys/Home/End in the tab bar. - -The Prometheus card controls enabled, bind IP/port, URL path, client CIDRs, -bearer-token environment variable name and optional collectors, including the bounded -Traffic analytics collector. Only the variable name is exported; supply the actual secret to the service separately. See -PROMETHEUS.md for immediate revocation, restart rules and scrape configuration. - -## Stream Dashboard cards (0.32.0) - -Under **Settings → Appearance → Dashboard layout**, the existing Show/Hide and Up/Down card controls now include Stream overview, traffic, sessions and backend health. They are opt-in and account-scoped, saved through the existing `/api/preferences` flow. Legacy 12-card orders migrate in memory with all four Stream cards hidden. They use the existing status/SSE stream and stop computing their charts while hidden; no backend monitoring or listener settings are changed when toggled. An explicitly enabled Stream card stays on Overview and shows a neutral no-data state when no Stream is active. Overview details and chart legends stay visible inside the cards (without hover tooltips). Appearance Stream card descriptions use browser-native `title` hints on hover, with the same localized descriptions available to assistive technology through `aria-description` on the visibility buttons; they no longer add floating boxes or horizontal scrolling. - -## Full Overview layout and additional metrics (0.32.3) - -Open **Overview -> Arrange cards visually** (or **Settings -> Appearance -> Dashboard layout -> Visual layout editor**) to change **all 49** dashboard blocks: 30 previous tiles, 12 new optional charts, three large live charts, three diagnostic panels and the backend table. Drag tiles/sections, use arrow buttons for keyboard access, or hide and re-enable individual blocks. Large sections retain their own original charts, controls and live data after being moved. The responsive six-column desktop view scales to narrower widths. Visibility and order are account-scoped; they never alter backend behavior. - -**Default view** loads the factory order and visibility into the editor **without saving**; **Save layout** applies it. **Reset layout** asks for confirmation, then persists the same default order and visibility in one preferences write. **Cancel** preserves the current layout. All 12 added charts are hidden until explicitly enabled and use a maximum of 30 status samples without additional HTTP polling. They cover process CPU/RSS/file descriptors, connections, WebSocket tunnels, TLS handshake failure and request-error rates, cache HIT share and HOT RAM usage, incoming HTTP/2 request share, mean response duration, and unhealthy backends. Unknown and first-delta measurements show `-`; response time is a mean, not a percentile, and HTTP/2 share is **frontend** traffic rather than negotiated upstream ALPN. - -## HTTP backend Overview cards (0.32.3) - -**Settings -> Appearance -> Dashboard layout** now contains fourteen opt-in -HTTP origin cards: **Backend health**, **Active backend requests**, **Backend -errors**, **Health probe RTT**, **Backend traffic**, **Configured backend -modes**, **Backend request rate**, **Backend capacity**, **HTTP 5xx**, **Backend -timeouts**, **Upstream connection errors**, **Backend retries**, **Backend -queue pressure**, and **Backend connection pools**. All are hidden by default on new and migrated administrator profiles; -card visibility and ordering use existing per-account `/api/preferences` fields. -They do not affect routing, health checks, upstream keep-alive or listeners. - -The cards reuse `backend_pools` already included in the live status payload: - -- Health separates up/down/checking and draining/disabled members. Active - requests is a sum of in-flight pool member exchanges, not open TCP sockets. -- Errors show lifetime member error counts and a sampled errors/s graph. Traffic - uses the positive differences of per-member in/out byte counters; resets and - newly added backend members cannot generate an artificial rate spike. -- Health probe RTT is the mean last observed healthy probe time, **not** client - end-to-end latency. It shows no data when health probes are unavailable. -- Configured backend modes distinguish saved `http1`, `auto`, compatible `http2` - and strict `h2` values, and count H2C cleartext members. This is **not an observed - H1/H2 negotiation rate**: TLS ALPN can select H1 for Auto and compatible HTTP/2. -- Backend request rate uses started-request counter deltas, with per-member - baselines after a restart or config change. Backend capacity compares active - requests against finite per-member concurrency limits; unlimited members are - excluded and a neutral state is shown when no such limit is configured. - -The six additional diagnostics use pre-existing `backend_pools.members` counters: -HTTP 5xx responses, all timeout classes, combined connect/DNS/TLS/I/O failures, -retry attempts and successes from the existing safe retry policy, queue timeouts -and capacity rejections, and Hyper client-generation rotations and age. Rotations -are **not TLS handshake counts**. Rates are based on per-member positive counter -differences; newly added or reset members cannot create arbitrary spikes. - -To arrange the actual Overview cards visually, click **Arrange cards visually** -on Overview or **Visual layout editor** in Settings -> Appearance -> Dashboard -layout. The modal displays copies of the existing telemetry cards; drag to -reorder, or use the move arrows for keyboard-only input. Hide/show tiles in -the modal and select **Save layout** once to persist order and visibility in -the existing `dashboard_widgets` and `dashboard_hidden_widgets` preferences. -Cancel/Escape discards the draft. The existing Appearance visibility and move -buttons remain available. No extra API endpoint, refresh loop, or database -migration is needed. - -TLS ALPN selects H2 or H1 only when opening a **new TLS connection**, not for -each request. Hyper already pools reusable connections per compiled backend; -subsequent requests share the negotiated connection (H2 multiplexing when -possible). Existing bounded pool recycling (`pool_recycle_requests` and -`pool_recycle_seconds`), configured idle timeouts, and reopening failed -connections naturally re-evaluate TLS capabilities. A backend-global protocol -cache or scheduled H2 probe would be unsafe for origins with multiple DNS/peer -capabilities and would add network work. No traffic replay/downgrade on -post-request errors was added; strict H2, H2C, H1 and WebSocket separation -remain intact. - -The sample charts are browser-local, bounded and reset on a changed config -revision, logout or source unavailability. They are not computed while hidden. -No additional polling, SQLite schema migration or new administrator endpoint -is introduced. Existing Stream widgets remain independent. - - -## Dynamic DNS / DDNS client ACL (0.32.5) - -In **ACL profiles**, add an **Allow** rule and select **DNS / DDNS** instead of -IP/CIDR. Enter the hostname without a URL, port or wildcard, for example: - -```text -allow myoffice.example.ddns.net -``` - -Apply the profile to the desired vhost. Use the **Allow exceptions** preset -(default deny) to admit only resolved addresses (and any other explicitly -allowed IP/country exceptions). Host policies and inline vhost/location ACLs -accept the same rule format. A/AAAA records are refreshed every 60 seconds, -with immediate refresh on published configuration changes. No request-path DNS -lookup is performed. The initial state and failed, empty, oversized, or expired -DNS results **do not authorize any client IP** for that DNS rule. Only -**allow** rules may contain DNS names; `deny example.com` is rejected. - -Limits: 128 distinct names across active ACLs, 32 returned IPs/name, four- -second lookup timeout and eight parallel DNS lookups. Successful answers expire -after 180 seconds if the refresh task stalls. DNS changes may take up to the -next poll interval to take effect. The chosen DDNS provider controls which IPs -receive access; always trust the provider and protect its credentials. Existing -IP/CIDR ACLs and country rules are unchanged. No new admin access path or -backend origin DNS exceptions are created by this feature. diff --git a/docs/STREAMS.md b/docs/STREAMS.md index b783b1f..4497d95 100644 --- a/docs/STREAMS.md +++ b/docs/STREAMS.md @@ -1,127 +1,28 @@ -# TCP/UDP Stream pools (v0.32.0) +# TCP and UDP Streams -Layer-4 forwarding runs on dedicated TCP/UDP listeners and never traverses HTTP Vhosts, ACL, cache, headers, compression, TLS termination or authentication. The existing **Streams** page provides a card/modal editor and per-Stream RX/TX chart. Changes to Stream listeners and backend pool configuration remain **desired state** until the next controlled RustProxy restart; saving Appearance preferences does not restart the proxy. +The **Streams** workspace forwards non-HTTP TCP or UDP traffic through dedicated sockets. It is separate from Vhost HTTP routing. Suitable examples include selected private TCP application ports and UDP protocols that need basic forwarding rather than HTTP features. -## Compatible configuration +## Configure a Stream -The legacy document still works without modification: +1. Open **Streams → Add** and choose `TCP` or `UDP`, a unique name/ID and enabled state. +2. Set the listening IP/port and an upstream address, or configure a backend pool. +3. If using several backends, select weights, balancing, health checks and draining appropriately. +4. Configure connection/session limit, TCP connect timeout and idle timeout. +5. Save, restart RustProxy if the listener change requires it, then test with a real client and inspect Stream metrics. -```json -{"streams":[{"id":"ssh","name":"SSH relay","enabled":true,"protocol":"tcp","listen":"0.0.0.0:2222","upstream":"10.0.0.20:22","connect_timeout_seconds":5,"idle_timeout_seconds":300,"max_connections":512}]} -``` +The source model supports a legacy single-upstream configuration or a backend list (up to the validated per-host limit), with pool-based load balancing and health observations. Do not assume HTTP health-check response codes can be applied to a UDP-only application: use the probe options actually supported by the Stream type. -Without `backends`, `upstream` is treated as one enabled backend with ID `primary`, weight 1, no active health probe and detailed metrics disabled by default (opt in explicitly). The legacy field is retained in serialized configuration and is set to the first backend address when a pool is explicitly configured. Existing listeners continue to require restart on change. +## Behavior and boundaries -A TCP pool can be configured as follows (the same backend fields apply to UDP): +- **TCP** forwards a bidirectional byte stream. The proxy does not interpret application messages or terminate application TLS. +- **UDP** maintains bounded per-client forwarding sessions; datagrams and inactivity timeouts behave differently from TCP connections. +- **Not available:** HTTP Vhost routing, Host/path matching, HTTP cookies, WebSocket logic, shared HTTP cache, HTTP Basic/Tinyauth, HTTP header rules or frontend HTTP/3/TLS termination. Protect these endpoints with firewall rules and protocol-specific backend authentication/encryption. +- Listeners and backend IP/ports must be concrete, valid and nonconflicting with other RustProxy/admin/exporter sockets. Resource limits and network policy still apply. -```json -{ - "id":"ssh", "name":"SSH pool", "enabled":true, "protocol":"tcp", - "listen":"0.0.0.0:2222", "upstream":"10.0.0.20:22", - "balance":"round_robin", "metrics_enabled":true, - "connect_timeout_seconds":5, "idle_timeout_seconds":300, "max_connections":512, - "backends":[ - {"id":"node-a","name":"Server A","address":"10.0.0.20:22","enabled":true,"drain":false,"weight":2,"max_sessions":256,"metrics_enabled":true,"health":{"mode":"tcp_connect","interval_seconds":10,"timeout_ms":1000,"rise":2,"fall":2}}, - {"id":"node-b","name":"Server B","address":"10.0.0.21:22","enabled":true,"drain":false,"weight":1,"max_sessions":256,"metrics_enabled":false,"health":{"mode":"tcp_connect","interval_seconds":10,"timeout_ms":1000,"rise":2,"fall":2}} - ] -} -``` +## Status and testing -`balance` is `round_robin` (default), `least_connections` (TCP), or `least_sessions` (UDP). The least-load variants use an active-assignment-to-weight ratio. Each new TCP connection selects one backend for its entire connection; a UDP client session selects once and remains pinned for its lifetime (idle timeout). `drain: true`, `enabled: false`, `Unhealthy` state, or a reached `max_sessions` prevents new assignments. In-flight TCP relays and UDP sessions retain their selected backend; a controlled process restart still follows the existing shutdown grace period and may end in-flight sessions. +Stream views/Overview include sessions, connections, traffic, rejected/errors and backend health/capacity when enabled. Counter reset or no-data state after service restart is normal. -## Health monitoring +The source package includes `scripts/start_stream_backends.sh`, `scripts/test_stream_tcp.py` and `scripts/test_stream_udp.py` for isolated test networks. Their mock selftests alone do **not** establish that RustProxy forwarding works; run end-to-end client traffic through the configured Stream listener. -Each backend has independent `enabled`, `health` and `metrics_enabled` controls. Disabled and draining backends can continue independent health monitoring if probes are explicitly configured; it does not get new sessions. Probe concurrency is globally limited to 16, at most one health task per enabled/configured backend, with lifecycle tied to the listener cancellation token. Timeouts, probe intervals and rise/fall thresholds are bounded by validation. TCP checks use a timed TCP connect without application payload. - -UDP checks cannot use a local UDP `connect()` as evidence that the remote service exists. For a validated active UDP probe define both `payload_hex` and an exact, nonempty `expect_hex` response (up to 128 bytes each), e.g.: - -```json -{"mode":"udp_probe","interval_seconds":10,"timeout_ms":1000,"rise":2,"fall":2,"payload_hex":"50494e47","expect_hex":"504f4e47"} -``` - -**Do not enable UDP probing without the service owner's approval.** Unknown application protocols must leave checks `off`; `udp_probe` without an expected reply is `Unverified`, sends no datagrams and does not report Healthy. A backend without active checks is `Unverified` (routing still allowed), while probed backends show `Checking`, `Healthy`, `Unhealthy` or `Disabled`; hysteresis prevents state flapping. An Unhealthy backend stops receiving new sessions, but no ongoing TCP connection is reassigned. If the pool has no eligible backend, new sessions or datagrams are rejected and the legacy per-Stream `rejected` counter advances. - -## Telemetry and Dashboard - -The existing `/api/status` response and `/api/status?view=live` SSE/fallback add a bounded `backends` array under `streams.metrics[stream_id]`. It includes stable configured IDs, status, active sessions, health counts/last errors and (if enabled) optional completed/created/assignment, bytes, packets and error/reject metrics. `bytes_in` remains client → upstream, `bytes_out` upstream → client. A UDP datagram rejected before a session is established remains counted in the original per-Stream incoming counters, not backend delivered-byte counters. - -In the editor, **Stream → Detailed metrics → Default (disabled)** means the absence of a Stream override resolves to `false` (it does **not** inherit from Prometheus). At the backend level, **Use Stream setting** resolves to that Stream's value; backend **Enabled**/**Disabled** override it. Prometheus retains `include_streams` as the separate global export gate. `metrics_enabled` defaults to `false` for detailed per-backend accounting (no new overhead on legacy streams). Stream-level `true` enables the default for its backends; backend-level `true`/`false` explicitly overrides the Stream value, while `null` inherits. This choice never changes session routing or health checks. Basic per-Stream counters and backend active gauges stay available. Backend label cardinality is bounded to IDs declared in config, never client IPs. History is a limited browser-memory window only; metrics are not persisted as periodic SQLite samples. - -Under **Settings → Appearance → Dashboard layout**, toggle/reorder Stream overview, traffic, sessions and backend-health cards alongside existing telemetry cards. All Stream cards default hidden for existing accounts, including migrated 0.31.x layouts. An explicitly enabled card stays visible even when no Stream is configured, enabled or running; it displays a neutral **No stream data** state in that case rather than ignoring the Appearance setting. They use the existing API status/SSE/fallback and one dashboard refresh scheduler. Hiding a card avoids its chart computations, and changing visibility does not alter Stream runtime, existing widgets or global Prometheus settings. Widget descriptions in Appearance use native `title` tooltips on hover and accessible button descriptions (`aria-description`) instead of floating elements that expand the layout. On the main Overview page, secondary values and RX/TX chart legend are always inline under the metric/graph (not tooltips), with the original six-column desktop layout and taller tiles for readability. - -## Safety and operational limits - -At most 128 Stream hosts and 32 backends per host, weights 1–16, 1–65535 stream/session concurrency and optional per-backend session caps. Validated concrete backend IPs and nonzero ports are required. Duplicate backend IDs, local listener loops, conflicting transport health modes and invalid probes are rejected before configuration is published. UDP client queues are bounded at 64 datagrams. The existing listener restart indicator remains authoritative, and no new public administration routes or polling tasks are added. - -## Standalone TCP and UDP forwarding tests - -The ready-to-use launcher and individual TCP/UDP test clients are all in `scripts/`. -Start four loopback-only mock backends (TCP 9101/9102; UDP 9201/9202): - -```bash -./scripts/start_stream_backends.sh -# Optional, only on an isolated test network: ./scripts/start_stream_backends.sh 0.0.0.0 -``` - -Point your corresponding Stream backend pools at these ports, restart RustProxy -(the Stream configuration's existing restart requirement), then run: - -```bash -python3 scripts/test_stream_tcp.py client --port 2222 --sessions 32 --messages 5 --min-backends 2 -python3 scripts/test_stream_udp.py client --port 5353 --sessions 32 --messages 5 --min-backends 2 -``` - -Stop the launcher with `Ctrl+C`. Its four children are stopped together. If you -prefer launching backend peers separately or need different ports, use the examples below. - -These are **black-box clients for a real running RustProxy** plus small, intentionally -loopback-only mock services. Do not aim them at live/production backends. Use -separate terminals for each mock, and configure the Stream editor with these -backend addresses. **Restart RustProxy after modifying Stream backends** (the -existing Stream lifecycle still applies). - -TCP example (Stream listener `127.0.0.1:2222`, backend pool `127.0.0.1:9121` and -`127.0.0.1:9122`): - -```bash -# Terminal 1 -python3 scripts/test_stream_tcp.py server --port 9121 --backend-id node-a -# Terminal 2 -python3 scripts/test_stream_tcp.py server --port 9122 --backend-id node-b -# Terminal 3 - after enabling the TCP Stream listener in RustProxy -python3 scripts/test_stream_tcp.py client --port 2222 --sessions 32 --messages 5 --min-backends 2 -``` - -UDP example (Stream listener `127.0.0.1:5353`, pool `127.0.0.1:9131` and -`127.0.0.1:9132`): - -```bash -# Terminal 1 -python3 scripts/test_stream_udp.py server --port 9131 --backend-id node-a -# Terminal 2 -python3 scripts/test_stream_udp.py server --port 9132 --backend-id node-b -# Terminal 3 - after enabling the UDP Stream listener in RustProxy -python3 scripts/test_stream_udp.py client --port 5353 --sessions 32 --messages 5 --min-backends 2 -``` - -Each client verifies exact round-trip payloads and logs per-backend session -assignments. TCP verifies the selected backend stays unchanged through several -messages on one connection; UDP verifies the same over datagrams from one -client socket. Use `--min-backends 1` for a single backend, drain, or failover -case. Compare the reported assignments with Stream metrics and Prometheus. -`--parallel`, `--sessions`, `--messages`, and `--timeout` control test load. -UDP mock services also respond to the **explicit** health probe `PING`/`PONG` -(`payload_hex: 50494e47`, `expect_hex: 504f4e47`); do not apply this payload -arbitrarily to real application servers. - -To check the scripts themselves *without RustProxy*, run: - -```bash -python3 scripts/test_stream_tcp.py selftest -python3 scripts/test_stream_udp.py selftest -``` - -Selftests only exercise the Python mock peer/harness; they do not verify the -RustProxy forwarding path, balance algorithms or real health check lifecycle. -Run the `client` cases against your compiled proxy and manually repeat after -disabling, draining, or stopping one mock backend to verify failover behavior. +If clients cannot connect, inspect listen IP/port, host firewall, TCP vs UDP selection, routing to the chosen backend, backend health and stream logs. See [Security](SECURITY.md) and [Monitoring](MONITORING.md). diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md deleted file mode 100644 index dc6d564..0000000 --- a/docs/TEST_REPORT.md +++ /dev/null @@ -1,115 +0,0 @@ -# RustProxy 0.33.8 verification report - -This report accompanies the performance and compatibility work on the supplied -0.33.7 source archive and executable. All measurements use disposable local -state. Production data, certificates, configuration and installed services are -not modified. Raw results and exact binary hashes are supplied in the separate -verification archive. The live runners are in `scripts/performance/`. - -## Scope and acceptance - -The target is reduced work per uncached reverse-proxy request and per local -static-file response while retaining routing, security, cache policy, streaming, -observability and administrative functionality. The LB panel, database schema, -default TLS policy, cache defaults and bounded connection recycling are unchanged. -The performance comparison requires the same compiler, locked dependencies, -release profile, CPU allocation, payloads and client configuration for the -unmodified source and the candidate. The supplied executable is also retained as -an independent compatibility baseline. - -Acceptance has separate layers: compilation and Rust tests; source/SQL/upgrade -checks; actual HTTP process tests; actual TLS/H2/H3/WebSocket tests; repeated -before/after/nginx throughput measurements; and a bounded mixed-traffic soak. -A passing source assertion or simulated DOM check is not a live runtime result. -Passing this matrix is not a claim that every possible deployment is covered. - -## Baseline findings - -The first candidate Rust run completed 658 tests successfully, with 22 failures -and one explicitly ignored formatting microbenchmark. An unmodified-source run -reproduces the old failures; the audit records each stale fixture and each actual -defect separately. The raw initial failed runs remain in the evidence archive. -Tests that expected obsolete behavior are corrected to exercise the currently -documented contract; security, cache invalidation and transport guarantees are -not weakened to obtain a passing result. - -The initial live HTTP matrix gave identical results for the supplied executable -and rebuilt 0.33.7: 145 passes, three failures and one skipped optional Brotli -decoder case. All three failures concerned local HTTP/1 file-body completion: -normal responses were recorded as cancelled, and full or empty files failed to -publish a cache entry. The candidate uses a bounded local-file body with accurate -end-of-stream reporting. Arbitrary upstream bodies retain their original trailer -and end-of-stream handling. - -The supplied executable passed 111 actual transport assertions covering verified -TLS, H2, H3/QUIC, stream multiplexing, uploads, cache behavior, ranges, validators -and HTTP/1 WebSocket data/control frames. The same runner is used for the final -candidate. The H3 client adapter accounts for HEAD semantics without disabling -GET body-length validation. - -## Compatibility checks - -The Rust suites exercise cache privacy, Vary and scheme separation, publication -generation barriers, stale access rules, disk tiers, header policies, ACL and -SSRF decision paths, timeout and permit lifetime, actual codecs, filesystem -bounds, symlink policies, range/conditional handling, SQL-backed administration, -TLS configuration, loopback upstream H1/H2 and raw H2 listener behavior. Stream -tests include real TCP/UDP peers. PHP tests use a deterministic FastCGI protocol -peer rather than an installed PHP-FPM application. - -The HTTP process runner checks exact response bytes and hashes, streaming before -the final upload chunk, non-replayable POST failures, client cancellation, -configuration reloads, scoped headers and security policy, administration -session/CSRF, cache OFF/ON and privacy behavior, compression and transformations. -The transport runner uses actual local TLS and QUIC connections and trusts the -fixture certificate explicitly. - -History-specific tests retain exact log fields, bounded chronological retention, -drop accounting and delivery to live subscribers and configured sinks during -forced history-lock contention. Cloning and destruction of evicted entries occur -outside the history mutex. This preserves the existing best-effort policy; -saturation measurements must still disclose actual history drops. - -SQL checks apply all ten migrations twice, exercise named dashboard presets and -account isolation, and verify the 16-preset bound and cascade behavior. The source -upgrade test uses disposable installations to check the stopped-process guard, -held lock rejection, data/configuration preservation, managed source replacement, -backup creation and symlink rejection. Existing JavaScript checks retain their -status as simulated UI checks; no browser acceptance is inferred from them. - -## Reproduction - -Use the project entry points, which set the private process umask required by -key-store and certificate fixtures: - -```bash -./dev.sh check -CARGO_INCREMENTAL=0 ./dev.sh test -python3 scripts/tests/dashboard_presets_sql_test.py -python3 scripts/tests/upgrade_performance_test.py -``` - -The isolated validation environment uses Rust 1.99.1 with the original locked -dependency versions, nginx 1.26.3 and wrk 4.2.0. See -`scripts/performance/README.md` for live regression, transport, profiling, -benchmark and soak commands. Supply explicit executable paths and new output -directories; preserve failed runs as evidence. - -## Measurement boundaries - -Throughput measurements use HTTP/1.1 keep-alive, cache OFF, identity encoding, -1040-byte, 64-KiB and 1-MiB objects, separate server/origin/client CPU sets, -interleaved variants and excluded warm-up. Resource samples retain CPU time, -RSS, file descriptors, generator errors and application drop counters. Medians -use all scheduled repetitions. Closed-loop wrk latency is not an open-loop SLA. -Syscall profiles are collected separately from timed throughput runs. - -Remaining deployment-specific checks include a 24-hour production-like soak, -remote-network latency and loss, actual PHP-FPM applications, external Tinyauth, -DNS rebinding against a real resolver, and HTTP/2 or HTTP/3 WebSocket tunnels. -The measured same-host comparison does not establish internet-facing capacity. - -Final executable-specific outcomes, run counts, percentages and resource costs -are recorded in the separately generated performance/evidence report after the -release build. This embedded document describes the test contract and baseline; -it does not substitute planned work for a completed measurement. diff --git a/docs/TRANSFORM.md b/docs/TRANSFORM.md deleted file mode 100644 index e351fa6..0000000 --- a/docs/TRANSFORM.md +++ /dev/null @@ -1,55 +0,0 @@ -# Static-response transformation - -RustProxy 0.22.3 retains static transformation between origin decoding and shared-cache -storage. A transformed identity representation is cached once and downstream compression -is selected later for each client. Per-vhost and Docker-discovery transform opt-in remain -disabled by default even when the global engine is available. - -## Web Server origins (0.29.4) - -Static-file and PHP-FPM responses selected through Web Server vhost sources -use the same optional vhost transform stage as reverse-proxy backends. -The normal rules still apply: supported MIME/extension and response encoding, -known bounded body size, memory/concurrency limits, and `no-transform` -exclusions. Successful transformed representations are eligible for shared -cache storage under the original/derived cache privacy policy. Downstream -compression is still applied separately. - -## Administration dashboard - -The main **Transform** workspace reads `GET /api/transform` and exposes in-memory runtime -statistics globally or per vhost: - -- responses examined, transformed, skipped/fail-open and failed; -- transformed cache HITs (served without running the transformer again); -- original, transformed and saved bytes before client compression; -- average transform time; -- applied transforms by JavaScript/CSS/HTML/SVG/JSON; -- bounded skip/fail-open reason counters; -- current process-wide temporary-memory reservation versus the configured budget; -- current active transform slots versus the configured concurrency limit; -- a per-vhost effectiveness table. - -Counters reset on process restart. They are diagnostic telemetry, not persisted cache -metadata. Resource usage is process-wide even when a vhost counter scope is selected. The -reservation is the transform limiter's conservative buffer estimate, not a second RSS -measurement. The Cache workspace separately marks stored transformed objects. - -## Cache diagnostics - -When `cache.expose_cache_status=true`, transformed cached representations are identified -with the RFC 9211 field plus an extension parameter: - -```http -Cache-Status: RustProxy; hit; ttl=556488; detail="cache_hit"; transformed -``` - -The field remains `Cache-Status`, not `X-Cache-Status`. HTTP/2 and HTTP/3 normalize field -names to lowercase on the wire. Origin-provided `Cache-Status` and `X-Cache-Status` values -are removed before RustProxy emits its own diagnostics. - -## Release status - -The dashboard/telemetry does not change the existing v0.22.3 release gate. JavaScript and -CSS still require mature parser-backed minifiers, and the complete Rust compiler/runtime -acceptance gate must pass before release acceptance. diff --git a/docs/TRANSPORT.md b/docs/TRANSPORT.md index 59d1fa0..4024083 100644 --- a/docs/TRANSPORT.md +++ b/docs/TRANSPORT.md @@ -1,223 +1,40 @@ -# Transport contract +# HTTP transport, TLS, HTTP/2 and HTTP/3 -This document describes the implementation contract of 0.22.3. It is not a record -of successful Rust execution; see TEST_REPORT.md for verification status. +RustProxy distinguishes **frontend** client protocols from the **upstream** protocol used to contact an application. Selecting HTTP/3 on a listener does not require an HTTP/3 backend. -## Protocol matrix +## Frontend listeners -| Connection | Implemented mode | -|---|---| -| Client to cleartext listener | HTTP/1.1 plus opt-in HTTP/2 prior knowledge and HTTP/1.1 `Upgrade: h2c`; WebSocket Upgrade on H1 and RFC 8441 Extended CONNECT on H2 when enabled per vhost | -| Client to TLS listener | HTTP/1.1 or HTTP/2 through ALPN; WebSocket uses H1 Upgrade by default or opt-in RFC 8441 Extended CONNECT on H2 | -| Client to HTTP/3 TLS listener | HTTP/3 over QUIC; RFC 9220 WebSocket Extended CONNECT only when a bound vhost explicitly opts in | -| Proxy to HTTP backend, `http1` | HTTP/1.1 | -| Proxy to HTTPS backend, `http1` | TLS with HTTP/1.1 | -| Proxy to HTTP backend, `http2` | HTTP/2 prior knowledge; the backend must support it | -| Proxy to HTTPS backend, `http2` | Prefer HTTP/2 through ALPN, retain HTTP/1.1 | -| Proxy to HTTPS backend, `auto` | ALPN HTTP/2 or HTTP/1.1 | -| Proxy to HTTP backend, `auto` | HTTP/1.1; no automatic h2c upgrade | -| Proxy to a WebSocket backend | HTTP/1.1 Upgrade over HTTP or HTTPS | +- **HTTP/1.1:** default plaintext HTTP and standard Upgrade-based WebSocket support where enabled. +- **HTTPS / HTTP/2:** TLS listeners may negotiate HTTP/2 and HTTP/1.1. Settings include H2 idle/connection-drain controls. +- **HTTP/3 (QUIC):** optional UDP transport on an enabled TLS frontend, using the listener certificate and supported H3 settings. The host firewall/NAT must pass UDP on the actual HTTPS port, not only TCP/443. Browsers can fall back to H2/H1 when H3 is unavailable. +- **H2C:** explicit cleartext HTTP/2 capability only where the listener or upstream policy enables it; it is not ordinary browser HTTPS. -Cleartext downstream HTTP/2 is an explicit listener opt-in. The same listener accepts both -the HTTP/2 prior-knowledge preface and a standards-oriented body-free HTTP/1.1 `Upgrade: h2c` -bootstrap. Upgrade requests must include `Connection: Upgrade, HTTP2-Settings`, exactly one -`Upgrade: h2c` and one valid base64url `HTTP2-Settings` field. RustProxy sends `101`, waits for -the client's normal HTTP/2 connection preface and initial SETTINGS frame, then reconstructs the -accepted HTTP/1.1 request as HTTP/2 stream 1 before handing later streams to Hyper's ordinary H2 -state machine. Body-bearing or malformed upgrade requests are rejected before `101`. HTTP/1.1 -remains available on the same cleartext listener. +Transport settings attach to **named listeners**, and a Vhost must select the appropriate listener IDs. Addresses locked by CLI/systemd startup cannot be rewritten from the panel. Changing a listener bind or startup-owned TLS option may require a restart. -Enabling WebSocket on a vhost keeps the HTTP/1.1 Upgrade path by default. `websocket_rfc8441` -and `websocket_rfc9220` are independent per-vhost opt-ins for HTTP/2 and HTTP/3 Extended -CONNECT with `:protocol=websocket`. HTTP/2 listeners advertise ENABLE_CONNECT_PROTOCOL when -serving H2; QUIC advertises it when required by the HTTP/3 transport. Routing still rejects -Extended CONNECT on vhosts that did not opt in. Ordinary CONNECT and other Extended CONNECT -protocols remain rejected because RustProxy is not an open forward proxy. RFC 8441 may bridge -to an HTTP/1.1 WebSocket backend or use a backend explicitly configured for HTTP/2. +## Backend protocol selection -Since 0.32.4, `preserve_host: true` is supported with `http1`, `auto`, `http2` -and strict `h2`. H1 carries the public Host; on H2 the outgoing `:authority` -follows that Host. The *network* destination and DNS/ACL policy stay pinned to -the configured backend URL. TLS SNI and certificate validation independently use -`tls_server_name` when supplied or the configured origin otherwise. -The client hostname also remains available via `X-Forwarded-Host`. HTTP health -probes are separate H1 requests and may need an explicit `health_check.host`. +| Backend mode | HTTPS origin | Plain HTTP origin | +|---|---|---| +| `http1` | HTTP/1.1 | HTTP/1.1 | +| `auto` | ALPN H2 or H1 | HTTP/1.1 | +| Legacy `http2` | Compatible ALPN H2/H1 | H2C | +| Strict `h2` | Requires H2 | Requires H2C | -For ordinary proxy traffic, an explicit `protocol=http2` backend is forwarded with an -HTTP/2 request version. Cleartext `http://` uses h2 prior knowledge. HTTPS keeps the -existing ALPN policy and offers HTTP/2 with HTTP/1.1 compatibility; RustProxy does not -replay a failed TLS request merely to downgrade protocols. +WebSockets normally use HTTP/1.1 Upgrade. HTTP/2/HTTP/3 client requests can still reach HTTP/1.1 backends when the selected bridge/path is supported; do not assume the frontend and origin protocol always match. HTTP/2 GOAWAY, H3 stream cancellation and retries are constrained to safe lifecycle cases. Use backend protocol telemetry and logs when diagnosing negotiation. -Balanced routes may perform bounded failover retry only for body-free `GET`, `HEAD` and -`OPTIONS`. Defaults allow one retry on connection failure, response-header timeout or -`502/503/504`, always selecting another currently healthy backend. Path-pinned routes, -WebSocket handshakes and write/body-bearing requests are never automatically replayed. +## TLS and trust -## DNS and upstream TLS +Public HTTPS requires a correct certificate with hostname coverage. Administration TLS is separate. Backend `https://` encrypts traffic, but **upstream certificate verification is opt-in by default** in the current backend model; enable it and specify CA/name where needed. Private/loopback addresses and routing to a local RustProxy listener require explicit risk overrides. -Only explicit HTTP/HTTPS backend origins are accepted; no credentials, path, query -or fragment are allowed. Client requests cannot choose an arbitrary upstream URL. +## Timeouts, request bodies and streaming -Each new DNS connection uses a guarded resolver. Every returned IPv4/IPv6 address -must pass the configured address policy, with IPv4-mapped IPv6 normalized for checks. -A mixed safe/unsafe response is rejected as a whole. Up to 32 answers and 16 OS -resolver workers are allowed. Validated answers go directly to the HTTP connector: -there is no second unvalidated DNS lookup before dialing. Existing pooled sockets -remain bound to their original peer until closed; they do not silently move when -DNS changes. New snapshots get new connection pools. +Idle/read/connect/response-header timeouts and backend connection pools are configurable. A response may stream after its headers have arrived; response-header metrics are not full-body duration. WebSockets and Streams have separate idle lifetimes and should be tested with the actual application. RustProxy rejects suspicious ambiguous request targets rather than normalizing them into potentially unsafe backend paths. -`allow_private` is explicit approval for eligible private/loopback addresses, not a -universal SSRF bypass. `address_allowlist` further restricts eligible addresses by -CIDR. Include both address families when a hostname legitimately resolves to both. -Link-local/metadata, multicast, unspecified and certain special ranges remain -blocked. Remote origins may use any valid backend port, including 9090. A target that -resolves to an actual/local RustProxy proxy, TLS or administration listener remains -blocked by default, including validated DNS answers. An administrator may intentionally -override only that self-target guard per backend with `accept_local_listener_risk: true`; -loopback/private targets still require `allow_private: true`. This opt-in can create a -proxy loop or expose the administration plane, so keep it disabled unless that route is -deliberate and separately access-controlled. For example, an intentional local backend -`http://127.0.0.1:9090` requires both `allow_private: true` and -`accept_local_listener_risk: true`; this only permits the connection and does not bypass -the administration listener's own Host/Origin/authentication policy. The special-range -policy is a basic safeguard, not a substitute for network egress controls or an independent -security audit. +## Troubleshooting -HTTPS upstream transport remains encrypted, but certificate-chain/hostname verification -is **off by default**. Set `tls_verify: true` to authenticate the backend identity. An -optional `tls_ca_pem` adds up to 16 trust anchors within 64 KiB per backend when -verification is enabled. The configured `tls_server_name`, when present, determines TLS -identity while DNS/TCP still targets the configured URL. Upstream redirects are returned -to the client, not followed internally. +- **HTTPS works but H3 does not:** check UDP firewall/NAT, listener H3 enablement, TLS certificate, network middleboxes and actual browser negotiation. +- **502 / protocol error to backend:** verify origin supports selected `auto`, `http2`, `h2` or H2C mode and the correct TLS hostname/verification policy. +- **WebSocket disconnects:** enable websocket forwarding in the Vhost and ensure idle limits/proxy/backend are compatible. +- **Unexpected HTTP→HTTPS redirect:** inspect Vhost `force_ssl`, selected listeners and actual public scheme. -## Public certificates - -`dev.sh` enables `127.0.0.1:8443` by default. Direct binary startup enables TLS only -when `serve --tls-listen ADDRESS` or the corresponding environment setting is used. -Changing listener addresses or enabling a new listener requires a restart. - -`data/certs/default.pem` and `default.key` are created once when needed. Generated -certificates are self-signed development material; they do not provide public trust. -Explicit bindings use exact hostname names and already-present certificate/key files. -Manual entries support two addressing modes: simple filenames are loaded from -`/certs`, while two absolute paths are read in place (for example Certbot -`/etc/letsencrypt/live//fullchain.pem` plus `privkey.pem`). Mixed relative/absolute -pairs are rejected. The API stores filenames/paths and names, never private key bytes. -CLI generate/import refuses to overwrite named files. Input size, regular-file status, -private-key permissions, matching keys, chain parsing and name coverage are validated. - -```bash -rust-proxy tls generate --name app --hosts app.localhost,alias.localhost -rust-proxy tls import --name app-v2 --cert fullchain.pem --key private.key -rust-proxy tls deploy --id shared-cert \ - --cert /etc/letsencrypt/live/example/fullchain.pem \ - --key /etc/letsencrypt/live/example/privkey.pem -rust-proxy tls reload -``` - -Select these files in the panel or `tls.certificates` configuration. Set -`tls.default_certificate` to a binding ID to change the fallback. Unknown/no SNI -receives that fallback, not a default HTTP route. Unknown Host still returns 404. -Wildcard/SAN coverage and certificate expiry/issuer/renewal status are exposed by the certificate inventory; certificate ownership remains explicit per certificate id. - -`tls deploy` is intended for an external issuer such as a system Certbot. Certbot's -`live/*.pem` symlinks are resolved locally; the final files are bounded regular files and -the private key must not be group/world readable. The local CLI performs bounded file/permission checks and sends the PEM pair through the authenticated loopback deploy API. The running RustProxy process treats the request as a convergent upsert: it creates a missing manual `certificate_id`, rotates changed material through immutable generation-specific files, and returns a no-op for an identical RustProxy-owned generation. The same id may be assigned to many vhosts, which all switch together when the pair changes. RustProxy-managed ACME certificate ids are deliberately rejected by this command so one lineage never has two renewal owners. - -`POST /api/certificates/reload` and `rust-proxy tls reload` re-read all currently configured -certificate/key files and publish a fresh public TLS snapshot without changing the desired -configuration or its revision. This supports an operator that replaces the already configured -files by another safe local mechanism. Validation and file reads happen on bounded control-plane -workers; invalid material leaves the previous snapshot active. New public TLS/H2 and HTTP/3 -handshakes use the refreshed snapshot, while established sessions/tunnels are not renegotiated. -The administration TLS listener remains startup-fixed and reports that a service restart is -required when its selected certificate changed. - -For an externally managed/manual certificate whose configured files were replaced in place, `POST /api/certificates/{certificate_id}/reload` provides the same revision-stable hot-publication contract while targeting that certificate ID. For absolute external paths RustProxy canonicalizes the path again on every reload, intentionally following Certbot `live/` symlinks to the current `archive/` generation before opening the final regular file. The TLS resolver is immutable, so RustProxy validates and republishes a complete snapshot atomically; this avoids partially updated SNI state. Managed ACME certificate IDs are not accepted by this manual reload path. - -External absolute-path files are not copied into RustProxy full backups. Their path strings remain in configuration; restore or mount the certificate material separately on the destination host before starting TLS. The `rustproxy` service identity must be able to traverse every parent directory and read both files. RustProxy accepts POSIX ACL read access and does not require an exact chmod value. External certificate/key files must not be group/world-writable, and private keys must not be world-readable. The configured and resolved parent chains must be root- or RustProxy-owned and not group/world-writable, except for root-owned sticky ancestors such as `/tmp`. The final file is opened with `O_NOFOLLOW` after path validation. - -## WebSocket behavior - -The default WebSocket path validates an HTTP/1.1 GET upgrade, version 13, one valid key, -connection upgrade tokens and a body-free handshake. RFC 8441 over HTTP/2 and RFC 9220 over -HTTP/3 must be enabled explicitly for the selected vhost and use Extended CONNECT rather -than H1-only Upgrade/Connection/Key headers. When RFC 8441/9220 is bridged to an HTTP/1.1 -backend, RustProxy performs and validates the backend RFC 6455 handshake before exposing a -successful downstream tunnel. Direct HTTP/2 upstream RFC 8441 validates the 2xx response and -selected subprotocol. Protected handshake fields cannot be replaced by generic custom-header -rules. Unsupported/malformed upgrades fail; unsolicited upstream 101 responses become a -gateway error. - -After an accepted handshake the proxy copies bytes in both directions using bounded -buffers and backpressure. It does not aggregate messages, interpret application -payloads, log frames or hardcode a particular WebSocket application. Fragmentation, -control frames and negotiated extensions remain the peers' responsibility. -The demo echo server separately caps messages/frames at 64 KiB. - -Request and TCP-connection leases survive Hyper's upgrade and are held until the -tunnel closes. An independent idle timeout observes traffic in either direction. -Shutdown cancels tunnels; routing/ACL changes affect new handshakes, not existing tunnels. -The administration API and dashboard expose active tunnels with a stable exchange ID, client, -host, upstream, transport, duration and RX/TX counters. An administrator can cancel one tunnel; -the action and supplied reason are written to the audit log. - -## HTTP/2 connection retirement - -Downstream H2 idle retirement is activity-aware: RustProxy tracks transport I/O and accepted streams, rechecks that a connection is actually unused, then asks Hyper for graceful shutdown. Hyper sends GOAWAY and refuses newer streams while accepted streams continue. Each H2/H2C listener uses `binding.http2_settings.max_connection_lifetime_seconds` for the independent absolute lifetime (zero disables it) and `binding.http2_settings.drain_timeout_seconds` for the maximum drain before TCP is forcibly dropped. Older configurations without per-listener values inherit the legacy top-level `http2` settings. RFC 8441 Extended CONNECT/WebSocket streams remain active for this accounting until their tunnel body ends. - -## Resource and timeout defaults - -| Resource | Default / bound | -|---|---| -| Shared public TCP connections, HTTP + TLS | 512; configurable up to 4096 | -| Active request/body/tunnel exchanges | 256; configurable up to 4096 | -| Concurrent HTTP/2 streams per public connection | 64 | -| HTTP/2 header list / send buffer | 32 KiB / 64 KiB | -| HTTP/2 stream / connection receive window | 65,535 bytes / 1 MiB | -| HTTP/2 idle / max lifetime / drain | 300 s / disabled (0) / 10 s | -| Public HTTP/1 header timeout | 10 seconds | -| TLS accept / upstream connect including TLS | 10 seconds | -| DNS lookup caller timeout / OS workers | 5 seconds / 16 | -| Backend response-header deadline | 120 seconds, including upload until headers | -| HTTP body idle timeout | 60 seconds | -| WebSocket idle timeout / upgrade completion | 300 seconds / 10 seconds | -| Idle pooled upstream connections | up to 64 per backend origin by default for new vhosts (1..64 configurable), 30-second idle timeout; existing explicit values preserved | -| Configuration writers / log-viewer streams | 8 / 8 | - -CLI serve flags expose connection/in-flight counts and HTTP body, WebSocket idle, -upstream and shutdown timeouts. OS DNS calls cannot be forcibly cancelled; their -worker permits remain occupied until completion. Runtime shutdown stops waiting -for blocking workers after an additional bounded timeout. - -Request and response bodies stream without collection, including trailers. Body-bearing/write requests are not transparently replayed. Replay-safe body-free -GET/HEAD/OPTIONS may use the bounded backend retry policy described above. Bytes counted at the body layer are not -acknowledged network bytes. WebSocket counters measure raw frame bytes separately -from HTTP body counters and exclude TLS/TCP overhead. - -## Vhost sockets and keepalive in 0.22.3 - -Public connections across global and vhost endpoints share the same connection -budget. Per-vhost routing uses the actual accepted local IP/port/TLS state, including -when an OS wildcard socket is shared. See VHOSTS.md for overlap rules and restrictions. -Changing sockets or their ALPN policy needs restart. Certificate refresh remains hot. - -TLS `http2: true` advertises h2 followed by http/1.1; false advertises HTTP/1.1 only. -An HTTP/1.1-only client, including one without ALPN, can connect when h2 is enabled. -HTTPS origins configured `http2` also negotiate h2 or HTTP/1.1. This is one TLS -negotiation, not a failed-request retry or TLS-verification downgrade. Plaintext -h2c remains explicit and strict: when `h2c: true`, a cleartext listener accepts both HTTP/2 -prior knowledge and validated HTTP/1.1 `Upgrade: h2c`. The Upgrade bootstrap is body-free, -requires `HTTP2-Settings`, and becomes HTTP/2 stream 1 after `101`. Optional downstream HTTP/3 -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. 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. +For listener configuration see [Configuration](CONFIGURATION.md); for safer deployment see [Security](SECURITY.md). diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..42198b3 --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,61 @@ +# Troubleshooting and validation checklist + +Start with a reproducible request, its hostname/path and the exact listener. Compare **Vhost**, **Backend**, **Cache**, **Logs** and the service journal before changing multiple settings at once. + +## Quick symptoms + +| Symptom | First checks | +|---|---| +| Domain returns the wrong site or local 404 | DNS A/AAAA, selected listener ID, exact vs wildcard host precedence, Vhost enabled state, redirect/404 host ownership. | +| HTTP works, HTTPS does not | Trusted certificate, hostname/SAN coverage, TLS listener, port 443 firewall, certificate assignment and whether restart was needed. | +| HTTPS/H2 works but H3 fails | UDP firewall/NAT, H3 toggle on TLS listener, browser QUIC support and actual transport negotiation. | +| `502` / `503` / `504` to origin | Backend IP/port, pool eligibility/health, private-address policy, upstream HTTPS verification, protocol `auto`/`h2`, timeout and container/PHP process availability. | +| WebSocket handshake fails or disconnects | Vhost WebSockets flag, backend Upgrade support, TLS/frontend protocol path, WebSocket idle limits and auth redirect. | +| `403` / `405` / `429` | Applicable IP/ACL/country policy, HTTP methods, rate limit, GeoIP availability, Basic/Tinyauth permissions. | +| Cache always BYPASS or MISS | Global + Vhost enablement, cookies/login, origin `Cache-Control`, `Set-Cookie`, `Vary`, TTL, size and exclusion rules. | +| Content remains stale | Cache Age/resident age, origin validator, policy changes, matching scheme/Host/query and selected purge scope. | +| Static file not found | Document root, process read permissions, index files, symlink policy, `try_files` and rewrite ordering. | +| PHP returns errors or another site's PHP settings | FPM endpoint/socket permission, distinct pool/INI settings, pool restart, PHP `PATH_INFO`, application logs. | +| Docker Vhost remains WAITING / 503 | First agent poll, token/certificate pin, published host TCP binding, backend IP, container state and health. | +| Stream TCP/UDP unreachable | Actual listen address/port, protocol and firewall, backend IP, Stream health/session limits; HTTP Vhost settings do not apply. | +| Administration cannot be accessed remotely | Admin binds loopback by default. Use SSH tunnel or configure **separate admin HTTPS** and intentional bind address. | +| Prometheus exporter unavailable | Dedicated listener, CIDR and token, configured path, startup/restart state and monitoring server route. | +| Logs missing or search incomplete | Check sink enabled/path/permissions, Live vs Stored distinction, log rotation and bounded scan/truncation notices. | +| Certificate renewal fails | ACME runtime, HTTP-01 TCP 80, DNS-01 token/zone, expired credentials, Certbot files and external key ACL after rotation. | + +## Useful commands + +Local service status for installed Linux/systemd deployments: + +```bash +systemctl status rustproxy +journalctl -u rustproxy -n 200 --no-pager +``` + +Inspect a specific HTTP frontend with the correct Host (adapt hostname, port and path): + +```bash +curl -v -H 'Host: app.example.com' http://127.0.0.1:8080/health +``` + +For HTTPS certificate/routing testing use the real site DNS or a deliberate `curl --resolve` mapping; avoid permanently disabling certificate verification as a workaround. The local CLI can check configuration without saving: + +```bash +rust-proxy --data-dir /var/lib/rustproxy config get +rust-proxy --data-dir /var/lib/rustproxy config validate config.json +``` + +Use **Logs → Live Logs** or the `--livelogs` CLI mode to correlate request IDs, Vhost/route/backend and status. Inspect **Stored Logs** for previously saved JSONL and selected low-level protocol failures. + +## Rollout gate for RustProxy 0.34.0 + +The latest source optimizations were **not all compiled or performance-tested** in the evidence supplied with this release. Before deploying: + +1. Build using the declared Rust toolchain and run the project's available regression suite on the target platform. +2. Verify HTTP/1.1, HTTPS, HTTP/2, HTTP/3 (if enabled), WebSockets and multi-backend failover with a real origin. +3. Test private origin constraints, ACL/auth/CSRF, rate limits and protected cache content. +4. Test Web Server static files, PHP-FPM, cache revalidation/invalidation and optional `sendfile=auto` only if enabled. +5. Exercise certificate renewal/reload and backup **restore** on isolated staging. +6. Compare performance only under matched configurations with controlled repeatable traffic; do **not** label 0.34.0 as faster merely because its source reduces internal work. + +For persistent problems, use the feature-specific guide in [Documentation](README.md) and preserve a redacted error example with environment/version and request ID. diff --git a/docs/VHOSTS.md b/docs/VHOSTS.md index 487db42..d58aac7 100644 --- a/docs/VHOSTS.md +++ b/docs/VHOSTS.md @@ -1,495 +1,57 @@ -> 0.7 adds graphical locations, Basic Auth, cumulative IP ACL editing and a -> WebSocket toggle. See [host access and locations](VHOST_ACCESS.md). +# Vhosts, domains and backends -# Virtual hosts, frontend bindings and backend pools +A **Vhost** publishes one or more hostnames on named HTTP/HTTPS listeners. Its source can be an HTTP reverse-proxy backend pool or a reusable local Web Service. Use **Vhosts → Add** in the panel. -This is the source implementation contract. Rust compilation and runtime acceptance -were not performed in the packaging environment; see TEST_REPORT.md. +## Publish a site -## Backend quick-view in Vhosts +1. Enter an ID and the exact domain (`app.example.com`) or supported wildcard (`*.example.com`). Exact names win over wildcards. Configure DNS independently. +2. Choose the enabled HTTP/HTTPS frontend listeners. Select a certificate for HTTPS. +3. Choose **Reverse Proxy** and add an upstream address such as `http://10.0.0.20:3000`, or select an existing **Web Service**. +4. Check backend reachability and TLS policy; configure health checks and load balancing for multiple origins. +5. Decide whether to force HTTPS, redirect www/non-www, enable WebSockets, cache, access rules and custom paths. +6. Save, visit the hostname, inspect the Vhost's health and logs, then test failure and recovery. -Click the **Backends** count or the **Status** tile of a Vhost to open its -backend list. Each member displays the configured ID, origin host/IP and port, -HTTP/HTTPS scheme and HTTP mode, current reported health and `last_status`. -`last_status` is the most recent HTTP code from a proxied request; zero/no -member means no known HTTP response, not a failed probe. DNS origins show the -configured hostname, as the status API does not report the resolved IP. -The panel updates from the existing status feed without initiating requests, -and supports Escape and click-outside dismissal. +A Vhost may bind several frontends. Hostnames and selected listeners must match: the existence of a certificate or an upstream does not create DNS entries or a listening socket. Existing Vhosts retain their bindings when new listeners are created. -## One vhost, one backend pool +## Source and backends -A `vhosts` entry owns an `id`, `enabled`, `frontend`, `source`, `backends`, `load_balancing`, -`routes`, optional `acl`, `headers` and `security_headers`. `source` defaults to Reverse Proxy -for older documents; it can instead reference an independently configured Web Service. See -[WEB_SERVER.md](WEB_SERVER.md). The pre-production top-level -routing and inline-listener formats are no longer accepted. Unknown JSON fields are -rejected. The examples are complete documents, not JSON merge patches: +- **Reverse Proxy:** one Vhost has a backend pool. Each member has a configured URL, ID, enabled flag, weight, optional backup/drain state, health settings and capacity limits. +- **Web Server:** Vhost references a Web Service by ID. Its document root, directory handling, PHP options and static-file headers remain configured under **Web Server**. +- **Custom locations:** can override selected URL paths and use the default pool, a pinned backend, a different Web Service, a redirect or a local response. Unmatched paths still use the main source. -- `examples/vhosts-private.json`: explicit loopback IP, two existing HTTP origins, - adaptive balancing, HTTP health checks, keepalive, no cache or cookie affinity. -- `examples/vhosts-balanced.json`: explicitly public HTTP 80 and TLS 443, cookie - affinity and a deliberately short HSTS value; requires a trusted, imported `app` - certificate/key and real origins. It does not create those services or files. +Sample documents in `examples/vhosts-private.json` and `examples/vhosts-balanced.json` illustrate JSON formats, but their IP addresses, certificates and health endpoints are examples only. -In the examples, both origins need `/health` to return a quick 2xx response. Adjust -that path/status range for the real service. New pools start quarantined until -`rise` successful checks; an unavailable or misconfigured check deliberately causes -503 instead of sending traffic to an unverified member. `mode: off` opts out. +## Backend connection and HTTP modes -The editor includes frontends, members, balancing, health, connection reuse, sticky -sessions and response protection. Custom locations have graphical match/action/rewrite/ACL/header -controls plus synchronized Advanced JSON for automation. Unsaved drafts survive translation/SSE refreshes. A stale draft must be -reloaded before saving: the panel does not silently merge competing revisions. - -Existing vhost IDs can be renamed in the dashboard as long as the new ID is valid and -unique. The rename keeps the configured frontend hostnames, routes and backend IDs, but it -intentionally starts a new vhost runtime identity for metrics/log attribution and current -health/traffic history. Because the vhost configuration participates in cache compatibility, -a rename also rotates that compatibility identity and invalidates incompatible cached objects. -The editor warns before applying the rename. - -## Internal error routing - -The main vhost `source` may define `error_fallback`, and each Custom location may do the same. A fallback references the `id` of an enabled `internal` Custom location in the same vhost. Supported statuses are `403`, `404`, `500`, `502`, `503` and `504`; omitted status lists default to `404`. Internal targets are not reachable by ordinary path matching. - -Configured upstream/Web Server failures are intercepted before final response/cache publication. The original request method, URI/query and context are internally dispatched to the target, which then follows normal vhost/location processing. Validation rejects fallback cycles and runtime dispatch is depth-limited. See [Custom Locations](CUSTOM_LOCATIONS.md) for the active internal-fallback behavior. - -## Per-backend health monitoring (0.29.7) - -Each backend has an independent `health_check_enabled` switch (default `true`). -Setting it to `false` excludes that member from both active health probes and -passive health ejection, **without** disabling the member for a named location. -This is useful for an internal 404 handler configured with `pool_eligible: false` -and a location pinned through `backend_id`: the 404 handler never receives -ordinary balanced traffic and does not need health monitoring. - -A backend may also define `health_check` with the same fields as -`load_balancing.health_check` to use its own TCP/HTTP/off probe policy, timing, -path, Host and status ranges. Omitting it inherits the vhost policy. A custom -HTTP health check without `host` inherits the vhost Host default even when the -vhost's main health check is TCP. When monitoring is disabled, the backend is -assumed available; connection errors during real requests are still counted and -logged, so monitoring off is not a guarantee of backend reachability. - -## Frontend selection and private IP isolation - -`frontend.hosts` is a normalized hostname-pattern list. Exact hostnames and a single -leading wildcard such as `*.example.com` are supported; regex and mid-label wildcards -are not. Routing tries the exact hostname first, then the longest matching wildcard. -This means `api.example.com` can have its own vhost while `*.example.com` handles other -subdomains on the same listener without stealing the exact route. -`frontend.listener_ids` references named `listeners.endpoints` configured under -Settings. A listener owns its IP:port, TLS flag and H2/H3 transport capabilities; -a vhost never stores a listening address directly. For a new dashboard vhost, the -editor preselects both an enabled HTTP listener on port 80 and an enabled TLS listener -on port 443 when they exist (public-scope listeners are preferred). Existing vhost -bindings are never replaced by that convenience default. - -`force_ssl` defaults to false. When enabled, any cleartext request that matches this -vhost receives HTTP `308 Permanent Redirect` to the same normalized hostname and -original path/query. At least one selected plain HTTP listener and one selected TLS -listener are required so there is both an ingress to redirect and an HTTPS target. If -several TLS ports are selected, port 443 is preferred; otherwise the selected TLS target -port must be unique. - -`www_redirect` defaults to false. When enabled, `www_redirect_target` selects an exact DNS hostname from `frontend.hosts` as canonical (default: the first hostname for backwards compatibility). RustProxy also claims its direct companion (`example.com` <-> `www.example.com`) on the same listeners and returns `308 Permanent Redirect` from that companion to the first hostname while preserving path/query. Wildcards and IP literals cannot be the canonical hostname for this feature. The companion also participates in vhost certificate assignment, but the selected certificate must still contain both DNS names in its SANs. When `force_ssl` is also enabled, an HTTP companion request redirects directly to the HTTPS canonical URL instead of creating two redirect hops. - -`forward_client_ip` defaults to true. Normal vhosts therefore receive the sanitized -`Forwarded`, `X-Forwarded-For` and `X-Real-IP` client identity generated by RustProxy. -Set it to false for applications whose own trusted-proxy/bypass logic must see the -RustProxy TCP peer instead. Only the backend application request loses those three -client-IP headers; ACLs, GeoIP, rate limiting and Tinyauth still use the effective client -address, and origin metadata such as `X-Forwarded-Proto`, `X-Forwarded-Host`, -`X-Forwarded-Port` and `X-Forwarded-URI` remains available to the backend. - -`websockets` enables WebSocket handling for the vhost. The dashboard defaults new WebSocket -vhosts to the nginx-compatible HTTP/1.1 Upgrade transport. `websocket_legacy_support: true` is -a separate per-vhost compatibility switch for older appliances and embedded servers. It affects -only WebSocket handshakes: RustProxy forces an HTTP/1.1 upstream WebSocket hop, uses the configured backend authority as upstream `Host`, -rewrites `Origin` to the matching upstream scheme + backend authority, and tolerates legacy `101 Switching Protocols` response -framing while keeping the `Sec-WebSocket-Accept` proof and offered subprotocol checks strict. -Ordinary HTTP requests are unchanged. `websocket_rfc8441: true` and `websocket_rfc9220: true` are -separate opt-ins for HTTP/2 and HTTP/3 Extended CONNECT and are valid only when WebSocket -support is enabled. They are presented together in the vhost editor. -RFC 8441 requires an H2-capable selected listener (TLS ALPN or cleartext h2c prior knowledge). -Because the RFC 9220 advertisement is QUIC-connection scoped, one opted-in vhost causes the -shared QUIC listener to advertise Extended CONNECT, while request routing still rejects RFC -9220 on non-opted-in vhosts. - -`http2_enabled` and `http3_enabled` are per-vhost opt-outs (both default true). The -panel presents the two switches together and enables each only when at least one -selected listener provides that protocol. For TLS H2, ClientHello/SNI selects H2+HTTP/1.1 or HTTP/1.1-only ALPN for -the matched vhost. H3 remains a shared UDP listener capability; disabling it on a -vhost removes Alt-Svc and rejects H3 for that host. - -Each vhost can optionally set `tls_policy` with `tls12`, `tls13` and a list of rustls -cipher-suite names. Omitting it inherits `tls.policy` from Settings. The override is -selected from ClientHello/SNI before the TCP TLS handshake, so a vhost can for example -run TLS 1.2 only while another frontend continues to use the global TLS 1.2/1.3 policy. -At least one protocol version and a compatible effective cipher suite are required. -Because HTTP/3 shares QUIC TLS on the UDP listener and requires TLS 1.3, a vhost with -HTTP/3 enabled must use the same TLS policy as the global setting. Disable HTTP/3 on -that vhost before using a differing per-vhost TLS policy. - -Routing checks Host/HTTP authority, actual accepted local IP, local port and actual -TLS state. Neither `Forwarded` nor `X-Forwarded-*` can create frontend access. A -request for a private hostname on a different local IP is not routed to that host. -Unknown or unmatched hosts return 404. - -Bindings must be local addresses assigned to the OS. The process does not add IPs, -manage routes, configure firewall rules, acquire virtual IPs or run Keepalived/VRRP. -A missing IP, occupied port or permission error aborts startup without wildcard -fallback. Low ports may need deployment capabilities; see `../deploy/README.md`. - -Identical or same-family wildcard sockets are consolidated only when the named -listener transport capabilities agree. Vhost routing restrictions are not -consolidated: a wildcard TCP listener can accept on a public IP while still rejecting -a private vhost there. Per-vhost H2 is narrowed by SNI and therefore does not require -a second TCP socket. IPv4/IPv6 wildcard overlap is rejected; use explicit addresses. -Native IPv4 binds are required instead of mapped IPv6 literals. The administration -port is reserved even on a different IP. - -IP isolation is not client authentication. A firewall, router, NAT or preceding -proxy can forward external clients to the private destination IP; RustProxy sees -that local destination and the connecting peer. Use network ACLs, firewall rules -and application authentication too. Trusted forwarding headers have a separate explicit CIDR policy (see -INTEGRATIONS_040.md); PROXY protocol is not implemented. Interface changes require config -reload/restart to refresh the upstream loop-guard's local-address enumeration. - -The same literal hostname pattern may occur only where its listener bindings remain -unambiguous. Exact and wildcard patterns are distinct ownership keys, so an exact host -and its parent wildcard may coexist. Disabled vhosts reserve their patterns and are -still validated, but create no pool or additional active listener. - -## Paths, policies and migration - -`routes` contains per-vhost paths: `id`, match fields, `enabled`, `source`, nullable `cache`, -optional `acl_profile_id`, inline `acl`, `country_access`, `method_policy`, `headers` and `backend_id`. `source` selects Reverse Proxy or a reusable Web Service; -`backend_id` is valid only for Reverse Proxy. An empty array leaves the main vhost source as the fallback. Longest -segment-boundary prefix wins. Origins receive the original path/query; prefixes -are not stripped. IDs must use ASCII letters, digits, hyphens and underscores. -Host method policy, reusable ACL profiles, vhost ACL/country policy and route method/profile/inline -ACL/country policies are cumulative before pool selection/DNS/cache. A vhost and each route can -attach a reusable `acl_profile_id`; profiles may contain an IP ACL plus a country block/allow-only -rule. Inline controls remain independent and cannot be weakened by a profile. Method-policy denial -returns 405; allow-list mode also emits `Allow`. Their client identity is the -validated effective peer address, not an untrusted forwarding header. - -There is no legacy routing UI/runtime or automatic importer in 0.8.0. Development -databases using the old top-level routes or inline frontend listeners must be reset -or migrated explicitly before startup. An omitted path backend ID selects from the -balanced pool; a specified ID must belong to the same vhost and does not fail over to a -different application's member. Imported health checks start OFF. Ambiguous names, -unreferenced old backends and invalid/oversized documents stop migration before -saving. See MIGRATION.md before updating an existing installation. - -## Backend members and connection reuse - -Each member has `id`, HTTP(S) origin `url`, `weight` (1..256), `enabled` (true), -`backup` (false), `drain` (false), `allow_private` (false), -`accept_local_listener_risk` (false), `preserve_host` (true), `protocol` (`http1`), -`address_allowlist`, optional `tls_server_name`, `tls_verify` (false) and optional -`tls_ca_pem`. `tls_server_name` is an HTTPS-only DNS hostname -used for SNI and, while verification is enabled, certificate hostname verification; -DNS/TCP still targets the configured `url`. `tls_verify` defaults to false. HTTPS connections remain encrypted, but backend certificate authentication is skipped until an administrator explicitly sets `tls_verify: true`; this includes compatibility with legacy certificate encodings that modern webpki validation rejects. Address/DNS policy remains enforced; local-listener self-targeting has only the explicit risk exception described below. `tls_ca_pem` is ignored while verification is disabled. -Backend IDs are unique inside a vhost. URLs cannot contain credentials, paths, -queries or fragments. -Every DNS answer and every configured literal must pass the same upstream policy -for health probes and forwarding. Active, pending and prior-snapshot frontend -addresses and local administration are guarded against self-proxying by default. -For an intentional local target such as `http://127.0.0.1:9090`, enable both -`allow_private: true` and `accept_local_listener_risk: true`. The latter is a narrow, -explicit acceptance of loop/control-plane exposure risk; metadata/link-local and other -special-address blocks are not bypassed. In the dashboard this risk checkbox is hidden -unless the entered backend URL directly targets an administration listener reported by -persisted configuration or live status. Startup-owned/locked administration sockets are -therefore handled too. On the same administration port, loopback spellings such as -`localhost`, `127.0.0.1` (and the rest of `127.0.0.0/8`) and `::1` are treated as -equivalent for showing the explicit risk control, so it is not presented as a normal -backend option. - -A vhost can independently enable two proxy-owned response diagnostics: - -- `backend_header: true` -> `X-RustProxy-Backend: `, -- `backend_timing_header: true` -> `X-RustProxy-Backend-Time: ms`. - -The timing value covers the origin request through receipt of the backend response -headers; it is not full response-body duration. Both proxy-owned names are protected -from custom header rules and any upstream values are removed before RustProxy optionally -writes its own value. The former `X-Rust-Proxy-Backend-Time` spelling is also stripped -as a legacy lookalike but is never emitted. Responses served without contacting an -origin do not expose a selected backend or stale origin timing. -`timing_headers` remains the separate opt-in `Server-Timing` diagnostic. - -`keepalive` controls **connection pooling**, not an HA service: - -| Field | Default | Accepted range | -|---|---|---| -| `enabled` | true | Boolean | -| `idle_timeout_seconds` | 30 | 1..3600 | -| `max_idle_per_host` | 64 | 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 | - -The new default of 64 applies when this field is absent in a newly created or deserialized vhost; existing saved values (including 4) remain unchanged. Each idle upstream connection consumes a file descriptor and memory, so use a smaller value for resource-constrained deployments with many origins. - -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. `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. -Compatible backend clients are retained across unrelated runtime snapshot rebuilds, so -generation age, request count and rotation totals remain continuous. A changed backend -origin, address policy, keepalive settings or connect timeout creates a fresh client -generation state instead of reusing an incompatible pool. -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. - -Backend upstream `protocol` has four modes: - -| UI mode | Serialized value | Backend behavior | -|---|---|---| -| **HTTP/1.1** | `http1` | HTTP/1.1 only over HTTPS or HTTP; default for new backends | -| **Auto** | `auto` | HTTPS: prefer H2 and negotiate H1 fallback with ALPN before sending a request; HTTP: H1 only, without H2C probing | -| **HTTP/2 compatible** | `http2` | **Unchanged 0.32.2 behavior**: HTTPS offers H2/H1 via ALPN; plaintext HTTP uses H2C prior knowledge (no H1 downgrade on HTTP) | -| **H2 only** | `h2` | HTTPS: require H2 via ALPN; reject H1-only peers. HTTP: prior-knowledge H2C, no fallback | - -**0.32.2 -> 0.32.3 compatibility:** saved `"protocol": "http2"` retains its existing -HTTPS ALPN fallback and cleartext H2C behavior. No conversion, migration or -administrator action is needed to preserve the old selection. Choose the new -`"protocol": "h2"` only when H2 must be enforced. The editor describes the -transport for the selected backend URL; a descriptive notice next to compatible -HTTPS `http2` is informational, not a migration warning. - -HTTPS Auto negotiation selects the wire protocol **before sending the request**. -RustProxy does not replay an ambiguous failed POST/PATCH/DELETE through H1, and -does not interpret backend 4xx/5xx responses as a reason to switch protocols. -Existing pool reuse, keep-alive, client generation rotation and the independent -HTTP/1.1 WebSocket Upgrade / H2 Extended CONNECT paths are retained. -HTTP health probes continue to use HTTP/1.1 independently of ordinary requests; -H2C-only peers can use TCP probes. A health probe that needs a virtual-host -name must configure `health_check.host` separately; `preserve_host` affects -visitor requests, not independent health probes. - -**Since 0.32.4:** all four upstream modes allow `preserve_host: true`. -With HTTP/2, `:authority` follows the selected HTTP Host instead of the backend -IP; with HTTP/1.1 it remains the regular Host. TCP/DNS always target the -configured backend URL and still enforce the address and local-listener -policies; `tls_server_name` independently controls HTTPS SNI and certificate -identity, not the visitor Host. HTTPS backend certificate verification defaults -to OFF when `tls_verify` is omitted. Turn on verification and configure -`tls_server_name` / `tls_ca_pem` for a private DNS certificate; a valid IP-SAN -certificate can instead authenticate an IP backend without a DNS name. -The form's HTTP/1.1 troubleshooting preset is optional; it is not required -for Auto/H2, private origins or Host preservation. - -## Scheduling - -| `algorithm` | Decision | +| Setting | Use | |---|---| -| `round_robin` | Smooth weighted round-robin | -| `least_connections` (default) | Lowest `(active exchanges + 1) / effective weight` | -| `least_traffic` | Lowest recent EWMA payload rate plus active-exchange allowance, divided by effective weight | -| `adaptive` | Combines active exchanges, EWMA time-to-headers and payload rate, divided by effective weight | -| `ip_hash` | Weighted rendezvous hash of vhost/member and canonical actual peer IP | +| `http1` | Standard HTTP/1.1 upstream. | +| `auto` | HTTPS negotiates HTTP/2 or HTTP/1.1; plaintext uses HTTP/1.1. | +| `http2` (legacy) | Compatible HTTPS ALPN or plaintext H2C, kept for existing configurations. | +| `h2` | Strict HTTP/2/H2C; requires origin support. | -An active exchange spans upload, response body and upgraded tunnel, not just the -wait for response headers. Slow-start gradually increases effective weight after -initial success/recovery (`slow_start_seconds`: 10, allowed 0..300; 0 disables it). -IP hashing uses configured weights instead of slow-start to keep assignments stable. -These are explicit heuristics, not workload predictions or benchmark-backed claims. -No remote host CPU measurement is used by the scheduler. +A backend's `preserve_host` option can retain the frontend authority when appropriate. HTTPS encryption alone does not guarantee verified backend identity: `tls_verify` is **false by default**. Enable verification and configure trusted CA/name for sensitive connections. Private, loopback and RustProxy-local listener targets require explicit security exceptions; do not point a public route to the admin listener. Read [Security](SECURITY.md). -Only enabled, healthy, non-draining primary members receive new assignments. If -none exists, eligible backups are used. All unavailable means 503 with Retry-After; -there is no fallback into another vhost. Balanced routes may retry a body-free `GET`, -`HEAD` or `OPTIONS` on another healthy member according to `load_balancing.retry`: +Connection reuse is bounded by configured idle-pool/recycle limits. 0.34.0 changes the default H1 upstream idle pool for newly configured Vhosts; previously saved explicit values remain. Configure timeouts to suit expected request and upload behavior; a slow origin may need more than the default response-header timeout. -| Retry field | Default | Accepted range / meaning | -|---|---|---| -| `max_retries` | 1 | 0..3; zero disables failover retry | -| `retry_connect_errors` | true | Retry connection establishment failures | -| `retry_timeouts` | true | Retry the response-header timeout | -| `retry_statuses` | `[502,503,504]` | Up to 8 HTTP 5xx statuses | +## Load balancing and health -A backend already attempted in the same exchange is excluded from later attempts. -Path-pinned routes and WebSocket handshakes are not retried. POST/PUT/PATCH and any -request with a body are never automatically replayed. Failed attempts still feed the -normal passive-health policy and retry counters/logs; successful failover updates sticky -affinity to the backend that actually answered. +Backend pools support scheduling/weighting, standby members, draining and optional session affinity. Check the **Backends** status view for member health, recently observed HTTP response code, upstream protocol and resource usage. A reported status from normal traffic is not necessarily the result of a health probe. -## Health checks and recovery +- Health monitoring may use TCP or HTTP checks with a path, Host, interval and thresholds. Avoid write endpoints; use a lightweight `/health` or equivalent. +- Each member can override the pool health check or disable active/passive monitoring with `health_check_enabled=false`. Disabling checks treats the member as available, not proven healthy. +- New monitored pools can temporarily have no eligible healthy member until rise checks succeed. A failed/incorrect check can produce `503` even if the process is reachable. +- `backup` members act as fallbacks; `drain` prevents new normal selection while letting existing work complete within normal lifetimes. Explicitly pinned backends need careful policy review. +- Concurrency and queue limits protect overloaded origins. Retries are restricted; do not assume non-idempotent requests are automatically replayed. -| Field | Default | Accepted range / meaning | -|---|---|---| -| `mode` | `tcp` | `off`, `tcp`, `http` | -| `path` | `/` | Leading slash, up to 1024 bytes; no authority/fragment | -| `host` | null | Optional validated HTTP Host override; otherwise the first exact vhost DNS hostname is used | -| `interval_ms` | 5000 | 250..300000 | -| `timeout_ms` | 2000 | 50..60000; no greater than interval | -| `slow_response_ms` | 1500 | 0 disables; otherwise no greater than timeout | -| `fall` / `rise` | 3 / 2 | 1..20 consecutive failed/successful checks | -| `expected_status_min` / `max` | 200 / 399 | Inclusive, ordered HTTP status range within 100..599 | +## Hostnames, redirects and errors -TCP checks verify that an approved origin address accepts a connection. HTTP checks -perform GET on the configured path, without redirects, cookies or authorization. -They use HTTP/1.1, including verified TLS when appropriate, and measure time to -headers, not the full body. When `health_check.host` is empty, RustProxy automatically uses the first exact DNS hostname configured on the vhost so an IP-address backend can still route the probe through its name-based virtual host. If no exact DNS hostname exists, the existing backend-host fallback remains. Set `health_check.host` only when the probe must use a different Host value. For an h2c-only service, use TCP checks. Slow probes -count as active failures. Active TCP/HTTP health is independent from `passive_health`. +- `force_ssl` redirects matching plaintext traffic to HTTPS using `308`, preserving path/query; configure both HTTP and HTTPS frontends. +- WWW controls can direct `www.example.com` to `example.com` or the opposite as supported by the editor; ensure DNS/certificate coverage for both. +- Use the dedicated **Redirects** workspace for redirect-only domains. Use **404 hosts** to claim a hostname and return a local not-found page without forwarding. +- **Custom locations** may return status/body or redirect for selected paths. **Internal locations** can be used as fallbacks for configured `403/404/500/502/503/504` application responses. +- The **Pages** section can replace supported origin error bodies without contacting a second backend. This keeps the response status and disables caching of the replaced error body. -`passive_health.enabled` defaults to `false`. With that default, normal user traffic -never changes the shared backend healthy/unhealthy state: HTTP 5xx, connection errors, -timeouts, response-body I/O failures and slow responses are still counted/logged but -cannot quarantine the member. When explicitly enabled, the administrator can choose -`count_connect_errors`, `count_timeouts`, `count_io_errors`, `count_http_5xx` and -`count_slow_responses`, with optional passive `fall`/`rise` overrides. Missing overrides -inherit the active health thresholds. Active and passive streaks are separate, so a -successful user request cannot erase an active-probe failure streak and an active probe -cannot erase a passive failure streak. `mode: off` disables only active probes; an -explicitly enabled passive policy may still quarantine a backend. +## Safe operating practices -One cancellation-aware supervisor schedules at most 16 probes, one per member, -with a rotating window for fairness. The tick is 250 ms; configured intervals are -best-effort under resource pressure, not precise service-level guarantees. DNS -workers have their own bounded slots. Probe bodies are not collected or cached. - -## Sticky sessions and drain - -`sticky.mode` is `off`, `cookie` or `ip_hash`. IP mode overrides the normal algorithm -using the actual peer IP. Users behind one NAT/proxy share that identity. - -Cookie mode uses a random 32-character opaque member token, not a backend URL. -The default name is `RP_AFFINITY_`; custom names accept 1..96 ASCII letters, -digits, underscores or hyphens and cannot use the admin session name. TTL defaults -to 3600 seconds (60..604800), with Path=/, HttpOnly, SameSite=Lax and Secure on TLS. -`__Host-`/`__Secure-` names require every frontend to use TLS. Cookies are host-only, -so aliases have independent browser cookies. They do not authenticate users. - -Cookie-affine requests always bypass shared cache. The dedicated routing cookie -is stripped before forwarding; origins cannot override it via Set-Cookie. Unknown, -duplicate or unhealthy-target tokens are ignored/reassigned. Existing valid cookie -sessions may continue on a healthy draining/backup member; new sessions do not -choose a draining member. Disabling a member prevents all new assignments, -including existing cookie sessions, while already admitted exchanges finish. - -Affinity tokens, health and traffic observations survive compatible hot edits. -A changed origin/health policy or re-enabled member is checked afresh. Process -restart resets tokens/counters; clients can be reassigned. Session data itself is -never replicated: applications needing durable sessions require a shared store. - -## Response protection - -`security_headers` supports `hsts` (`enabled`, `max_age_seconds`, -`include_subdomains`, `preload`), `nosniff`, `frame_options` (`preserve`, `deny`, -`sameorigin`), `referrer_policy`, `content_security_policy` and `permissions_policy`. -Header values are validated against splitting and bounded to 8192 bytes. - -HSTS is configured only for TLS responses. For a matched vhost, plaintext responses -remove Strict-Transport-Security even if an origin/custom rule adds it. On TLS, -disabled HSTS means no forced replacement of an origin value; use a custom remove -rule to suppress that value. Enabled HSTS overrides generic rules. `max_age_seconds` -accepts 0..63072000 (default31536000); setting zero is available for explicit expiry. -Preload is opt-in and requires enabled HSTS, includeSubDomains and at least one year. -This only emits the directive; it does not register a hostname in a preload list. -Start with a short lifetime and verify every affected hostname before increasing it. - -Request rules run global -> vhost -> path, then protected identity fields are -regenerated. Response rules run global -> vhost -> path, followed by dedicated -security/Server policy and optional compression. Security headers cover cache hits -and matched-host synthetic errors/ACL denials too. Protected transport fields -remain uneditable. `false`, `preserve` or null security settings otherwise leave -origin values unchanged. CSP and Permissions-Policy must suit the application; -RustProxy validates header encoding, not the semantic correctness of those policies. - -The **Compression** tab inherits the enclosing/global policy by default. It can enable/disable dynamic compression and independently inherit/enable/disable zstd, Brotli, gzip and deflate. Selecting **Disabled** hides and disables the codec selectors while preserving their saved values for a later Inherit/Enabled selection. Global **Settings -> Response** codec switches are the hard availability ceiling, so a vhost cannot activate a codec disabled globally; `min_bytes` also remains global. Client `q` values are honored; equal-quality codecs use `zstd -> br -> gzip -> deflate`, and an existing `Content-Encoding` is never recompressed. - -## Live state, cache and limits - -The dashboard defaults a newly created vhost to `cache.enabled: false`. When the -effective cache state is disabled, the editor hides the remaining per-vhost cache -controls; enabling cache (or explicitly choosing inheritance from an enabled global -cache) reveals them. This is a UI default for new drafts, not a migration that rewrites -existing vhost cache settings. - - -`GET /api/backends` requires an admin session and returns `pools`, -`active_frontends`, `desired_frontends`. `/api/status` and SSE contain `backend_pools` -and complete active/desired endpoint plans. The dashboard renders per-member state, -active exchanges, request/error totals, EWMA header latency and payload traffic. -Prometheus adds bounded vhost/member labels; access logs include `backend` as -`vhost/member` for actual pool forwarding (null when no upstream exchange is made). - -Bytes are observed payload, not NIC throughput; response counters include the -client-delivered encoded representation. Header latency includes connection/setup and any request upload; it is not full -exchange time. Active probes use body-free requests for an independent measurement. -Health mode off is labeled as checks off, not proof of a probe passing. Statistics -are observations, not persisted configuration. They reset on process restart. - -Cookie affinity bypasses cache entirely. A pool with no eligible member does not invent -an unselected cached fallback. Config changes invalidate incompatible cache state. -RFC 5861/request `max-stale` behavior is owned by the shared cache and does not alter -backend member selection; request coalescing remains separate from stale-while-revalidate -deduplication. - -Limits: 64 vhosts; hostname lists are not capped by an application-level item count; -listener entries remain capped at 16 per host, with 32 members and 64 paths per host; -128 backend definitions and 256 paths across all vhosts; -128 additional declared frontend entries plus two globals; 256 KiB serialized -configuration. Existing request/connection/DNS/queue bounds remain in force. - -Every successful config publication rebuilds upstream connection pools. Compatible -member observations/tokens and in-flight exchanges survive, but idle sockets are -not retained across publication. Listener/address/ALPN changes are saved immediately -and require restart; routing changes apply to existing bound sockets immediately. -When moving a host away from an old IP, it can become unavailable until restart -rather than being served on an address no longer permitted by desired policy. - -## Per-vhost operations in 0.3.0 - -The compact list opens a tabbed editor rather than displaying a full form beside -the list. Health summaries update on the list and editor without replacing unsaved -form controls. Header rules have a visual request/response editor; Advanced JSON -remains available and stays synchronized with those rows. - -The SSL tab selects an existing certificate ID or uploads a chain/private-key -pair into private staging. Only Save activates the selection. A shared global SNI -resolver cannot serve different certificates for the same DNS name by destination -IP, so conflicting selections are rejected. Browser upload is local-admin only; -there is no ACME, encrypted-key password prompt, certificate download or automated -cleanup. Details and limits: MANAGEMENT.md. - -Cache overrides inherit field by field and use one shared memory budget. The list -and Cache tab offer selective purge. An explicitly disabled global cache can still be -overridden by an explicitly enabled vhost. Path exclusions and sticky-cookie safeguards -remain in force. See CACHE.md. - -The **Transform** tab is a separate vhost opt-in. `transform.enabled` defaults to false -for every vhost even when the global transform engine is enabled. JavaScript, CSS, HTML, -SVG and JSON can inherit or override global format switches, and the extension list may -be replaced per vhost. Eligible origin responses are transformed before shared-cache -storage and before downstream compression. Origin `Cache-Control: no-transform` always -wins and cannot be overridden from the vhost. - -Pages offers a static/maintenance response, a fallback for unassigned paths, and -error overrides. The static/maintenance response can set an explicit HTTP status independently of its HTML body, so an operator can serve a maintenance document as `503 Service Unavailable` instead of returning `200`. Vhosts still retain their configured backend pool; static mode -avoids forwarding but is not a backend-less static-file server. Host/path ACLs run -first. Explicit error-status overrides change presentation, not authorization. -Empty/inherit means use the global error policy; to disable one inherited error -page specifically, configure an explicit replacement rather than expecting null -to override inheritance. See MANAGEMENT.md for exact precedence. - - -### Backend Host compatibility - -New backends use the backend URL host for the upstream `Host` header by default, matching nginx `proxy_pass` behavior. Enable **Preserve client Host** only when the application intentionally routes on the public hostname. This is especially important for embedded/lighttpd-style administration panels that may close or reject requests carrying an unknown Host. Existing explicit settings are preserved during upgrades. - -When an upstream request fails before an HTTP response is received, access logs include a bounded `upstream_error` field in addition to `upstream_status`. - -Multiple explicit hostnames can belong to a single vhost: `suba.local` and `subb.local` share the same routing, backends and configuration. Only the www/non-www companion of the selected canonical hostname redirects; other aliases continue serving traffic directly. +Keep Vhost IDs stable for monitoring/history unless renaming is intentional. Renaming may reset the runtime identity and invalidate incompatible cache objects. Confirm the deployed scheme and Host, HTTPS trust, method/access restrictions, health status and logs after saving. For per-path behavior see [Custom locations](CUSTOM_LOCATIONS.md); for policies see [Access](ACCESS.md); for caching see [Cache](CACHE.md). diff --git a/docs/VHOST_ACCESS.md b/docs/VHOST_ACCESS.md deleted file mode 100644 index baa602f..0000000 --- a/docs/VHOST_ACCESS.md +++ /dev/null @@ -1,136 +0,0 @@ -# Vhost access and visual locations - -## 0.8.0 edge traffic controls - -The Security/Traffic policy section now includes per-client-IP request limits, burst, -window length, country-specific limit overrides, country blocking or allow-only mode, -per-vhost HTTP/3 enable/disable, optional `Server-Timing`, and a Cloudflare real-client-IP -switch. Country rules use the install-time GeoIP country database. Allow-only mode -fails closed when a country cannot be resolved; block mode only blocks resolved matches. -Exceeded limits return 429 plus `Retry-After`; country policy denial returns 403. Both -use built-in HTML pages unless a configured custom error page replaces them. - -Cloudflare mode trusts `CF-Connecting-IP` only when the **direct peer** is inside the -compiled Cloudflare edge CIDRs. An arbitrary client sending that header directly does -not become trusted. The verified address becomes the effective client IP for ACLs, -rate limiting and forwarded metadata. On the same verified Cloudflare hop, a single -two-letter `CF-IPCountry` value is accepted as the country source for country policy, -logging and Traffic analytics. Missing, duplicate, invalid and Cloudflare `XX` values -fall back to the local GeoIP lookup of that verified effective client IP. - -Vhost listener editing is now selection-only. Listener addresses/IPs and transport -settings live under Settings > Listeners; a vhost selects one or more named endpoints. -Existing inline/legacy bindings are promoted to named endpoints when the vhost is saved. - -New backend rows use the HTTPS-by-IP debugging defaults: include in the default LB pool, -allow private origin, HTTP/1.1, preserve client Host and backend TLS verification OFF. -The panel shows the risk and asks before saving an HTTPS-by-IP backend that does not -match the complete preset. Existing backend values are not rewritten automatically. - -Health mode `off` disables active probes only. Passive Backend Health is a separate -opt-in policy and defaults to OFF. With the default policy, normal request failures are -still counted but never mark a backend healthy/unhealthy; an administrator may explicitly -enable passive health even while active probe mode remains `off`. - - -## WebSockets - -`vhosts[].websockets` controls HTTP/1.1 Upgrade forwarding for that host. Existing -configuration omitting it retains the previous enabled behavior. New hosts created -in the panel default to disabled. Disabled valid upgrades return 403 without opening -an upstream connection; normal HTTP continues. Basic Auth/Tinyauth and ACLs still -apply to an allowed handshake. This does not implement WebSockets over HTTP/3, -WebTransport or generalized CONNECT, and does not terminate already open tunnels -when toggled; it governs subsequent handshakes. After a successful `101`, normal TCP -EOF/reset/abort/broken-pipe closure is recorded as tunnel closure rather than an invalid -proxy request; handshake/protocol errors remain errors. - -## Basic Auth and ACL - -The Authentication tab selects None, Tinyauth or HTTP Basic. Basic and Tinyauth -cannot both be enabled. The Basic editor manages users, realm, TLS requirement -and whether verified `Authorization: Basic ...` is passed to the backend. - -Only bounded Argon2id password hashes are persisted. New/changed passwords are -sent to `POST /api/basic-auth/hash` through the protected administrator API; no -password is stored in browser storage, desired configuration, audit or access logs. -An empty password while editing keeps the saved hash. New passwords use the existing -12-128 byte administrator password policy; Basic credentials cannot contain control -characters. Usernames are case-sensitive, up to 128 UTF-8 bytes, without colons or -control characters. This is not an htpasswd/APR1/bcrypt importer. - -TLS is required by default and is checked from the accepted connection, not -`X-Forwarded-Proto`. Plaintext HTTP is allowed only by an explicit host option; avoid -that outside a deliberately protected TLS-termination network. HTTP Basic encodes, -not encrypts, the username/password. Keep the frontend and any credential-carrying -upstream hop protected. Administrator access defaults to loopback; direct remote access is supported when Administration HTTPS is enabled and the bind/hostname policy is configured. - -Successful credentials are removed before forwarding by default. Selecting forward -passes the original **verified** Basic Authorization header, not a password from -configuration or an unverified client value. The final enforcement step runs after -custom request-header rules, which cannot reintroduce removed credentials. -`Remote-User`/`Remote-Email` are stripped in Basic mode; the separate Tinyauth mode -continues to use verified identity headers from that service. - -Global host policies, reusable ACL profiles, vhost ACLs and location ACLs remain -cumulative: access requires all applicable policy scopes **and** successful authentication. -An ACL profile can be defined once under **ACL profiles**, combine default allow/deny -IPv4/IPv6 CIDR rules with country block/allow-only policy, then be attached to several -vhosts. Per-vhost rules remain available and are evaluated in addition to the profile. -Within one policy scope, explicit IP deny rules and blocked countries take precedence. -When country mode is **Allow only**, a listed country or an explicit IP allow rule may -satisfy that scope; therefore `default=deny` can express e.g. private CIDRs **or** selected -countries. An unresolved country still fails closed unless the client matches an explicit -IP allow. This does not let a permitted IP skip another profile/vhost/location policy or -a password. The graphical inline ACL editor still supports default allow/deny and -IPv4/IPv6 CIDR rules. -Trusted-proxy settings control which peer can contribute the effective client IP; -untrusted forwarding headers do not grant network access. - -Authentication runs before static pages, cache, origin connections and upgrades. -401 responses include a Basic challenge and `private, no-store`; custom pages or -header rules cannot convert these failures into successful access. Protected vhosts -bypass shared response caching. A process-wide limit allows two Argon2 workers; -failed-password accounting is bounded to 4096 IPs, with ten failures per minute -blocking that address for the rest of the window. Busy hashing returns 503; a -blocked address returns 429 with Retry-After. This is abuse resistance, not MFA or -a replacement for perimeter rate limiting, especially for many clients behind NAT. - -## HTTP method policy - -Each vhost can leave method filtering **Off**, define an **Allow only** list, or define a **Block** list. Methods are validated as HTTP method tokens and normalized to uppercase. A denied request returns `405 Method Not Allowed` before authentication, cache, DNS or origin connection; allow-list mode also returns an `Allow` header containing the configured methods. - -A Custom location has the same independent method policy. Vhost and location policies are cumulative: a location cannot re-enable a method rejected by its vhost. This makes patterns such as `GET, HEAD, POST` allow-only at host level and a stricter `GET, HEAD` allow-only policy on a read-only location explicit and fail-closed. - -## Custom locations - -The **Custom locations** tab supports ordinary prefix, priority prefix (`^~`), exact (`=`), -case-sensitive regex (`~`) and case-insensitive regex (`~*`) matching. Exact matches win first; -a selected priority prefix skips regex evaluation; regexes are tested in declaration order; the -longest ordinary prefix is the fallback. Exact paths preserve a meaningful trailing slash. - -A proxy location can use the normal pool, pin one backend, inherit/override cache behavior and -apply an additional inline ACL. It can also select a reusable ACL profile, apply its own country -allow/block policy and define an HTTP method allow/block policy. These controls are cumulative with -the vhost policies and are enforced before a backend connection is opened. Path handling can preserve the public URL, strip a prefix, replace a -prefix or perform a capture-based regex rewrite. Query parameters are preserved. Repeated literal -slashes are normalized only for routing/ACL decisions; without an explicit rewrite the backend -receives the original target, including `//`. Encoded separators, percent double-decoding and dot -segments remain rejected. - -A location can alternatively **Return custom response** with a 200–599 status, Content-Type and -body without opening a backend connection. Route/vhost ACL and authentication still apply. A 3xx -return plus a visual `Location` response-header rule is the supported simple redirect pattern. - -Request/response headers have a graphical `set`/`append`/`remove` builder, inline documentation -and a synchronized **Advanced JSON** view. Protected framing, forwarding-identity, request-ID and -WebSocket handshake headers cannot be overridden. The full routes JSON remains available as an -automation/escape hatch, but invalid/unknown fields are rejected rather than silently ignored. - -RustProxy intentionally implements an explicit safe subset, not arbitrary Nginx configuration: -location-level `try_files`, filesystem `root`/`alias`, embedded scripting, arbitrary `if` and -module directives are not accepted. See [Custom locations](CUSTOM_LOCATIONS.md) for examples and -the exact supported subset. - -Reference: HTTP Basic security considerations, RFC 7617: -https://www.rfc-editor.org/rfc/rfc7617.html#section-4 diff --git a/docs/WEB_SERVER.md b/docs/WEB_SERVER.md index 0f2a3ca..c0f709e 100644 --- a/docs/WEB_SERVER.md +++ b/docs/WEB_SERVER.md @@ -1,472 +1,59 @@ -# Web Server - RustProxy 0.25 +# Web Server — static sites, downloads and routing -RustProxy 0.25 provides a Web Server source inside the existing vhost/location pipeline. A Web Service is a reusable configuration resource; vhosts and locations select it by ID and do not duplicate filesystem/PHP settings. +The built-in Web Server serves local files and, optionally, passes PHP scripts to an **external PHP-FPM service**. A reusable **Web Service** is referenced by a Vhost or by a Custom location. Its files and PHP settings are not duplicated in the Vhost. -## Architecture +## Create a Web Service -```text -listener -> vhost -> location -> shared ACL/auth/policy - -> source - |- Reverse Proxy - `- Web Server -> Web Service - |- static file - `- FastCGI -> PHP-FPM - -> shared response/error/header/compression pipeline -``` +1. Open **Web Server → New Web Service**. Choose an ID/name and an absolute document root readable by the RustProxy service account. +2. Set index files (default `index.html`, `index.htm`), whether directory listing is allowed (off by default), MIME handling and download extensions. +3. Add `try_files`, rewrites, response headers and cache rules only if your application requires them. +4. Enable PHP only if you have a reachable, protected PHP-FPM endpoint; otherwise static files do not need any PHP runtime. +5. Save, create or edit a **Vhost**, select **Web Server** as source and choose the service ID. +6. Test index files, static assets, missing files, direct directory access, downloads and headers. -A Web Service can be reused by several vhosts and one vhost can mix Web Server and Reverse Proxy locations. Web Services live in the normal revisioned RustProxy configuration (`web_services`); there is no separate Web Server configuration database. +A Web Service can be shared across several Vhosts. A Vhost may also use ordinary Reverse Proxy as its default and a Web Service for `/assets` or another custom location. Web Server and HTTP reverse proxy use the normal frontend TLS, access, logging and eligible cache pipeline. -### Source-module layout +## File serving options -The Web Server implementation is split under `src/web_server/` by responsibility. `mod.rs` is only the public facade; `config.rs` owns persisted/compiled service configuration, `filesystem.rs` owns document-root confinement and directory reads, `security.rs` owns Web Service hardening, `response.rs` owns static HTTP response/validator/range/listing behavior, `routing.rs` owns rewrites/`try_files`/precompressed selection, `fastcgi.rs` owns PHP-FPM/FastCGI, `serve.rs` owns request dispatch, and `htaccess.rs` owns the compatibility layer. Regression tests live in `tests.rs`. This keeps filesystem/security and FastCGI changes reviewable without growing another single Web Server source file. +| Feature | Behavior / caution | +|---|---| +| **Document root** | Files are served only through the configured root; keep private keys, source secrets and writable control files outside it. | +| **Indexes / listing** | Configurable index priority; directory listing is disabled by default. | +| **`try_files`** | Try requested paths, then a fallback such as `/index.html` or the PHP front controller. Useful for SPAs and frameworks. | +| **Rewrites** | Bounded regex internal rewrites or supported redirect statuses; evaluated in the Web Service routing pipeline. | +| **MIME / downloads** | Built-in type mapping with extensions and explicit attachment options. | +| **Headers / cache** | Service-level headers and optional static cache-control rules; later Vhost/location response rules may override. | +| **Compression** | Dynamic gzip/Brotli/Zstandard/deflate policy and eligible precompressed `.br`/`.gz` sidecars. | +| **File validators / ranges** | Conditional `ETag`/date checks, HEAD and supported byte ranges for eligible files. | +| **Symlinks** | `deny` by default; `inside_root` is an explicit less restrictive policy. | +| **Hardening** | Optional checks for common sensitive/attack paths and removal of selected server-identifying headers; not a full WAF. | -## Live Web Service diagnostics (0.33.0) +### Static delivery (`sendfile`) — 0.34.0 -Each configured Web Service card contains an **always-visible Live metrics** -section (since 0.33.4), styled like the Vhost live metrics: six responsive -metric tiles and a full-width per-service response-rate history chart. It shows -completed requests, responses/s, HTTP 5xx count, mean elapsed time until -response headers, PHP request permits in use/limit and delivered response body -bytes. Static/PHP/other counts appear below the chart. The section needs no -expansion and reuses the existing authenticated live status stream. -Unconfigured/non-PHP services do not claim PHP worker capacity. +In **Web Server → Edit service → Delivery**, `sendfile` supports `off` (default) and `auto`. On **Linux**, eligible full static **GET** responses of at least **64 KiB** over **unencrypted HTTP/1.1** may use a kernel file-transfer path. TLS, HTTP/2, HTTP/3, ranged or transformed content, compressed responses and active object-cache responses keep the normal streaming path. This transport choice should not invalidate otherwise compatible cached objects. It is an **unverified source candidate** in the provided archive: test it on the actual server before use and do not assume a measured speedup. -**Overview** provides 12 additional opt-in Web Server blocks under -**Settings -> Appearance -> Dashboard layout -> Overview cards**. All 12 are -hidden by default, including when opening an older saved dashboard layout. -They follow normal widget reordering, theme, card sizing and SSE updates. -Sample history length is selectable as 60/120/300/600 points (temporary or -browser-persistent), independently of Cache and Transform. +## Rewrites and application presets -The authenticated `/api/status` and compact `/api/status?view=live` payloads -include `web_server_metrics`, keyed only by *configured Web Service ID*: - -```json -{ - "web_server_metrics": { - "site": { - "web": { - "requests": 120, "status_2xx": 100, "status_3xx": 5, - "status_4xx": 12, "status_5xx": 3, - "static_requests": 90, "php_requests": 25, "other_requests": 5, - "response_headers_us": 1500000, "response_bytes": 98000, - "active_headers": 0, - "methods": [110, 8, 0, 0, 2], - "selected_status": [95, 1, 3, 4, 8, 0, 2, 1], - "failures": [0, 0, 0, 0, 0, 0, 0, 0, 0], - "compression": [12, 1, 0, 0] - }, - "php": { - "metrics": { - "requests": 25, "response_headers_us": 750000, - "response_bytes": 24000, - "failures": [0, 0, 0, 0, 0, 0, 0, 0, 0] - }, - "capacity_limit": 32, "capacity_available": 24, - "capacity_used": 8 - } - } - } -} -``` - -The example is illustrative; the actual `php` object is `null` when PHP is -not configured. Array orders: methods `GET, POST, PUT, DELETE, other`; -selected statuses `200, 206, 304, 403, 404, 500, 502, 503`; -FastCGI failures `capacity, connect_timeout, connect_error, request_timeout, -request_io, response_timeout, response_io, invalid_response, body_io`; -compression `gzip, br, zstd, deflate`. - -**Accounting semantics:** The response-kind counters describe the handler -that produced the response; `other` covers early rejections not dispatched -to a static file or PHP. `active_headers` means requests still awaiting their -response headers, *not* live streaming body counts. Mean duration is until -response headers are ready. Body-byte counters advance only when body frames -are polled, **before final dynamic compression**; these are not network wire -bytes. The compression counter records only the downstream encoder, not static -precompressed `.gz`/`.br` sidecar selection. Failed PHP requests may count as -both PHP responses and FastCGI failures. PHP permit capacity is RustProxy's -configured request semaphore, *not* the number of processes in the external -PHP-FPM pool. All counters are process/snapshot lifetime observations and may -restart on a config publication. - -## Dashboard configuration - -The Web Service editor is split into stable configuration tabs so later features do not create one long form: - -- **General** - optional one-shot application preset, ID, name, document root, ordered index files, directory listing and quick hardening switches. -- **Routing** - `try_files` and Web Service rewrite rules. -- **.htaccess** - bounded migration-oriented Apache compatibility, recursive scan and strict-mode behavior. -- **MIME** - per-extension MIME overrides and fallback MIME type. -- **Cache & headers** - default `Cache-Control`, ordered cache rules and fixed response headers. -- **Delivery** - opt-in Linux `sendfile`, precompressed `.br`/`.gz` sidecars, forced-download extensions and Range/streaming information. -- **PHP-FPM** - attach a shared PHP-FPM profile through the selector, open the profile-management modal to expand/edit/create profiles, or choose local configuration for all FastCGI tuning options. -- **Security** - symlink policy, request/response hardening summary and filesystem-confinement notes. - -After the Web Service is saved, attach it under **Vhosts** by selecting `Web Server` and the service ID for the main source or a custom location. - -### Optional native Linux sendfile (0.34.0 source candidate) - -Set **Web Server -> Edit service -> Delivery -> Linux sendfile** to **Auto** -(or `web_services[].sendfile: "auto"`). The default is `"off"` for full -backward compatibility. This option changes only transport; switching it -never invalidates the HOT or COLD object cache. No database migration is needed. - -**Platforms other than Linux (FreeBSD, OpenBSD, NetBSD, DragonFly BSD, macOS and Windows):** `sendfile: "auto"` is accepted as configuration but does **not** activate kernel zero-copy in RustProxy 0.34.0. The Linux-specific bridge is compiled only on Linux; on other targets ordinary Tokio `ReaderStream`/Hyper streaming serves the exact file content, including HEAD, ranges, validators, PHP and existing authorization/cache policies. `"off"` and `"auto"` behave the same on those targets. This describes the `sendfile` feature only and is **not** a claim that the entire application currently passes a cross-platform compile/test suite. FreeBSD/macOS native `sendfile` and Windows `TransmitFile` require separate implementations and runtime tests; Linux `libc::sendfile` cannot be reused directly. - -The kernel `sendfile(2)` bridge is selected **only** if all conditions hold: - -- Linux, plain TCP **HTTP/1.1** (no TLS, HTTP/2, HTTP/3 or QUIC); -- static **GET** of an already-constrained file, full **200 OK** response; -- file at least **64 KiB**, exact `Content-Length`, no `Range`/`If-Range`; -- no negotiated compression/precompressed variant, response transform or - object cache participation, and no rewritten content/encoding headers. - -The file descriptor is duplicated from the already-authorized confined open -file (not re-resolved by path), then attached to a bounded HTTP/1.1 TCP -connection bridge. The bridge sends the physical file bytes using Linux -`sendfile(2)` and never sends the internal marker bytes to the client. If -conditions are not met, RustProxy uses the existing stream/counters unchanged. -Only new cleartext connections pick up a newly enabled adapter; this option -does not change the listener protocol configuration. - -**Release acceptance:** This source candidate has not been compiled or -tested against a running 0.34.0 RustProxy binary in the current environment. -The included Rust unit/integration test and real-network test cases must pass -on a Linux build worker before enabling in production. Sendfile cannot by -itself close the small-response gap to nginx. Live protocol and performance acceptance is still required. - -### Intercept application HTTP errors (0.29.7) - -Open **Vhosts → Edit vhost → Pages** (not the Web Service PHP tab), enable -**Intercept application error responses**, and enter statuses such as `404, 500, 503`. -This opt-in vhost policy applies to response bodies returned by both **Reverse -Proxy origins** and **Web Server/PHP-FPM origins**. It is configured in -`vhosts[].pages.intercept_statuses`; an omitted/empty list disables it and -preserves the application error HTML. Supported codes: 400, 401, 403, 404, -408, 413, 429, 500, 502, 503, 504. - -For each opted-in origin status, RustProxy replaces the *body* using the -vhost's configured error page, then the global error page, then its built-in -error page. The HTTP response status always remains the application's status, -even if the selected page has a custom `status` for standalone error handling. -`Cache-Control: no-store` is set and origin response cookies, compression -metadata and application error HTML are not forwarded. Relevant protocol -headers such as `WWW-Authenticate`, `Allow` and `Retry-After` are retained. -Local RustProxy security/routing failures are not treated as application -responses. If `error_fallback` sends an error to a named internal location, -that response takes precedence and is not overwritten by this option. - -```json -{ - "id": "my-vhost", - "pages": { - "intercept_statuses": [404, 500, 503], - "errors": { - "404": {"kind": "html", "status": null, "html": "

Not found

"} - } - } -} -``` - -This is a **vhost fragment**, not a standalone valid vhost object. A page's -`status` override still applies to locally generated RustProxy error pages, -but is ignored when replacing an application's error body. See also -[Custom locations](CUSTOM_LOCATIONS.md). - -### Application presets - -The editor provides one-shot presets for **Static site, SPA, PHP generic, WordPress, Drupal, Laravel, Symfony, Joomla, PrestaShop and Downloads/file repository**. Applying a preset writes normal Web Service fields; it does not store a hidden CMS mode and every generated field remains editable. Service ID/name, document root and the deployment-specific PHP-FPM endpoint/tuning are preserved. Laravel/Symfony operators should point `document_root` at the application's `public/` directory. - -The preset control also exposes two independent hardening switches. They are saved as normal Web Service configuration and remain active even after the generated preset settings are edited manually. - -## Configuration example - -```json -{ - "id": "main-site", - "name": "Main site", - "document_root": "/var/www/main", - "index": ["index.html", "index.php"], - "directory_listing": false, - "htaccess": { - "enabled": true, - "filename": ".htaccess", - "scan_subdirectories": true, - "strict": false - }, - "try_files": ["$uri", "$uri/", "/index.php?$query_string"], - "rewrites": [ - {"pattern": "^/old/(.*)$", "replacement": "/new/$1", "status": 301} - ], - "mime_types": {"avif": "image/avif"}, - "default_mime_type": "application/octet-stream", - "cache_control": "public, max-age=3600", - "cache_rules": [ - {"matcher": "ext:html", "cache_control": "no-cache"}, - {"matcher": "mime:image/", "cache_control": "public, max-age=604800"} - ], - "response_headers": {"x-content-type-options": "nosniff"}, - "precompressed": {"enabled": true, "brotli": true, "gzip": true}, - "download_extensions": ["zip", "iso"], - "symlink_policy": "deny", - "hardening": { - "block_common_attacks": true, - "strip_unnecessary_headers": true - }, - "php": { - "enabled": true, - "extensions": ["php"], - "endpoint": "unix:/run/php/php-fpm.sock", - "connect_timeout_ms": 1000, - "request_timeout_ms": 30000, - "max_concurrent_requests": 64 - } -} -``` - -Older configurations remain compatible because all newly added settings have safe defaults. - -## Request routing inside a Web Service - -The outer vhost/location router always runs first. Inside the selected Web Service the file-serving sequence is: - -1. validate and decode the already-selected public path; -2. apply configured Web Service rewrite rules in order; -3. apply the compiled `.htaccess` rewrite subset when enabled; -4. resolve `try_files` candidates when configured; -5. open the selected filesystem object within the document-root boundary; -6. execute configured PHP extensions through PHP-FPM or serve a static file; -7. for a directory, redirect to the slash form if required, then test the effective index list; -8. if no index exists, generate directory listing when effectively enabled or return the normal customizable 403 response. - -This is Web Service file resolution, not a replacement for vhost/location routing. - -### `try_files` - -One candidate is configured per line. Supported substitutions are `$uri` and `$query_string`. - -Example for an SPA: - -```text -$uri -$uri/ -/index.html -``` - -Example for a PHP front controller: - -```text -$uri -$uri/ -/index.php?$query_string -``` - -A final `=4xx`/`=5xx` candidate can return an error status, for example `=404`. Non-final candidates are tested for existence; the final path candidate is the fallback target. - -### Rewrite rules - -Rules use a regular-expression pattern and a replacement path. The replacement must start with `/`. - -- without a status: internal rewrite; -- with `301`, `302`, `307` or `308`: client redirect. - -The current query string is retained unless the replacement supplies its own query. Rewritten paths still pass normal Web Server path validation and filesystem confinement. +The editor supports common website presets to fill suggested fields; review their document root, `try_files`, MIME and PHP values before saving. Presets do not install WordPress, Roundcube or other applications. For a single-page app, a fallback similar to `$uri`, `$uri/`, `/index.html` serves the SPA entry for routes without an existing file. For PHP frameworks, use the appropriate front controller; see [PHP-FPM](PHP_FPM.md). ## `.htaccess` compatibility -RustProxy implements a **bounded compatibility subset for migration**, not a complete Apache runtime. The files are read and compiled when the Web Service is validated/saved/applied. They are **not parsed on every request**. After changing `.htaccess` on disk, apply the Web Service again. +An optional bounded Apache-style `.htaccess` compatibility layer supports a **subset** of rules useful for migrations, not arbitrary Apache modules or dynamic directive execution. It can scan configured paths and enforce stricter handling of unsupported expressions. Prefer explicit `try_files`/rewrite/header controls when possible and test migrated sites. A real Apache configuration cannot be pasted unchanged into RustProxy. -Supported directives in 0.25 development: +## PHP and error responses -- `RewriteEngine`, `RewriteBase`, `RewriteCond`, `RewriteRule`; -- `DirectoryIndex`; -- `Options +Indexes`, `Options -Indexes` and the common `-MultiViews` no-op; -- `AddType`; -- `AddHandler` when the handler is PHP-related; -- `Header set`, `Header add`, `Header unset`, including `always`/`onsuccess`; -- `ExpiresActive`, `ExpiresDefault`, `ExpiresByType`; -- `Require all granted` / `Require all denied` outside `Files` blocks. +A Web Service may enable PHP via a shared PHP-FPM profile or its own configuration. PHP is executed by the FPM service, not RustProxy, and must not be exposed as a raw Internet port. Use dedicated pools for different applications. See [PHP-FPM](PHP_FPM.md). -Rewrite compatibility covers the common front-controller patterns used by WordPress/Laravel-style deployments. Conditions can inspect `REQUEST_FILENAME` with `-f`, `-d` and `-s`, plus `REQUEST_URI`, `REQUEST_METHOD`, `QUERY_STRING`, `HTTP_HOST`, `HTTPS`, `REQUEST_SCHEME`, `DOCUMENT_ROOT`, `THE_REQUEST`, selected server/client variables and request headers. Supported rewrite flags are `L`, `END`, `R`, `R=301/302/307/308`, `QSA`, `NC`, `F` and condition `OR`. `PT`/`NE` are accepted as no-ops. `E=` is accepted as a compatibility no-op because HTTP request headers are already forwarded to PHP-FPM. +Vhosts can intercept selected application error bodies in **Pages** and return a configured HTML error page while preserving the status. Internal location `error_fallback` is different: it routes the error to a named internal handler. Error-page replacements are marked uncacheable. -Example front controller: +## Monitoring and issues -```apache -RewriteEngine On -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -RewriteRule . /index.php [L] -``` +A Web Service's dashboard shows request rate, status/error counts, response-header timing, delivered bytes and PHP permit usage (when PHP is configured). These are application observations, not proof of external PHP worker count or final compressed network bytes. -`DirectoryIndex`, `Options ±Indexes`, MIME, header and Expires directives are scoped by the directory containing the compiled `.htaccess`. Subdirectory files can therefore override the effective index/listing/MIME/header/cache behavior for their subtree when recursive scanning is enabled. +- **403 / unexpected block:** inspect document-root permissions, symlink policy, hardening and inherited ACLs. +- **404 / unwanted SPA rewrite:** check actual files, index list, `try_files`, location matching and rewrite order. +- **Wrong Content-Type/download:** check MIME override and attachment extensions. +- **PHP script downloaded instead of executed:** check PHP enablement, script extension and FPM endpoint. +- **Cache not filling:** inspect both global and Vhost cache, static response headers and cookies. -PHP remains intentionally controlled by RustProxy: `AddHandler` may extend the set of PHP file extensions, but the PHP-FPM endpoint, timeouts and concurrency are configured only in the Web Service PHP-FPM tab. Apache `SetHandler` socket/proxy syntax cannot choose an arbitrary FastCGI endpoint. - -The compatibility scanner is bounded to 128 files, depth 16, 128 KiB per file and 1 MiB total. Symlinked directories are not traversed. `.htaccess`, `.htpasswd` and `.ht*` control files are blocked from HTTP delivery. Unsupported non-security directives are logged and ignored by default; **Strict** mode rejects them instead. General `Files`/`FilesMatch`, unsupported authentication/authorization directives, non-PHP handlers and `SetHandler` are rejected even with Strict off because ignoring them could weaken access control or expose source. `ErrorDocument`, `SetEnvIf`, `Redirect`/`RedirectMatch` and `FallbackResource` are not part of this subset. Existing RustProxy vhost/location ACL, authentication, custom error pages and response rules remain authoritative. - -## Index and directory behavior - -`index` is an ordered list and can contain static files and, when PHP is enabled, `index.php`. For every requested directory RustProxy checks the list from the beginning. If no configured index exists, directory listing is used only when enabled. - -Directory listing is disabled by default. A generated listing: - -- opens a fresh directory handle for every request, so repeated refreshes remain correct; -- HTML-escapes names and percent-encodes URL path segments; -- distinguishes files/directories without revealing local filesystem paths; -- sends `Cache-Control: no-store`. - -If listing is disabled and no index exists, RustProxy returns the standard generic `403 Access denied` response. Existing vhost/global custom error pages may replace it. - -## MIME handling - -Static `Content-Type` is selected in this order: - -1. Web Service extension override; -2. built-in MIME table; -3. `default_mime_type` (default `application/octet-stream`). - -The built-in table covers common HTML/CSS/JS/data formats, archives, images, audio/video, fonts and common Office file formats. Extension override keys are normalized to lowercase without a leading dot. - -## Cache policy and Web Service headers - -For Web Server sources, filesystem/OS page cache still accelerates file reads, and -static files retain validators and normal browser cache headers. Additionally, -when global and vhost RustProxy caching are enabled, eligible Web Server -responses pass through the same shared HOT/COLD RustProxy object cache as -reverse-proxy origins. Cache policy requires an eligible GET/HEAD, public -freshness and response-side safety; `Set-Cookie`, `private`, `no-store` and -other ineligible responses are not stored. Vhost response-header rules and -transformations now run for Web Server responses before cache fill. PHP-FPM -responses are protected by these same rules. - -A Web Service can define: - -- default `Cache-Control`; -- ordered `cache_rules` using `ext:` or `mime:`; -- fixed response headers. - -Rules are evaluated before the default. Example: - -```text -ext:html = no-cache -mime:image/ = public, max-age=604800, immutable -``` - -Vhost/location response-header rules still execute later in the shared pipeline and can impose URL-specific policy. Framing/transport headers such as `Content-Length`, `Content-Encoding`, `Content-Range`, `Transfer-Encoding`, connection headers and `Vary` cannot be replaced by Web Service fixed headers. - -## Precompressed static files - -Optional precompressed delivery checks sidecar files only after the original resource has been resolved safely: - -- `file.js.br` for Brotli; -- `file.js.gz` for gzip. - -The client must advertise the matching coding in `Accept-Encoding`. Brotli is preferred when both are available. The response keeps the original file's MIME type and adds the appropriate `Content-Encoding` and `Vary: Accept-Encoding`. - -Validators, streaming and a single Range request operate on the selected representation. Multipart Range responses use the identity representation so MIME boundaries are never mixed with a precompressed sidecar. RustProxy does not compress the file on every request in this mode. - -## Range and downloads - -Static responses support: - -- single `Range: bytes=...`; -- multipart `Range: bytes=0-99,200-299` with at most 16 ranges; -- `If-Range`; -- `206 Partial Content`; -- `416 Range Not Satisfiable`; -- `Accept-Ranges` and per-part `Content-Range`; -- streaming rather than whole-file buffering. - -Single ranges can operate on a selected precompressed representation as before. Multipart ranges intentionally use the identity representation and stream `multipart/byteranges`. - -Configured `download_extensions` add `Content-Disposition: attachment` using an encoded filename. This is useful for storage/download services without changing the MIME type. - -## Validators - -Static files emit metadata-based `ETag` and `Last-Modified` and handle `If-None-Match` / `If-Modified-Since` with `304 Not Modified`. Large files are not hashed on each request. - -## PHP-FPM / FastCGI - -PHP execution is optional per Web Service. RustProxy speaks FastCGI directly to a long-running PHP-FPM process; it never starts `php`, `php-cgi` or a shell command per request. - -Supported endpoint forms: - -```text -unix:/run/php/php-fpm.sock -tcp:127.0.0.1:9000 -tcp:[::1]:9000 -``` - -TCP endpoints are deliberately restricted to literal loopback addresses. The connector has explicit connect/request timeouts and a bounded semaphore. If all PHP execution slots are busy the request receives `503`; connection failures/timeouts become gateway errors and PHP source is never returned as a fallback. - -The PHP-FPM tab also exposes an optional streaming request-body limit (`0` means no additional RustProxy limit), response-header and stderr bounds, an Authorization-forwarding switch, static custom FastCGI parameters and php.ini-style `PHP_VALUE` overrides. Custom parameters use `NAME = value` per line; PHP settings use `directive = value` per line. Core CGI/HTTP/PHP control parameter names cannot be overridden. The in-panel **FastCGI syntax and safety** dialog contains the exact syntax and examples. - -Configured PHP extensions are executable rather than downloadable source. `index.php` and `try_files` front-controller patterns use the same Web Service root and path validation. See [PHP_FPM.md](PHP_FPM.md) for the operator/runtime details. - -## Optional Web Service hardening - -`hardening.block_common_attacks` is a deterministic high-confidence protection layer for the local Web Server. It is **not a generic WAF** and deliberately avoids broad SQL-injection/XSS regex matching that would create application false positives. When enabled it: - -- denies TRACE, CONNECT and TRACK at the Web Service source; -- blocks hidden dot/control paths except `.well-known`; -- blocks common secret/configuration targets such as `.env`, `wp-config.php`, Joomla `configuration.php`, Drupal `settings.php`, Composer auth/config metadata and private-key file names; -- blocks common backup/database suffixes (`.bak`, `.old`, `.sql`, `.sqlite`, `.db`, editor swap files and `~` backups); -- blocks high-confidence exploit-probe paths such as exposed PHPUnit `eval-stdin`, Laravel Ignition execute-solution, Apache server-status/server-info and `cgi-bin`; -- suppresses the incoming `Proxy` header when building FastCGI parameters, mitigating the CGI/FastCGI `HTTP_PROXY`/httpoxy class. - -Existing decoded-path/traversal validation, document-root confinement, `.ht*` protection, vhost ACL/authentication and PHP source protection continue to run independently of this switch. A denied hardening request uses the normal customizable 403 pipeline. - -`hardening.strip_unnecessary_headers` removes common implementation/fingerprint response headers after Web Service and `.htaccess` response processing: `Server`, `X-Powered-By`, ASP.NET version headers, `X-Runtime`, `X-Generator` and Drupal cache fingerprints. Vhost/location response rules run later and can intentionally add an operator-controlled header if required. - -## Filesystem security and symlinks - -The request path rejects malformed encoding, encoded separators, backslashes, control characters and `.`/`..` traversal before filesystem access. - -On Unix, filesystem lookup starts from an opened document-root descriptor and walks relative components with `openat`. - -Two symlink policies are available: - -- `deny` (default) - every path component is opened with `O_NOFOLLOW`; symlinks are rejected. -- `inside_root` - Linux-only; symlinks may be followed only when the opened descriptor resolves back inside the canonical document root through `/proc/self/fd` verification after every component. - -`inside_root` is not available with PHP-FPM in 0.25 because PHP-FPM reopens `SCRIPT_FILENAME` itself. PHP therefore requires the safer `deny` policy. - -The secure Web Server implementation remains Unix-only until an equivalent race-resistant confinement implementation exists for non-Unix systems. - -## Shared RustProxy pipeline - -Web Server dispatch occurs after the existing vhost/location selection and access controls. Responses continue through the existing common mechanisms, including: - -- ACL, authentication and authorization; -- rate/access policy; -- vhost/location custom and security headers; -- custom error pages; -- access logging and per-vhost Traffic metrics; -- response processing/compression where applicable; each Web Service can inherit, enable or disable dynamic compression independently of its precompressed `.br`/`.gz` sidecar policy and can filter individual codecs; selecting Disabled hides/disables those codec selectors without clearing their saved values, while globally disabled codecs stay unavailable and the minimum size remains global; -- TLS and existing HTTP/1.1, HTTP/2 and HTTP/3 listeners. - -A local Web Server request does not create a reverse-proxy backend connection. WebSocket upgrades remain unsupported for a Web Server source. - -## Validation limits - -Current configuration limits include: - -- index files: maximum 16; -- MIME overrides: maximum 64; -- cache rules: maximum 64; -- fixed Web Service headers: maximum 32; -- `try_files`: maximum 16 candidates; -- rewrites: maximum 32 rules; -- download extensions: maximum 64; -- PHP extensions: maximum 16; -- PHP concurrency: 1..4096; -- PHP connect timeout: 50..60000 ms; -- PHP request timeout: 100..3600000 ms; -- `.htaccess`: maximum 128 files, depth 16, 128 KiB per file and 1 MiB total. - -Document root must be absolute, exist and be a directory at configuration compile time. References to unknown Web Services are rejected and deletion of a referenced service is blocked/caught by validation. - -## Remaining follow-up - -The current implementation intentionally leaves multipart Range, named locations, `PATH_INFO` script splitting, full Apache `.htaccess` semantics and non-Unix confinement for later work. These additions must preserve the existing Web Service / vhost-source separation. +Keep all document roots and external application processes separately patched and secured. diff --git a/docs/screenshots/README.md b/docs/screenshots/README.md deleted file mode 100644 index 1612fef..0000000 --- a/docs/screenshots/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# UI preview policy - -Only two legacy simulated previews are retained as narrow visual references. They were generated with injected HTML/CSS/JS and simulated API/fetch/storage, so they are **not current Rust runtime evidence** and may differ from the 0.22.3 UI. Runtime verification status belongs in [../TEST_REPORT.md](../TEST_REPORT.md). - -- `login.png`: anonymous minimal Polish login document. -- `location-headers.png`: request/response rules for an individual location. - -RustProxy 0.19.1 removes the other old simulated screenshots so stale layouts are not presented as current product state. diff --git a/scripts/preflight.py b/scripts/preflight.py index 2fa308e..d1e099c 100755 --- a/scripts/preflight.py +++ b/scripts/preflight.py @@ -74,15 +74,15 @@ def check(root: Path, repair_known: bool = False) -> list[str]: flat = module.parent.with_suffix(".rs") if flat.is_file(): 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/rates.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/session-resume.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/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "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/rates.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/session-resume.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", "CHANGELOG.md", "ROADMAP.md", "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/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/MONITORING.md", "docs/CUSTOM_LOCATIONS.md", "grafana/rustproxy-overview.json"]: if not (root / path).is_file(): - raise ValueError(f"Incomplete RustProxy 0.31.9 source tree: missing {path}") + raise ValueError(f"Incomplete RustProxy 0.34.0 source tree: missing {path}") # The Reader uses compile-time include_str! for its bundled Markdown. A # dangling registration is a real compiler error, unlike an omitted old # release audit. Match both reader_doc! and the expanded include_str! form. registry = root / "src/admin/documentation/documents.rs" if not registry.is_file(): - raise ValueError("Incomplete RustProxy 0.31.9 source tree: missing src/admin/documentation/documents.rs") + raise ValueError("Incomplete RustProxy 0.34.0 source tree: missing src/admin/documentation/documents.rs") embedded_docs = set(re.findall( r'"(docs/[A-Za-z0-9_.-]+\.md)"', registry.read_text(encoding="utf-8") )) @@ -101,7 +101,7 @@ def check(root: Path, repair_known: bool = False) -> list[str]: raise ValueError("src/admin/dashboard_presets.rs starts with an inner doc comment; use /// instead (Rust E0753)") text = (root / "src/lib.rs").read_text(encoding="utf-8") if '#[path = "auth/mod.rs"]' not in text: - raise ValueError("src/lib.rs is not the 0.31.9 entry point; extract the complete package, not individual files.") + raise ValueError("src/lib.rs does not match the expected entry point; extract the complete package, not individual files.") return messages diff --git a/src/admin/documentation/documents.rs b/src/admin/documentation/documents.rs index 815b41a..1faefcf 100644 --- a/src/admin/documentation/documents.rs +++ b/src/admin/documentation/documents.rs @@ -1,4 +1,5 @@ -//! Embedded Markdown documents for the authenticated Reader. +//! Embedded current-version Markdown guides for the authenticated documentation Reader. +//! Keep this list in sync with docs/README.md. The embedded files must exist at build time. macro_rules! reader_doc { ($path:literal, $category:literal) => { @@ -11,264 +12,32 @@ macro_rules! reader_doc { } pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[ - ( - "README.md", - "project", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md")), - ), - ( - "ROADMAP.md", - "project", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/ROADMAP.md")), - ), - ( - "CHANGELOG.md", - "project", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/CHANGELOG.md")), - ), - ( - "docs/ACME_PROVIDERS.md", - "guides", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/ACME_PROVIDERS.md" - )), - ), - ( - "docs/ACME_RFC2136.md", - "historical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ACME_RFC2136.md")), - ), - ( - "docs/API.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API.md")), - ), - ( - "docs/API_ACCESS.md", - "technical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/API_ACCESS.md")), - ), - ( - "docs/ARCHITECTURE.md", - "technical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/ARCHITECTURE.md")), - ), - ( - "docs/CACHE.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CACHE.md")), - ), - ( - "docs/CACHE_DIAGNOSTICS.md", - "technical", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/CACHE_DIAGNOSTICS.md" - )), - ), - ( - "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", - "historical", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/CHANGELOG_TECHNICAL_ARCHIVE.md" - )), - ), - ( - "docs/CONTAINER.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/CONTAINER.md")), - ), - ( - "docs/CUSTOM_LOCATIONS.md", - "guides", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/CUSTOM_LOCATIONS.md" - )), - ), - ( - "docs/DOCKER_DISCOVERY.md", - "guides", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/DOCKER_DISCOVERY.md" - )), - ), - ( - "docs/ERROR_LOGGING.md", - "technical", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/ERROR_LOGGING.md" - )), - ), - ( - "docs/FILE_LOGS.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/FILE_LOGS.md")), - ), - ( - "docs/HTTP3.md", - "technical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/HTTP3.md")), - ), - ( - "docs/INSTALL.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/INSTALL.md")), - ), - ( - "docs/INTEGRATIONS_040.md", - "historical", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/INTEGRATIONS_040.md" - )), - ), - ( - "docs/LIVE_LOGS.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/LIVE_LOGS.md")), - ), - ( - "docs/MANAGEMENT.md", - "historical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MANAGEMENT.md")), - ), - reader_doc!("docs/MEMORY_STRESS.md", "technical"), - ( - "docs/MIGRATION.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MIGRATION.md")), - ), - ( - "docs/MOCK_AUTH.md", - "technical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_AUTH.md")), - ), - ( - "docs/MOCK_SERVER.md", - "technical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/MOCK_SERVER.md")), - ), - ( - "docs/NOT_FOUND_HOSTS.md", - "guides", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/NOT_FOUND_HOSTS.md" - )), - ), - ( - "docs/NPM_MIGRATION.md", - "guides", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/NPM_MIGRATION.md" - )), - ), - ( - "docs/PHP_FPM.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PHP_FPM.md")), - ), - ( - "docs/PHP_FPM_ISOLATION.md", - "guides", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/PHP_FPM_ISOLATION.md" - )), - ), - ( - "docs/PROMETHEUS.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PROMETHEUS.md")), - ), - ( - "docs/README.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/README.md")), - ), - ( - "docs/REDIRECTS.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REDIRECTS.md")), - ), - ( - "docs/REFERENCES.md", - "technical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/REFERENCES.md")), - ), - ( - "docs/RENEWAL.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/RENEWAL.md")), - ), - ( - "docs/ROADMAP_HISTORY.md", - "historical", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/ROADMAP_HISTORY.md" - )), - ), - ( - "docs/SECRET_STORE_BACKUP.md", - "guides", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/SECRET_STORE_BACKUP.md" - )), - ), - ( - "docs/SECURITY.md", - "technical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SECURITY.md")), - ), - ( - "docs/SECURITY_AUDIT.md", - "audits", - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/docs/SECURITY_AUDIT.md" - )), - ), - ( - "docs/SETTINGS.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/SETTINGS.md")), - ), - ( - "docs/STREAMS.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/STREAMS.md")), - ), - ( - "docs/TRANSFORM.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSFORM.md")), - ), - ( - "docs/TRANSPORT.md", - "technical", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSPORT.md")), - ), - ( - "docs/VHOSTS.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOSTS.md")), - ), - ( - "docs/VHOST_ACCESS.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOST_ACCESS.md")), - ), - ( - "docs/WEB_SERVER.md", - "guides", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/WEB_SERVER.md")), - ), + reader_doc!("README.md", "project"), + reader_doc!("CHANGELOG.md", "project"), + reader_doc!("ROADMAP.md", "project"), + reader_doc!("docs/README.md", "guides"), + reader_doc!("docs/INSTALL.md", "guides"), + reader_doc!("docs/CONTAINER.md", "guides"), + reader_doc!("docs/CONFIGURATION.md", "guides"), + reader_doc!("docs/VHOSTS.md", "guides"), + reader_doc!("docs/CUSTOM_LOCATIONS.md", "guides"), + reader_doc!("docs/ACCESS.md", "guides"), + reader_doc!("docs/WEB_SERVER.md", "guides"), + reader_doc!("docs/PHP_FPM.md", "guides"), + reader_doc!("docs/CACHE.md", "guides"), + reader_doc!("docs/RESPONSE_DELIVERY.md", "guides"), + reader_doc!("docs/TRANSPORT.md", "guides"), + reader_doc!("docs/STREAMS.md", "guides"), + reader_doc!("docs/ACME_PROVIDERS.md", "guides"), + reader_doc!("docs/DOCKER_DISCOVERY.md", "guides"), + reader_doc!("docs/INTEGRATIONS.md", "guides"), + reader_doc!("docs/DASHBOARD.md", "guides"), + reader_doc!("docs/MONITORING.md", "guides"), + reader_doc!("docs/LOGS.md", "guides"), + reader_doc!("docs/BACKUP.md", "guides"), + reader_doc!("docs/MIGRATION.md", "guides"), + reader_doc!("docs/SECURITY.md", "guides"), + reader_doc!("docs/API.md", "guides"), + reader_doc!("docs/API_ACCESS.md", "guides"), + reader_doc!("docs/TROUBLESHOOTING.md", "guides"), ]; diff --git a/src/admin/tests.rs b/src/admin/tests.rs index 27bbaae..13d4f23 100644 --- a/src/admin/tests.rs +++ b/src/admin/tests.rs @@ -2370,10 +2370,10 @@ mod tests { .any(|document| document["path"] == "docs/PHP_FPM.md")); assert!(documents .iter() - .any(|document| document["path"] == "docs/SECURITY_AUDIT.md")); + .any(|document| document["path"] == "docs/SECURITY.md")); assert!(documents .iter() - .any(|document| document["path"] == "docs/TEST_REPORT.md")); + .any(|document| document["path"] == "docs/TROUBLESHOOTING.md")); store.close().await; } #[tokio::test] diff --git a/src/admin/web/index.html b/src/admin/web/index.html index aa3c72a..48c4a68 100644 --- a/src/admin/web/index.html +++ b/src/admin/web/index.html @@ -3571,8 +3571,11 @@ + + Opt-in for static GET files at + least 64 KiB on Linux HTTP/1.1 without TLS, cache, compression or transform. Other requests + use safe streaming; switching this setting never clears the cache.