75 lines
8.0 KiB
Markdown
75 lines
8.0 KiB
Markdown
# RustProxy 0.27.3 implementation note
|
|
|
|
## Scope
|
|
|
|
0.27.3 aligns HTTP/3 `HEAD` cache handling with HTTP/2, reduces long-lived cache/rate-limit memory, excludes the rebuildable managed Python runtime from backups, promotes package repositories as the normal installation path, and includes maintenance fixes for restore ownership, startup listener overrides, strict WebSocket 101 framing, resilient update checks, scaled sidebar layout and automatic GeoIP maintenance. SQLite schema 8 and the COLD cache on-disk format remain unchanged; existing saved configurations remain compatible because the new `geoip_updates` object is defaulted when absent.
|
|
|
|
## HTTP/3 HEAD cache parity
|
|
|
|
HTTP semantics do not make `HEAD` meaningfully different between HTTP/2 and HTTP/3. RustProxy already allows `GET` and `HEAD` in cache policy, but the forwarding path actively probed an initially unknown request-body stream only for `GET`.
|
|
|
|
The HTTP/3 receive body starts with an unknown end-of-stream state until it is polled. A body-less `HEAD` could therefore reach cache policy as `body_empty=false` and be bypassed with `request_body`. HTTP/2 happened to expose end-of-stream immediately for the same curl request.
|
|
|
|
0.27.3 runs the existing bounded `empty_for_cache` probe for both `GET` and `HEAD`. A genuinely body-bearing request is still bypassed, while a body-less H3 `HEAD` follows the same cache lookup path as H2.
|
|
|
|
## COLD cache RAM
|
|
|
|
The COLD tier stores bodies on disk, but its old in-memory index retained a complete response `HeaderMap`, origin validators, a duplicate 32-byte cache key and a 64-character SHA-256 string for every object. With many small COLD objects, these metadata allocations could become a material RSS component even though body bytes were no longer resident.
|
|
|
|
0.27.3 keeps the fields needed for matching, invalidation, dashboard rows, LRU and freshness in the COLD index, but removes the persistent header/validator copies. On a real COLD read RustProxy reads and validates the existing metadata JSON, materializes headers/validators for that request, verifies the body digest and then promotes/serves the object as before. The in-memory digest is stored as 32 raw bytes and the map-owned cache key is no longer duplicated inside `ColdEntry`.
|
|
|
|
This trades one metadata-file read on a COLD hit for substantially lower long-lived metadata RAM. HOT behavior, COLD disk format, invalidation fields and response semantics are unchanged.
|
|
|
|
## Rate-limit/IP retention
|
|
|
|
The previous limiter keyed every client bucket by `(revision, vhost String, IP)`, duplicating the vhost allocation for each IP. Expired buckets were normally retained until the global 65,536-entry bound caused cleanup.
|
|
|
|
0.27.3 groups client-IP buckets below a single vhost key, stores the active revision once and removes buckets whose fixed window has expired. Cleanup runs opportunistically during requests and from the existing periodic runtime-maintenance loop, so idle traffic no longer leaves expired IP state resident indefinitely. Sparse client/vhost hash tables are shrunk after reclamation and the allocator trim follows the periodic sweep. Removing an expired bucket is behavior-equivalent to the existing next-request reset: the next request starts a new fixed window with count zero. The global client-bucket bound remains 65,536.
|
|
|
|
## Compression RAM assessment
|
|
|
|
Downstream compression remains streaming through `tower_http::compression::Compression`. It does not collect the complete response body before encoding, and `CompressionLevel::Fastest` remains selected. The long-lived cache does not keep encoder state. Compression memory therefore scales primarily with the number and codec type of concurrently active compressed responses rather than the number of cached objects or historical requests.
|
|
|
|
No compression algorithm or negotiation behavior is changed in 0.27.3 because the existing implementation is already bounded by streaming response lifetime. Brotli and Zstandard can have larger per-stream codec state than gzip/deflate, so concurrency remains the main compression-RAM factor under high load.
|
|
|
|
## Backup scope
|
|
|
|
`acme-python` is a managed/rebuildable Python virtual environment, not authoritative application state. Backups now skip both `acme-python` and the underscore spelling `acme_python`, while certificate/configuration/secret state remains covered by the existing backup logic.
|
|
|
|
## Installation paths
|
|
|
|
The documented normal installation/update paths are:
|
|
|
|
- DEB/RPM packages: `https://rustproxy.linuxiarz.pl/`
|
|
- OPNsense plugin: `https://rustproxy-opn.linuxiarz.pl/`
|
|
|
|
The Debian 13+ source bootstrap remains available as an optional source/development path.
|
|
|
|
## Managed ACME bootstrap diagnostics
|
|
|
|
The managed ACME runtime now distinguishes private-environment creation, pip preparation, Certbot package installation and final plugin validation failures instead of collapsing all of them into one generic setup error. The selected stage is also emitted as a warning when no distribution Certbot fallback is available.
|
|
|
|
On distributions enforcing PEP 668, the application-local `virtualenv` bootstrap first uses normal `pip --target`; if the distribution rejects that operation, RustProxy retries with `--break-system-packages` while still writing exclusively into the private bootstrap directory under the RustProxy data tree. This does not install or modify packages in the distribution Python environment.
|
|
|
|
## Restore ownership and production data directory
|
|
|
|
The production CLI default is `/var/lib/rustproxy`; `dev.sh` explicitly sets `RUST_PROXY_DATA_DIR` to repository-local `./data`. During restore, RustProxy records the UID/GID of an existing target data directory and recursively applies that ownership to the validated staging tree before the atomic rename. This prevents a root-run restore from converting service data to `root:root`; symlinks are ownership-adjusted with `lchown` and are not followed.
|
|
|
|
## GeoIP background maintenance
|
|
|
|
`Config.geoip_updates` defaults to enabled with the sapics latest/checksum release URLs, 256 MiB maximum file size and a 24-hour interval. The worker starts after successful listener binding, attempts immediately, compares publisher SHA-256 files with local files and downloads only stale assets. Database payloads are streamed to private staging files with a transfer limit and incremental SHA-256, the staged pair is parsed before publication, and the in-memory GeoIP database is atomically replaced without a proxy restart. Failures are fail-soft and retry later. The worker and managed manual updater share `.geoip-update.lock`; that runtime lock is excluded from backups.
|
|
|
|
## Listener, WebSocket and control-plane startup fixes
|
|
|
|
Explicit CLI/systemd HTTP/HTTPS binds now replace the corresponding canonical saved `http`/`https` bind rather than appending a second listener that can leave `:80`/`:443` active. Strict WebSocket response validation accepts `101` with no `Content-Length` or exactly `Content-Length: 0`, while rejecting positive lengths and any `Transfer-Encoding`. The release-version checker starts only after all sockets bind and logs polling failures as warnings, keeping network/DNS availability outside the proxy startup success path.
|
|
|
|
## Scaled administration sidebar
|
|
|
|
The desktop shell/sidebar height compensates for CSS `zoom` via `100vh / --ui-scale`. At scales below 100%, the flex sidebar therefore still spans the visual viewport and its bottom runtime information remains at the bottom edge. Mobile rules continue to use automatic height.
|
|
|
|
## Source-tool separation
|
|
|
|
0.27.3 also separates application-coupled scripts from auxiliary tooling without changing proxy behavior. `scripts/runtime`, `scripts/build`, `scripts/deploy` and `scripts/migration` contain code directly tied to application/build/deployment workflows. Compiler-independent checks, mocks, audit probes and the retired Python backup helper moved to `tools/`. OPNsense and RouterOS helpers moved to `integrations/`.
|
|
|
|
The shell entry points, `build.rs`, embedded ACME source path, source upgrader, deterministic source archive and release checks were updated together so the relocation is part of the same 0.27.3 development release.
|