diff --git a/AGENTS.md b/AGENTS.md index e8aecc5..d604467 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ 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.27.2. +compatibility. Current source version: 0.27.3. ## Delivery diff --git a/CHANGELOG.md b/CHANGELOG.md index 6112be2..d7b10e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ > test-only changes and packaging implementation notes were moved to > [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md). +## 0.27.3 + +- 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. diff --git a/Cargo.lock b/Cargo.lock index 7dfe58c..cc5705b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1688,7 +1688,7 @@ dependencies = [ [[package]] name = "rust-proxy" -version = "0.27.2" +version = "0.27.3" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index e7d56d5..eaccbb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-proxy" -version = "0.27.2" +version = "0.27.3" edition = "2021" rust-version = "1.88" description = "RustProxy - independent reverse proxy with local administration" diff --git a/README.md b/README.md index 647812f..cb62e73 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# RustProxy 0.27.2 +# RustProxy 0.27.3 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.27.2 release source.** +**0.27.3 release source.** ## Layer-4 streams @@ -11,14 +11,18 @@ Raw TCP/UDP forwarding has its own **Streams** page in the primary navigation, a Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners. -## What changed in 0.27.2 +## What changed in 0.27.3 -- Fix ACME issuance and renewal with the RustProxy-managed Python environment introduced in 0.27.1. -- Run managed Certbot through the final venv interpreter (`acme-python/bin/python -m certbot`) instead of the pip console script whose shebang still points at the temporary `acme-python.installing` directory after the atomic rename. -- Validate the managed runtime through the same module launch path, so an existing 0.27.1 `acme-python` directory starts working immediately after upgrade; no manual venv deletion/rebuild is required. -- Keep the distribution Certbot fallback, SQLite schema 8 and saved configuration format unchanged. +- Treat body-less `HEAD` requests consistently on HTTP/2 and HTTP/3 cache paths. HTTP/3 `HEAD` now gets the same bounded empty-body probe as `GET`, preventing false `request_body` bypasses. +- Reduce COLD-cache RAM usage without changing the on-disk cache format: response headers and validators are loaded only on an actual COLD read, SHA-256 digests are stored as 32-byte values in the in-memory index, and the duplicate cache key is removed from each COLD entry. +- Reduce rate-limit/IP memory retention by storing each vhost name once, reclaiming expired client buckets during traffic and periodic maintenance, and shrinking sparse hash tables before the existing allocator trim runs. +- Exclude the rebuildable managed Certbot environments `acme-python` and `acme_python` from application backups. +- Keep response compression streaming with no full-body aggregation. Compression uses the fastest codec level and keeps only per-active-response encoder state, so it is not a long-lived cache-like RAM owner. +- Make the DEB/RPM and OPNsense package repositories the documented official installation paths; Debian source builds remain available as an optional path. +- Separate application-coupled scripts (`scripts/`) from developer/operator helpers (`tools/`) and external platform integrations (`integrations/`) without changing runtime behavior. +- Keep SQLite schema 8, the COLD on-disk format and the saved configuration format unchanged. -0.27.2 retains the 0.27.1 first-admin, managed-runtime UI and route-list sorting changes plus the 0.27.0 proxy/compression and Docker-publication behavior. Historical details are in [CHANGELOG.md](CHANGELOG.md). +0.27.3 retains the 0.27.2 managed-ACME repair and earlier administration/proxy behavior. Historical details are in [CHANGELOG.md](CHANGELOG.md). ### Historical 0.26.7 release notes @@ -36,7 +40,7 @@ Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost - 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/migrate_npm.py`, and fix the container example to mount the complete `scripts` directory. +- 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. @@ -86,7 +90,7 @@ Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost - 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. -The detailed 0.25.5 implementation audit remains available as historical baseline evidence. Current 0.27.2 verification status is maintained in [docs/TEST_REPORT.md](docs/TEST_REPORT.md). +The detailed 0.25.5 implementation audit remains available as historical baseline evidence. Current 0.27.3 verification status is maintained in [docs/TEST_REPORT.md](docs/TEST_REPORT.md). ## Documentation @@ -118,9 +122,19 @@ Read [installation and update](docs/INSTALL.md), [host access and locations](doc Examples are disposable full configurations: applying one replaces, not merges, an existing document. -## Requirements and first build +## Installation and source build -### Debian 13+ one-line install/update +### 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. Building on Debian is +kept as an optional source/development path rather than the primary installation method. + +### 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 @@ -164,9 +178,9 @@ CARGO_BUILD_JOBS=1 CARGO_PROFILE_RELEASE_LTO=false \ CARGO_PROFILE_RELEASE_CODEGEN_UNITS=8 cargo build --release --locked ``` -Ready Debian packages are published separately at `https://rustproxy.linuxiarz.pl`; Debian package-build metadata is intentionally not shipped in this source tree. +Package-build metadata remains outside this source tree and is maintained with the official package repositories above. -The 0.27.2 source archive preserves the resolved `Cargo.lock` supplied with the source tree and +The 0.27.3 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 @@ -381,7 +395,7 @@ bash dev.sh ``` The supported operator backup is the password-encrypted `rust-proxy backup create` -command. `scripts/backup.py` is retained only as an offline release-test helper and +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). @@ -398,6 +412,16 @@ behavior when omitted. Once a 0.25.3 configuration containing the new fields is 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/checks`, `tools/dev`, `tools/audit`, `tools/legacy` are auxiliary tools and are not needed by the running proxy; +- `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), @@ -407,12 +431,12 @@ well as the old source, not just the binary. ```bash # Rust-independent checks: -python3 scripts/check_source.py -python3 scripts/release_smoke.py +python3 tools/checks/check_source.py +python3 tools/checks/release_smoke.py # Runs those checks first, then the Rust suite when Cargo is available: bash dev.sh test # Source-only package, with no Git checkout required: -python3 make_zip.py /tmp/RustProxy-0.27.2.zip +python3 make_zip.py /tmp/RustProxy-0.27.3.zip ``` ## New management and test-origin guide diff --git a/ROADMAP.md b/ROADMAP.md index 7e66896..fe0399d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,18 @@ # RustProxy roadmap +## 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 @@ -18,7 +31,6 @@ Current direction: 0.27.0 -> 0.27.1 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.27.0 - Compression policy and persistent Docker publications @@ -32,7 +44,6 @@ Current direction: 0.26.7 -> 0.27.0 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.26.7 - Cache occupancy and backend rotation continuity @@ -45,7 +56,6 @@ Current direction: 0.26.6 -> 0.26.7 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.26.6 - Access-policy composition and administration fixes @@ -53,10 +63,9 @@ 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/migrate_npm.py` and fix the container helper example. +- [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.26.5 - Scaled controls and listener navigation @@ -66,7 +75,6 @@ Current direction: 0.26.4 -> 0.26.5 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.26.4 - Administration consistency and backend rotation reliability @@ -79,7 +87,6 @@ Current direction: 0.26.3 -> 0.26.4 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.26.3 - Administration appearance and draft workflow polish @@ -98,7 +105,6 @@ Current direction: 0.26.2 -> 0.26.3 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.26.2 - Unified logs search and dynamic fields @@ -114,7 +120,6 @@ Completed direction: 0.26.1 -> 0.26.2 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.26.1 - Vhost-aware protocol diagnostics and Logs Explorer UX @@ -137,7 +142,6 @@ Completed direction: 0.25.7 -> 0.26.0 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.25.7 - Vhost operations and backend observability @@ -149,7 +153,6 @@ Current direction: 0.25.6 -> 0.25.7 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.25.6 - Backend memory diagnostics visibility @@ -158,7 +161,6 @@ 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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.25.5 - Traffic country consistency and Revision History styling @@ -171,7 +173,6 @@ Current direction: 0.25.4 -> 0.25.5 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.25.4 - Configuration history restore @@ -181,7 +182,6 @@ Current direction: 0.25.3 -> 0.25.4 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.25.3 - Request policy, correlation and configuration history @@ -193,7 +193,6 @@ Current direction: 0.25.2 -> 0.25.3 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. ## 0.25.2 - Live backup and resumed-session hardening @@ -204,7 +203,6 @@ Current direction: 0.25.1 -> 0.25.2 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on the release host. ## 0.25.1 - Traffic UX and source modularization @@ -215,7 +213,6 @@ Current direction: 0.25.0 -> 0.25.1 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on the release host. ## 0.25.0 - Web Server foundation diff --git a/SHA256SUMS b/SHA256SUMS index 93be8f7..53f0077 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,18 +1,18 @@ 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example -9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore -4595fb01446f9e9e08a459085dea96c2b27a9f0aff0fab1f40c8a62e95761902 AGENTS.md -49f5f9b0490a8b411f16f8a1a2477e96f832d6d21155727bb80c4895f9877ea1 CHANGELOG.md -3002fea4dac2c78c11003b6ef669e53415702337062ab737958ac48471e24422 Cargo.lock -5823157f89f6d8576a038866ff5bc2be8733f226e345f4ff9be68d1b59d1d34c Cargo.toml +48a06923863f33c6a350dd66da8677437d184f4797b0a813930f5daa8cb23141 .gitignore +b17968dc24b2841ee7bf107f54e488a858afd888efea4bc89ec8ad5941dc494a AGENTS.md +dca9fc8a1eb1a4171e92b695b2eb58896c8701dbbdc3acbfc44b380d8a9fb5ab CHANGELOG.md +948e0848bc426a15a40e7dab02817242417d8633ca9c3d89a7dc8e8da23c4d62 Cargo.lock +1a27b4fc0950feac78dd61695131211a16179cc47dab327c3d556a3b3109ccb8 Cargo.toml 75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile -3ef21bb67c050fb7189a032e55e4794c4e07a1b20bcdc3f2f6222f5a37fa8fb5 README.md -e62a31c08cc123b39031e95d10d149d36dbccf73a78a18a7578bb88521c89983 ROADMAP.md +1725a09ad0dd2adff1aa32f125207fa4bfa6cbf051acbd3683c66e5961e6ad65 README.md +b6874ca7ddab94c40ddc0da3455e02e6e429292a092ff7152da88490c9cad9fc ROADMAP.md da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml -928ba6a504bc431ae100c6e587861e5088b28822d2cb19da5d437b39d26c864a build.rs +f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs 385ac7970fa94f043d0bcf04a91184e6e2c807587b85e55517ed064bb4bddba6 deploy/README.md 31d4e0cd454060b46f359fdb84a14b16a5fe0d3baabb387dd584d76e555558bb deploy/rustproxy.service -c90c8a484ff15bb516a810c01e19260ba941b652d5d67afc4405f444ab3b06ab dev.sh +8b8adb663f9b72e3f8303f4b68d3f401b4fda82f38e0d9e5f6b2c21919ebca1b dev.sh 5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md 7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go 5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example @@ -21,46 +21,46 @@ bf687a851374d8f1fa59d050f3f76532f7c12f16a6daa7d3c36102c2d9a8ec9c docker-agent/d 9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh b68234472bec5cedd67c7cfe1de7d8806a965dfd4101f944aa6320132f18c14e docker-agent/main.go 3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go -4e9eb90294d936e0e4f63618bbadd54b8063ec73d4beda9aa4d70d768b16ab97 docker-agent/model.go +c49dae9fd82dbad4db0f377e9d2b54debb5fde2a83a08fbae30cc942c79e04ac docker-agent/model.go 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 c7b75ed137094cade2b2047c1c8d304e936e0fe1121ff91a858f77c2e969fa39 docs/API.md -d7b224425930cf06cf34d0547e445e0363f6ac4aced43e182d1d88faa2d28cdb docs/API_ACCESS.md -0f7f159ad5b4b5277f80c19f3c47f4e7823ea439ef02e5913e1391f1b34d129f docs/ARCHITECTURE.md -86469d17f9786c3e6f20c66791b47e10e11695d2f1a88c66c9ded157a585c85f docs/CACHE.md +557d82ec1cf2295d6a41ffed1d862448c1f9441554f1fc07f8a12324f168901d docs/API_ACCESS.md +4c5288c9ed427cfb919bd023ed9a321dcf6d64bc4cad3e1768393356c5d835ba docs/ARCHITECTURE.md +4abdcc53efdb35cdd8feb024dbb81188da12c6abd37eefddecdb57a7eb93d6e2 docs/CACHE.md 6266163e17d2921468abdae92c1620266af8351321530e2c9b7dca6f3dd89c88 docs/CACHE_DIAGNOSTICS.md c6003dcbc0b95e2cfa9523b7017186cc8b4826248b588af77111ac5eb15174f9 docs/CHANGELOG_TECHNICAL_ARCHIVE.md -0a92a2d4ee88d02e9b460c680305680227c3ef82cf6b57d6f92b1ef5a6433154 docs/CONTAINER.md +17d9d2275d2838477e7542b164ebd41ad118ef06afdaccde36c95f8a522e5939 docs/CONTAINER.md 9538a52d508d8d7053c1387c42eef60e6d5dc1879c300b6fe951b3374c16884c docs/CUSTOM_LOCATIONS.md 16d8f59908c66e595d6003495cbf5c068c68b10aa9495eb8612c38b2de0e4a7c docs/DOCKER_DISCOVERY.md 9ea8db4d034ecb573976dd323da29f044538525c230df9f43a47be1ac867b24c docs/ERROR_LOGGING.md -5c46b311f48e143a3e43e61a69c8bc3cfad7748c7dfef87bd76df333b64023bd docs/FILE_LOGS.md -df6ecd4a50796640243676ad4b3a87789f6e5d20cbebea6255e92fbfff1c7131 docs/HTTP3.md -10f378674dced022f063bfc1b15dc9a5cfc0afd03639deb4db5de091c139288c docs/INSTALL.md +851e66e5dac47cfc70d963833d137fa0e35c04c374e9b943ed9b7345a51802fa docs/FILE_LOGS.md +8dfc89a8b3512103b87864bd7c24a3dc3de4465edf8f9874e6bed1f4634dd930 docs/HTTP3.md +5b48bc23ffa2dbd1ebd2ed5cae9e6022dc9c0795a2d0df9565816c11c0096421 docs/INSTALL.md 3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md 4bb56add544f15d54695cf797cd16a51c09024360cdeef803a88988f4a54b3a5 docs/LIVE_LOGS.md 7c35c4cdbe7bbb81d94f3bf6aeb22b8a866af3e7f0d62bbedc299e4d6f264e0d docs/MANAGEMENT.md -5f98c4006e89b432904e0b65ece6aa5df4befd6dc8e00024eb67b4ad901ba14d docs/MIGRATION.md +9583b0b69f81320e2227f44a853259e7a600846bbed681ac54896d83befd7b3b docs/MIGRATION.md 5f090a190000c6badab2a74e5d4968e70c34de43b59c1452f5b05503b5c5c18a docs/MOCK_AUTH.md 1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND_HOSTS.md -0c2241828e01b12f749562e2ac206a4519908fff63f46eba207b1cffa932dc5b docs/NPM_MIGRATION.md +89a1af6c9fe10c572b211f9632ce256166e67990b5dacacf2ae07265d99a5172 docs/NPM_MIGRATION.md 712cb1cbf84515384b081fd2929e86e40af7c10f65bc69919b78b7b3e2fd12b5 docs/PHP_FPM.md 572c70fbd2f61fecd80a8ff9fa5de4a94c4f158de5e90da8e920f99b051241fa docs/PROMETHEUS.md -a5faed54574e23cc5940205883a9731aa1572c2499783ecba138a621fcfb4a42 docs/README.md +4cf425aabd16366df99e447d3eba2a362babeff3de7d014e189df51def657039 docs/README.md 6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md 0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md be661dbf81af840d4d8aeeb5347c24c7faecdecc73a8cf0f6f09053f1abe04c1 docs/RENEWAL.md -585ef0ac4492e03adca46949823e5a7538f56d9768090cd4dc080bfce4305b52 docs/ROADMAP_HISTORY.md -4bf7b3ea0bfe6c6b0ae0b7884b48b10ba29ff3a71611ec96957a3327d3c6ec88 docs/SECRET_STORE_BACKUP.md +a2bb837cdd3559d58158880c1f832badf64035376a3623050bb4e40c2bca104c docs/ROADMAP_HISTORY.md +2b760c88e0e093d7705820560fbacf30bfc7bf6d67f2e68a500b3d4d970d80b6 docs/SECRET_STORE_BACKUP.md 5dbdf45fddba198a28f511dbbd2f12bf7d2b88c41c3cbedbc20dfea3858779d3 docs/SECURITY.md 620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md 632976125cbd9e96e91d1771331c73d1ac5adaa796476a20f8546971787c2529 docs/SETTINGS.md 3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md -6a6f48614fcf64b675ef0b7efb49f65878da3f42a6ee110f0d608c70e737be2a docs/TEST_REPORT.md +cbf3e7cfd32c50026df333489a8afb5056e873a9360bbe135b5ded5fadc4318b docs/TEST_REPORT.md cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md 8ee6187a5460ca45643b7fd789e27bffd264449e3a92ff70e9359edee011bbbb docs/TRANSFORM.md a1e9c6d38d21067d1ce7115ca3395ae8ff1f6a4c40fb0ebf00a6940ef79df439 docs/TRANSPORT.md @@ -75,15 +75,15 @@ ab7d48c2a359928da653263e69b4739cfff849a03fa1270491cb1ce553989788 docs/V0.23.1_A e4d47b50fb0ce33d26817fc0afcc2f05ac53bc9b62c7e50e61b9fa9734e817b0 docs/V0.23.3_AUDIT.md 984092eeb3899ed696f94059d88f7834703b151682d5c8af9fabacc7a823ea0b docs/V0.24.0_AUDIT.md 5782adf99a292783149882e844ad07b49d4ac0ea478e8393758cff849d48c13d docs/V0.24.1_AUDIT.md -521d6ef17bb0df7ec05f068411ed96a19e579c68b8e166dc314878d2c3cb0627 docs/V0.24.3_AUDIT.md +cc64ae2b85add081deb043f144caa4f10f6576475eada65302a56296ce2d75a1 docs/V0.24.3_AUDIT.md b891db1453cd7cf6b10f79879f4edaf7c83f14e91e4e95826919d40cb5dca2d4 docs/V0.24.4_AUDIT.md 8d6519b00e01e5cd41ee6772941fdc7e9a70fe29626df71cefced6eefe6a94d0 docs/V0.24.5_AUDIT.md -4d2d8b84b38df57f5c6a03995019f8418806fe5867ee34ce192a90ef561986a8 docs/V0.24.6_AUDIT.md -081f655554c62911b81719343e905f012356bf1df3054bda3ccd1a9bdffa89af docs/V0.24.7_AUDIT.md +f6ffaa9b72d86029b1629f8fd89efeac99780831f95d434b2ad549913633dcb9 docs/V0.24.6_AUDIT.md +1c9f3ab84700482820379268ea367600a0c110a4578504f0cc2ce54149fe1046 docs/V0.24.7_AUDIT.md d0e609febbcd1cfdb8ea0b00b4fa6f081eac01e2d43a81e38a53ae25783152cc docs/V0.24.8_AUDIT.md -6131ef0ac65af2c9544d9d44ecfb680587cb358b37efd9b2d1cf69f26e935861 docs/V0.25.0_AUDIT.md -1a8a07eb150100e6d5ab8443dac664038e1d838033609df168aaf60a15aeb84f docs/V0.25.0_SECURITY_PREPROD_AUDIT.md -32c4053d48e8f24eb6c3395a4bbbc18f1c9790601166c73fb83173a14142789e docs/V0.25.1_AUDIT.md +a840652a660adeb93999c01ae285f01fd13cdecff69f3ae58ddf9ade9acca258 docs/V0.25.0_AUDIT.md +e5a08bf5d34338a3e5f3b14b65c6f3630ccb0ef9c56c26f39bbb73fa724bf227 docs/V0.25.0_SECURITY_PREPROD_AUDIT.md +a709ec5e740b3da73149ba9843e3a184611f8be74e06bd19105684a57e3e68f8 docs/V0.25.1_AUDIT.md 41793fe2d0fa4a2bd88448f5b3870649f08eeaadd4cd420820647d332e7fdb5a docs/V0.25.2_AUDIT.md 9dbb66e408bcd4d32267724e557fa9f7f44ef9b70db4ab170c61a7a839b7b157 docs/V0.25.3_AUDIT.md 583d548b4a5db9f35a2bbf8ec00ec22397764c5850a8e883a3d12680da517df7 docs/V0.25.4_AUDIT.md @@ -93,11 +93,12 @@ bf9b4cf4fec2c8ce287e1b3c2e7d4828e101dafe4d6c173f099819f22493d144 docs/V0.25.5_A 57edc3d5ca6c268b2b6b2e8027e81fb543c0dab86e8f69c4ee5776ea8df901c7 docs/V0.26.3_IMPLEMENTATION.md da5ec87b9482f2fc506c4334d9365f3bfaec89254a14e5585dd6460326b20e10 docs/V0.26.4_IMPLEMENTATION.md 0ddf5215c40c8366844350bc6462d8013dba747b6f3e8fbf477985af215e5e8d docs/V0.26.5_IMPLEMENTATION.md -dd9b5dcdbd53a04fd32458caaf03b56aa335f0153b1a0e332fdf230fec12680e docs/V0.26.6_IMPLEMENTATION.md +c7785826cf7b20555ece5b23a3f360ce4a8a014cf976c14336770f74d630834f docs/V0.26.6_IMPLEMENTATION.md f9667fe81b31762e64d36126a7e94b609316dfa856de8f8e3b96661dd2a918c6 docs/V0.26.7_IMPLEMENTATION.md efd5935852d4cfd09758bf67c70e437490cfce2baade46c8733201e506dbcc42 docs/V0.27.0_IMPLEMENTATION.md 9ba6417417a0eb293879b26b019449f143e773d1482434bee88f5eaf1fe34b90 docs/V0.27.1_IMPLEMENTATION.md -6aafee24d7badabe00e23554c8c1ec868f586e09f2d915e9f48422639a7348fb docs/V0.27.2_IMPLEMENTATION.md +845cc7b7fcd553ef99613817e70fcecc4e50dd8fa77066a75577d63a7224cdec docs/V0.27.2_IMPLEMENTATION.md +86cf7498a031ed6aff8a79140767eaa07b696669623900b971f66ca5392e5174 docs/V0.27.3_IMPLEMENTATION.md bc2ac0117528ed4616c68cf7fe05cb1d9bbd36ecaa8573828cb3b3136923f265 docs/VHOSTS.md 09b60fb7b40db4d0139fa2a9e69a2aa9f28d4e61b8643521cfc7e936d7df8774 docs/VHOST_ACCESS.md 4026a4adeee70f98a96cc7aa8d8be16cda42ffb2e5890d38f1ba638134ab0675 docs/WEB_SERVER.md @@ -112,14 +113,23 @@ b26833cb4251f9390a44192ecc827ff7f052fd19016a5ccf7498c94503ab4ce3 examples/mock- 5a8f3a8a054abc89c6e78896b9fe2f903212199a24dc3d2d2bcbd0696a31bcde examples/transport.json d242de9614fbb52933b4accd32f4715ca94107abe4a3b1d7e088362b07a6ba60 examples/vhosts-balanced.json af2bdb1343a8141069e94ccd2c7f92574ad848437e2f9fdb5c7576a9a18ef4a5 examples/vhosts-private.json -cc99b14927c696adc17c9d0689041798f0a99cbc372b35a81652d6e38af69311 grafana/README.md -b6401012c55a9cdf7ca7ac5f90b4142c623230903877309e427142de1a81f9d0 grafana/rustproxy-overview.json +89e53a8f7098bbbe64a5315dde365a4346ef102b0a8e457b941cacf7b843e04d grafana/README.md +e87ac9f20310c265ce580c0adcef6403728452f98f12ddba603859ff0dd34458 grafana/rustproxy-overview.json fe1fc76bcb639cac0609a8969cd92725cfd77d87f1d8519756ef16825e04914a install-debian13.sh -858b39fa6886b71d20690bf369e76580922364b3cc7f0cf6ba9b040df9cd76e3 install.sh -29cada0936762f26495064d1524a668262ed729b21f20aebddc8b6e5cfea363b langs/README.md -917efd1646d06ac34c5729ce5a569482d7512cf285dfc603bca24b3608d36e7c langs/en_US.json -bab5f124ee1e1e823fe310217560c2abaff8a07c9c23a4daf7ab0f60d766eb4b langs/pl_pl.json -300b8cbb49e44d6ce11a4445df01b1a7467b81acfafa9c2d2ba62b5a611501e4 make_zip.py +909e9ab94617ace74a425f15084bf85f3086cc52ba5b28f2b441ff800abc6ef9 install.sh +42f9f1d18c021fc7734b5bd020844bf678725a0e539e2fcbe05ec676f6239eae integrations/README.md +2d4c859aae5ba357a6d30024c8487ae8f59f01b192711f13f9c2f8fee0f48b50 integrations/opnsense-acme/README.md +c73b814a42bb4ff703441dbd177ee625055eeb115f9b5bb0c2150aede573a159 integrations/opnsense-acme/opnsense-rustproxy-cert-deploy.sh +45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 integrations/routeros-oci-build/.env.routeros.example +cdb15e23f04b4a330b7ab8869eb38da72b3c27729fa7fcf164e3d9e8e893a651 integrations/routeros-oci-build/Dockerfile.routeros +7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e integrations/routeros-oci-build/Dockerfile.routeros.dockerignore +cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/routeros-oci-build/README.md +96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh +3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh +71cde48daa538431dfac51e438b1ed23b061abfdb4e144ba704237d75ac6270c langs/README.md +d0aa8b66b39b1f0835e558aa6e4c9d860bc9a26187aff887463cff74397b3dc1 langs/en_US.json +69f8b2c88a04f8a5d32290df35040e1619513dfb873a9ef4bc5dfbd655ce3316 langs/pl_pl.json +113458f12c516f1e4516a341d6e558cee8a621ac16ca23e9bbd865ccde9e1f75 make_zip.py e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql 5c62da30fa9db594a4d656c77ae676d3c388072b6a85f91170ec7d911d7cde43 migrations/0004_admin_preferences.sql @@ -134,43 +144,17 @@ ee50f38c037765fab742e888d4b9fc4d874c31c785c6064d4eca3d2c4d24d944 nagios/check_r 1465396cf1358067e9fe48b3d61f28fd4ac65c577b67c502950ce6b42ecbe3b3 nagios/check_rustproxy_latency 6e036f736a2b40feb78eec17bf34871771524190f283591d1c89e48e6d2271e6 nagios/check_rustproxy_queues 7458b2d74495e6fb1c1edeea4d5a793705476431dadb60627187111f91441e13 rust-toolchain.toml -5d4ae015f1c8199c2ed02b34576b74ec6f2b3b7a90c8d7178e5070a850bb6e72 scripts/acme_provider.py -e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/acme_rfc2136.py -a5b4451e45ce975685772aa8409b64a57a20a7548466584c3ea7fb975e64729c scripts/admin_audit.py -20e83f8b6347b819d1d1ea3de5c691c36f0816954a4210b0d3f39557fefe1931 scripts/backup.py -6e656850ca0bafae0aeb2fdf3b8f7e9342b027830df8e4ca19190a8b1cfeb7a9 scripts/check_source.py -187ee92c8029cdd59e89b4447cf4d170e856017a2091ceb85ae7c22dd1a79c72 scripts/deploy.py -6960ff75a235289d270a9d8e7205a4432b51d3bca83e2c599bd20bcc90caac81 scripts/deploy_core.py -f014472588e889488976970dbaa95a8745b6907516aa81a7cca21d0f81eaaad3 scripts/deploy_runtime.py -fc1a74246478cb477b3892812634cb28f8b8ad45604a098327f7a14a60f25c96 scripts/docs_check.py -d391681dc3f6786552ca44929979286a5c76a2b86785635871b59d2eb4d73389 scripts/langs_check.py -2e35b7c78887fd56f83ca9017caa168630963a9f47da71dfc53b1a8f9ad436c2 scripts/memory_contracts.py -2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migrate_npm.py -a459d95bbb0076f33ade8b273b813308167f9d8d45242b309a2d18320ca0093c scripts/migrate_npm_api.py -31ccad3ec3c880119f3b7002f0c6652f8b43634edad8ab3763cb0e4497d0bb1f scripts/migrate_npm_core.py -d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a scripts/mock_auth.py -2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 scripts/mock_server.py -2d4c859aae5ba357a6d30024c8487ae8f59f01b192711f13f9c2f8fee0f48b50 scripts/os-acme-client/README.md -c73b814a42bb4ff703441dbd177ee625055eeb115f9b5bb0c2150aede573a159 scripts/os-acme-client/opnsense-rustproxy-cert-deploy.sh -65ee67092955aae5216c304453f88066ad9f51dc27e682f36866540eebbf621b scripts/preflight.py -1df736690e3de94f1ddc363ecef9e0e86acee4e98a78c30b82936324f4747484 scripts/release_smoke.py -45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 scripts/routeros-oci-build/.env.routeros.example -3b48cff8506e5a6ae7d3876f47721f5de7d63e2e02264a78cf5b6ddd52d9fbc6 scripts/routeros-oci-build/Dockerfile.routeros -7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e scripts/routeros-oci-build/Dockerfile.routeros.dockerignore -68c076ef4537a047a7c390c534dbadd90e00cf495931c6d739f1c28b6b4dc7df scripts/routeros-oci-build/README.md -d53b2ae7315ac143bd17d6327611a39e7c23666408bb95e39da2ca8517322ccb scripts/routeros-oci-build/build-routeros-oci.sh -3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a scripts/routeros-oci-build/docker-entrypoint.sh -d36a5f5532f7d458b4e8249acf0fdf733b3c9531e9a590ea8d1b1dddec8fa7ef scripts/rust_module_contracts.py -ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed scripts/rust_source.py -1c83054336b3508d7ad72cd259f039394682642439877fe69639c0b76be09154 scripts/security_contracts.py -295b6f2625b1875950ea3eecccd142f49be327afdd00c92641286e6de530ec51 scripts/security_dependency_contracts.py -6fefb5c459aacdef3dcabe9c5e6e7718a2145a0190b165c29d7a46216ac53bad scripts/source_module_contracts.py -df2bffcd729a998c5c7ec5452235cc07e834d8f86017d65ccd0b1754d91d11b0 scripts/swagger_assets.py -538b5f6cfa738355a729940ee3d92e7ab3dc25ca69ae330ddaa08f831775b8ba scripts/test_compression.py -45a6a4cca69419da94b2389d73d3f1eb42ccc4aafa5825da14242eb236bdd963 scripts/upgrade.py -0aaa80453e25aae7ea953fadb91b3ae3eaff627977c81c9ab5a2779f9a93582f scripts/web_assets.py -12d2e324b2685f2d20d7bde4b68d2cb1565a3b5aff49e30850238e05d57bff9f scripts/web_server_contracts.py -c245f8f832bd2c36a3fbd5e66fe98e7367c70a8db840c79e6b640f1d9d74f2ac scripts/web_service_ui_test.mjs +2145ad17ae7a823756371ceab6bdbeb332211df93193ffb4e486f7a80c4f29ae scripts/README.md +783afca6730accdeb74c29dd0a05123562e0fafc5f8479be6e7ba0feaf80a923 scripts/build/swagger_assets.py +187ee92c8029cdd59e89b4447cf4d170e856017a2091ceb85ae7c22dd1a79c72 scripts/deploy/deploy.py +475aecff3e44a12199a29099a8c5069c37d880ff0307cdfe7af8ff66e532bb16 scripts/deploy/deploy_core.py +148b5270c8f9bb78c9dfa783c6ca6ddcd572b5ab0389568f780058662f03032e scripts/deploy/deploy_runtime.py +83c730c499b5099f11d967a0c8cb12aaa595bda60f13e22d7f61bf63a756d8a5 scripts/deploy/upgrade.py +2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migration/migrate_npm.py +a459d95bbb0076f33ade8b273b813308167f9d8d45242b309a2d18320ca0093c scripts/migration/migrate_npm_api.py +31ccad3ec3c880119f3b7002f0c6652f8b43634edad8ab3763cb0e4497d0bb1f scripts/migration/migrate_npm_core.py +20124173452b4514bad8850c1637569a9a191882478451e549bd16b432605fc1 scripts/runtime/acme_provider.py +e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/runtime/acme_rfc2136.py ad62f73bb42906d617cc1b479f02d4648c7324cb89fc11d2c6aa6bf13774eec9 src/acl.rs ea272433e7f92e983509a8ab1aa54242440abef21df526c2f99944ef279e09b9 src/acl/tests.rs 6f5d3137bf24451da05c1db9f0f9e2a451305db17b6bc2590815ad54a6511829 src/acme.rs @@ -178,18 +162,19 @@ ea272433e7f92e983509a8ab1aa54242440abef21df526c2f99944ef279e09b9 src/acl/tests. 3f158f46e80b403b658b4e9b1e3710e38c70e47b2d476b8223d9c394ffeeba95 src/acme/model.rs c5334de6c732d76b2bea7bfc17b3ac305a250bbaad31a5420d6a7d7abb200449 src/acme/providers.rs b9e57db4bd80ef37564dfc533a96897d8a494671d53acea9fb4bf28d23ba370d src/acme/providers/tests.rs -f8c4bb96814f669be8be40d013a6217c32f66943de0961ad827a184be6879645 src/acme/runtime_env.rs +29ff0647417b0f1d50a4cc075ab51afecff9ddf47db3eb7948d51db5a38f33f3 src/acme/runtime_bootstrap.rs +cbf221cd6695d8cf1f03da3aa2b42ed4b1afbfbc2e496329b2d29f7cc59ce0c8 src/acme/runtime_env.rs 7acbaf34e7b953b753ab074893e47b5024f0cd756dc4c7a1074067dd936c2e8e src/acme/tests.rs 55ef7ceb44c25c7a8ddcbfd5e0d4b647526f9bdcde901ee08db5314e4d055b39 src/admin/access.rs d18efa3f797f62b3916265e30ba546725a41e7ccfd9b123965ff9f522e3baa88 src/admin/certificates.rs 1de4c7c116eeab2452ccccbaba3f2b7b63c6eb1c1e146ab870d19c5a5ca16f80 src/admin/documentation.rs -e8892cca11b3d686d1e5f6ef091a01d7b28cf4289993876a0a4d91bedb751ec4 src/admin/documentation/documents.rs +09c05dc6ca92b2e6b32d1bd8e92739aa31b850abe04d7aef711d80828750cfa5 src/admin/documentation/documents.rs 67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs 002922c91aa07cbea17cb0ee11127bb6e7e015de108ee032761b34507dcfa06d src/admin/file_log_search.rs d3210e037481e1f95e840550428e088ffd83ec9a42f1810e4c976298fb555a9a src/admin/file_logs.rs beb8be241e4f1a8cea7b6c4e02b42f0359fb1fff10ab5ce2c70ccd0e63a554b0 src/admin/logging_api.rs 58802528128f53e493f7339063433a747b605007a8842b5efc670132d897d6f7 src/admin/mod.rs -01bde7e69062ad85449a03c05188c8af4299952921993e9828f9c03439cff104 src/admin/openapi.json +28611fe61da40ab45769055707235dca3ec5e0b1eaad81f130206ff7b9a7819f src/admin/openapi.json a53309daf118381031122ca70764f7a2f92adc44af3fc91df5f946e6856f76ae src/admin/operations.rs 5287fff6b3ad436ac695e9b66feb1b0b5705b744a9f00cec7ea6a3e951e5eebf src/admin/preferences.rs b4fa393b780b2676b5d72e0f90cd7290292f424b8062d4b5983e722d6ca72c32 src/admin/public.rs @@ -227,7 +212,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/ 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg af6e7a2e890df2fb7385a374bcb3fba52fb5a53dbe18adc1fb7865660fcba842 src/admin/web/file-logs.js b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js -9e236b9a2e9505a0eda738e4cec92efb57b1db60288499e0bf797cb67df7eecf src/admin/web/index.html +be1d4004d7188e92931dec34f49c1b8491f1d137573621176aae66217fdffb0b src/admin/web/index.html 24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js 7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json af0fbd94d9b88ce1cce28e39dfd2c35de031adcba412dd4a9a17d9b416c256dd src/admin/web/login.html @@ -249,9 +234,9 @@ b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files c96a87e82ff0c1c9ce3d0cd5135903bb53af78d1c4fb6ebfe5fea503bf5644ea src/auth/store.rs 745226cabb007819370fc6aa680995a03a8a13fa057dacfab0fcc21d0c43bbe4 src/auth/tests.rs b0efb68dec4b0e8611684457b21044d136882ddb5c529f934061a55d377d9b28 src/backup.rs -96fea1da6ba7803d6d532ae8abe4157bc0329a4236e40c0c21a77ddc520b9f11 src/backup/archive.rs +ebce56a154c57bf20d6618de161359c7c49392d7d24e8d23428b7d78a7a060c1 src/backup/archive.rs 4d7beea1c3d7c65ee6018d22af6461369754c67475b219d3474a85476cd0b57a src/backup/snapshot.rs -5ea9e176dbd5a212438e19fe8abbb0b35ac1d6e1920cbc9bb1b327e4d1e2dcc8 src/backup/tests.rs +c0561c2ebcb67b20ab2aacd18f61337269b437dce32dbb11bd0bf01a4e204f78 src/backup/tests.rs c8ee6afde0e463828757a9c2aca25506eb0a7b3b9369f4ec955857ab04b1f9d1 src/backup/validation.rs 426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs 3d4b3986f54bed99212063b6d135d40a202d556a3e4d253e24c74be4cb9e8ebc src/balance/health.rs @@ -267,9 +252,9 @@ ccb3e1988d6ae5825450e894eea896d53ae9fac150d0175ea5e742413e55105a src/basic_auth 9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs 7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs a9eeb93519aae2de7a1282296ced91d2eb8307cc966f8fbf6085af0fd21b0ad2 src/cache/cold/cleanup.rs -019e7632e09c6f8202d5d919b293f6eb102e1b339e7043fcdbf6b680e8841aca src/cache/cold/demotion.rs -5de20afb2ecb865d60ebf6593c1dc5167941e0ef812458e6da3bd48f7fd360dd src/cache/cold/startup.rs -5c132e98bcc3fd40e71e8c9666c78e47bbdf801958737e30e6a16ef34f6e324e src/cache/cold/worker.rs +206809de4dfdf2f100762e91e6d964a6fa03879dd225d5801f5fd91fe47b3ff1 src/cache/cold/demotion.rs +9fc18368e2ce278ef0ad2b73ff016aa24936c2186be371ca4010a1f6f63bb0ed src/cache/cold/startup.rs +4615ec97504fb0d41f2259595fdc96d272aeb30451c05b07d4f4fb1578472e6f src/cache/cold/worker.rs d51fa4f320be56c572f767617428c3f3838543cb0eeb7d1f8ae791e1a1ce9a71 src/cache/collapse.rs 27a35689fef639f1aea00d9f5a86adc981c0c304dc3e28e6da40494dffbb6bfa src/cache/dashboard.rs bbd6de8568fea79cc4a11d33b905af75713e609c8f37ec943c82f0d180e66dcc src/cache/fill.rs @@ -278,14 +263,15 @@ c1357a13658e1ed5158084d3a41ba9ea906512774a5863ce9c56cb508d3fa6c8 src/cache/inva 7ed64ac876e3be828401e493f08a8d54f28ea75d2c2ff156987848f9dc6097a9 src/cache/invalidation/invalidate.rs b74945c7460b76ed7afcb02a45ede09d76f08e0703c47920b9b9d794d4b74980 src/cache/lifecycle.rs bd831807ca4720b3a41a6ea1d9a6c0ed2d935225e2dacbd9313be064f2e4a20e src/cache/lookup.rs -108c8e3adfa0e35a3b4daa2d1e94f69278d4ddadc3b6c4d4d9d751298a23b91d src/cache/lookup/hot.rs +88f6210c03d7a8e7a35be1f123c9c15675af6e6a599c50db927d99866851b634 src/cache/lookup/hot.rs 1779abcee77600c13050a2882f896a5b35625b1ae53737c2554179492ce0e40d src/cache/lookup/insert.rs f3e026a3c0e953dca3e5c9def43d864a7706afcd7cbeeaf0f21c059a335b30cc src/cache/lookup/tiered.rs 58c03fde09e5eab40968679793b364f4f1cf5fd832bd79ebcdc92bb23871ad82 src/cache/mod.rs -f2ce12806e9572f0ac03277d38d9feafa7ed6a045ef010434e33069271ddd6a1 src/cache/model.rs -1fafec16201ff94ed52b00697a5ec53fb87a838f62e82fed157fdbc74cb9156e src/cache/model/cache.rs +58ede9780b70ac46fe8b35b6a85f02f56f9a2c75b045e3435062cceb3921f3aa src/cache/model.rs +ba94226addfd6cdab67d666dc15b8412c4d117111e44639e503e89a95175d0a2 src/cache/model/cache.rs +0ec4f6d0adea1ed80a3ca17c68c9c85502491da2904d9d690b4169ab9b025b21 src/cache/model/cold_metadata.rs dac97eb28052795b7acbbbab1eaf94e8dc1186b07a7e0c4b7dc522af6de02574 src/cache/model/entry.rs -3dd0a5f2bafab4a67a15e76c544bd123eb564aa33e20defbc9e338ef4507da81 src/cache/model/maintenance.rs +09a4417f82984eca8c040ea28eb82b3f129c347d951d4b4fab704bb194d9b985 src/cache/model/maintenance.rs 36d471e4b417f1eaffe1fd39bdaa64b6a6aa68faf58b26fe42d6b977f08769d9 src/cache/model/settings.rs 55dcc0ad84d6b73689ba43e759d5685c406419c7733e23e78d5b072891ab17d7 src/cache/model/views.rs e26dce02df9c043329ca164fed215d271781bce8fb03240f528331bf3df6b9d7 src/cache/policy.rs @@ -295,7 +281,7 @@ c46f335cf75b91b728125596242a9682a7ef3dbe86242dde7fc038790984a6e4 src/cache/poli efde2bd046ed073900cd3e67051f0542c802a06e8f5e38c2c20ee92604080c36 src/cache/tests.rs 13dab7b7a587cfc146826a92ab94ec9e04f7e500b7a84e678d77226829844923 src/cache/tests/cold_tier_tests.rs b4f609ee900c556b613136bbd3fce11c242a415d277ac3d25bdcb72275b66832 src/cache/tests/cold_tier_tests/part_01.rs -fb3074fa87b19a66db66842f2ab3a0728646293ca2154ab26f7865a58cda56ac src/cache/tests/cold_tier_tests/part_02.rs +f43aa6c231f2c85ee3390dddab74aab2eea2a3f874acb35f145c23866266cb52 src/cache/tests/cold_tier_tests/part_02.rs 8bcbd6e8158620c4e0c056b2872e0b5f786d5f2bc2e2203351b8416a94641f09 src/cache/tests/dashboard_tests.rs 6c69ef40567d34671d26231358e2db74cc26ec0113843303caa98d4dc0fbf1f9 src/cache/tests/scope_tests.rs 578b18cac918ba51fad775eca188f645b228500e59eb1790f783a0b8d0245b7b src/cache/tests/tests.rs @@ -306,7 +292,7 @@ cdc41a7241739b89297494323f29e0230fa06abbd74e219fedfda11df1db4334 src/cache/test 541a62b749440aa1099cf0fc8a2bd31eac0c082ccef35df22c1e049e7928e216 src/cache/tests/v022_cache_tests.rs 15e85357c3ddf4fc2283d621f5f4cd8ca309275e06677020299d4105f0475949 src/cache/tests/v022_cache_tests/part_01.rs 7ee652a781081c0b53d39dff213c2bda38e405a18a3d0b908f97cd3ac51d556f src/cache/tests/v022_cache_tests/part_02.rs -f9a3c4f0c7363819a0904860531078df89e2ee1ed5bcff7047986e40c9f38a0c src/cache/tickets.rs +a8a0ce446eb53ffdb8f8d97efa3e1344410dc3798b25f62b8817e8e160d28940 src/cache/tickets.rs 3fc79b09474fe23b5b6fc98e4a376469afb02a41d6cc2dbbba59dc6091c790c8 src/compression.rs e2f58279499b7c3b47c65d3a7814bb5c7cd6506544d939875fbb0f91fa9aa59d src/compression/tests.rs 7921ae451ccd65d647165b02919bd97399399da471454a6a1a1f63e682b76fc8 src/config.rs @@ -346,11 +332,11 @@ a16613114273c54b6d2a16a94fa8748550e91468585469642622bd2c86087baa src/logging/fo 17425ebbca6bbbb0dcaeb65ebc44b1e51488a4092b7b6add556e764c6d486d6a src/logging/tests.rs 3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs c66a9803e43bdd87101825e8c71ab33f890ac4c3428467ba5d43c17fdfa1439e src/main/admin_runtime.rs -04084e87167a8c47a24d5a4e89e0bde0f30398ea95c38fe34c057a1149ac33e8 src/main/bootstrap.rs +e1ca82cf6f0360176323b32907a5c8dd0646d661ed75a935af003c0b1ed691cd src/main/bootstrap.rs 5a98d0e249338e73aa57ae2e352dc16db2f79c2d042c1544ce1c2022da76a31c src/main/proxy_runtime.rs 4dd7c1084a6214c0f2513ebf86fe8ef144470695557772bfe25979edd64491ab src/main/shutdown.rs 0a6578a0f4023b9f8d73043bf942dc5e096ef9afb8c0d2f131c14f65d08ac01d src/main/shutdown/tests.rs -7e97938fd9209f2335af5d6304abcb4193e80bb33e4ccf480938e8c3ab4644d6 src/main/tasks.rs +b26945f6405493df85dffddab3ca8804137e6cb99c6d165eaaf0ad024cb570ff src/main/tasks.rs 49d7e104585c8791ac9a203dddd95802784b68c0ba65d6f90f3d57154b4c7871 src/metrics.rs 201ab702722d6d21585fe8bb49939150d61bfac7ed8dc198b98c2cc6098ffb5f src/metrics/global.rs 49b11df65b4fb0b5b727f9f867436584680b45e3f4fbd53f74bc0cab8b9a88ec src/metrics/vhost.rs @@ -371,7 +357,7 @@ b5856fd38a957b2ca9c7e3606d84d13d5cfcf3a45b487b15ae1d2dc248cbda57 src/proxy/body f0cb26ed34c35bacc74b914457acac8a611390de14cf46e2e6d3bcc848e914e8 src/proxy/cache_helpers.rs cfb0910af8cd868bde3f6c9992319e46d5328d6d8f69c50701df0ea12bef2108 src/proxy/forward/cache_lookup.rs 212de22f407dd96800afcf65fd1fc7c40fede1b88f80d04850eb8d1227b08a5b src/proxy/forward/response.rs -4992d8c2fabc468337999b3d4b58560a9e10632893cebe1669ecb3267ad1bdc9 src/proxy/forward/route_cache.rs +0a7841a355f4740c5d9182cfa3ede1b0e6195b66f0518f976810513612ca47dd src/proxy/forward/route_cache.rs 51c620b77004cca26edf387f9650f07d27801e093d32cc0162eaee1d0702c126 src/proxy/forward/success_finish.rs e5b1812c75b4cca91c32e99d28622cac1dea8e347bdf1c46e43546cb341f95a4 src/proxy/forward/success_prepare.rs 23b6bd26f6175b52c13d2571318fddd12550ba34ee77b87b5ac0e4811f8e2bee src/proxy/forward/upstream.rs @@ -387,13 +373,13 @@ d05e655ba0b3b322c657aaa9c2cbe7a0181064a896026b5eeaca347c4a0b7831 src/proxy/webs b21dc9cf3817dd4e3273ce25887d63d7a772c477b86ca57f8d5b72b8154456e7 src/proxy/websocket/tests.rs a44421714aab37f7ceda19e3ec2512c645d2a3ce5dcb3da5bc7a0d87a442d6a8 src/proxy/websocket/tunnel.rs 15fabd38576b61f650c039acdbf7ac5a5bd24330062475b94315285b2dc258b6 src/proxy/websocket/validation.rs -26f57e7e21be5697b57dbeceae8f148c50ac461973adcd8c07a0fd8a8c0d8c33 src/rate_limit.rs -3ac0b5c6407b2cf13e28b570ab94aab3ade62faf93e40a06269a058019fde165 src/rate_limit/tests.rs +463a871aef70a5531c33a9ea43b9dc4e628b272d654bb225e6161aa1497d96f3 src/rate_limit.rs +a767c23830f188f5cd6bb747ad6950732a987df3b36adec0d7a871cbab711bbf src/rate_limit/tests.rs d40ad63939097d2b91339127d5835a74b618d83b7ba31db0c512f05a82107d6e src/redirect.rs 811c83ae9fd0f5a13d35b5443cd37843f82b9d600bee4bb136ba9b87732a5347 src/redirect/tests.rs d0939e1b0829c25356a0b67ecb3a178c74936dd07baa15fee24eb32544db6cad src/runtime.rs 4e0b25c2b86eefddd76c64b39dc1c1cd6d243addc32cdea99e6c3ac84ed879a6 src/runtime/certificates.rs -de52ef274756a1543d8913d8ff36640a2091ea675882b0216d0ab99df0a2d0dc src/runtime/constructors.rs +2ef0bf9a1a80a4e310b274789e026590f219c6579f4f259af3d83e66ae76697c src/runtime/constructors.rs 31dc5cdea00efd63594dc6becd5a9103594696145cbc8e7b7594fb407214ad9d src/runtime/docker.rs c052b35673d905219a71130b44a5dc0027bf9b853bff0b6bdbed4be4064550ea src/runtime/tests.rs 03144b1b00fb45c5616ca9c9b0e57013e86c1f07f129830aaec98a73b274199f src/runtime/types.rs @@ -457,7 +443,7 @@ ff4980f0a091da9ff1722d6b9f6662529cbd7c5e2ac68dcd22a507ce6aaf3011 src/vhost/vali c1fef6dfb79275479c15097b211cd5f063ad7e0852a0767b0145cf4f32f751da src/web_server/config/model.rs f1d3e427c53f8b5be5768d85e69ead746d83647fcd3b355acc98e3f9d5e217e0 src/web_server/config/validation.rs 86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.rs -be75d0bf057a9b17d8c4a1cab67a1cbf349f542c8e57e4a822ebb9192b1c9582 src/web_server/fastcgi/protocol.rs +640d629c6fc2228d864cbe28b9982cf5b5e38feb941a5dbafaa3fd7fe1a924a5 src/web_server/fastcgi/protocol.rs 134c4998f6a0aa27e121af1ae3355b7e335488b2ae867ac0a934c58b4eb77191 src/web_server/fastcgi/response.rs 8186363315bd998919c6c783e6f7501528f257a5c0da77c5d60f423e8670db5e src/web_server/filesystem.rs 0aafd7f37c4fe784b047646e4ce5ddcb5fe821231a41ed0a9f519529385288a8 src/web_server/htaccess.rs @@ -467,19 +453,39 @@ be75d0bf057a9b17d8c4a1cab67a1cbf349f542c8e57e4a822ebb9192b1c9582 src/web_server 80f946570c14c8915fd21bfdb1a84dfb6a14c633ab6060acb828fab4c7c21daa src/web_server/mod.rs 67ab8bef567452f123c4b9b46a942b39c93f1f765c128edca7ee808ff1c5e304 src/web_server/request.rs c5cb561b0f276e8082f904b9446fa5885f6b3c0608f25ea2ef425061e9f7f6b4 src/web_server/response.rs -51b0878fcf6e8ef2458f1b8ded65f843951c122f0ff0afe2854f467c5e192273 src/web_server/response/file.rs +9f49fe34602f7c81543eef12a337c79d20b4dac904a9dfe5e709c123b2c9078e src/web_server/response/file.rs 9aa68251a5f549f4ce3b1bda88d4abb9efc629caf444989381b4117c9488e37b src/web_server/response/headers.rs 2268a6aeed2f565f9791af33e1cd21c56c228c8873b031725b39a6c57184f2d1 src/web_server/routing.rs 400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs 3e0b4e92600a7d5abcec92efed532b3a01e0781cb4f2681daac6d6ae13b0a6e9 src/web_server/serve.rs cf86c4692080723bbdf8f4105e1abfae33d7fcf020d2ad9e13ebae3943bd0cbe src/web_server/tests.rs c62752fe2fa6b74ddc9d94d36f8d191376b1575b15d21fc434c5a5da4c932692 src/web_server/tests/part_01.rs -ef49a4230a1cb46c51866c4c0a49eae8c711377245df50c4f36a86693c040c1e src/web_server/tests/part_02.rs +648d7d9cfc8cd5191fa7dab30c1829fc245f0d4f3e238243d7e32644125a135e src/web_server/tests/part_02.rs e54f44f6ca1ed2c41e620de9195d32c4f8bb6936c3881745316a07160f87b4fa src/web_server/tests/part_03.rs f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json 1725c10331745dc04a07675784d385260da7b14567b8dba3cbae5e891eb5dadf tests/http2.rs 347a4ff9e028a1e999ad129590c355b5734cf928dd81e5099aea0c8aca8d5908 tests/http3.rs eecf680f001f2110dde4eceaccf2f922859a16c946a644f8854e8de4d3771f7c tests/policies.rs 728999110a6d8cacb62bc0399bc333fa23a66526ee23f563a3c93bf00e5fad19 tests/transport_config.rs -77ee069a5c0f69c49d2688eff3c9aa0f3e39fa02042cfe348e410a23b8f3e116 update.sh -af688fd4e67e54a8a2ff243fdfe916a20ceae6251baef207a4e974de2149f308 upgrade.sh +b04362ad082a32c9ccc4cf38c46f44d71347adecf7aba3224b3af3ea4872e3d0 tools/README.md +358c1e94dd2b4a756430e5335747f0b437345b1713670a9972c5bcf78b837ccb tools/audit/admin_audit.py +ba6ebb2e393a89b78ce138f2572156e51a6ae557c3aaefff3d8e52daaeb5dc61 tools/checks/check_source.py +873f21239d31659d1d8d66c5645032583ff4b7032f4712af34137afe510269a6 tools/checks/docs_check.py +4f7821e7e25b09ce251d25a0b3fb4d8ce8396c36a5ed971c2637aeb325425909 tools/checks/langs_check.py +cf762780c4085791c72c3c5f93054d7f2a1107403bee0616a1f4113bbcbb99f9 tools/checks/memory_contracts.py +4256001fe5ac0677b87b1384e7e1225e8a19686a5daedf06699cb8663e77d679 tools/checks/preflight.py +39422107ae42162a45a9dae3521125f648942c67a336f15d444f98e3c5b80007 tools/checks/release_smoke.py +f69845bc26ec33160fc756953991cf32aa8ad6a7fb4a131caa2093db91e783ef tools/checks/rust_module_contracts.py +ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed tools/checks/rust_source.py +adf84a983e0dbba228e47d71ec2d5c9964f7ca9989d5139de58f96ee961b035a tools/checks/security_contracts.py +23b9250245e6bec5ef728c73cee4c88f64924afc3597f8a9b58a1924235df98e tools/checks/security_dependency_contracts.py +2309c41e769e797c838d4f40d50bb3c6b9ad9ad0f9ed07fb93e07f95d66563f9 tools/checks/source_module_contracts.py +0aaa80453e25aae7ea953fadb91b3ae3eaff627977c81c9ab5a2779f9a93582f tools/checks/web_assets.py +54cb50332c5805c0e11857161284bb001eac12363d555714c2ef3ab00b1a856e tools/checks/web_server_contracts.py +c245f8f832bd2c36a3fbd5e66fe98e7367c70a8db840c79e6b640f1d9d74f2ac tools/checks/web_service_ui_test.mjs +d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a tools/dev/mock_auth.py +2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 tools/dev/mock_server.py +538b5f6cfa738355a729940ee3d92e7ab3dc25ca69ae330ddaa08f831775b8ba tools/dev/test_compression.py +c711154a9df425a56607bd88f03815ffb408213a31a2b66ed1c1c9a6301a2fc6 tools/legacy/backup.py +fec1b9f7bf8d4b43c2b45e6a1f762c9412c51f3e4e3520aff185b0590b5d47be update.sh +56c702111022e1ca2c8f6b72c635c749b05936ffe6387bdb0a202691614cbc96 upgrade.sh diff --git a/V0.25.0_WEB_SERVER_PLAN.md b/V0.25.0_WEB_SERVER_PLAN.md deleted file mode 100644 index eda6a89..0000000 --- a/V0.25.0_WEB_SERVER_PLAN.md +++ /dev/null @@ -1,2283 +0,0 @@ -# RustProxy 0.25.0+ — Full Web Server / PHP-FPM Plan - -Status: design and implementation TODO -Target: RustProxy 0.25.0 + -Baseline reviewed: RustProxy 0.24.2 - -## 1. Goal - -RustProxy 0.25.0 should remain a reverse proxy exactly as it is today, while optionally becoming a full web server for a vhost: - -- serve static files directly from disk; -- serve directory indexes; -- support HTTP validators and byte ranges; -- execute PHP through PHP-FPM / FastCGI; -- provide structured `try_files` and internal rewrite behavior; -- support Nginx-style location matching already present in RustProxy; -- provide presets for common applications such as Drupal, WordPress, Laravel and Symfony; -- allow fully custom structured configuration; -- keep all current proxy, cache, ACL, authentication, TLS, HTTP/2, HTTP/3, WebSocket, header and security behavior intact for existing configurations. - -The feature must be additive. A 0.24.2 configuration loaded by 0.25.0 must continue to behave as a reverse proxy without requiring edits and without silently changing routing semantics. - ---- - -## 2. Non-negotiable compatibility rules - -### 2.1 Do not reinterpret existing configuration - -The meaning of the following existing fields must not change: - -- `Vhost.backends`; -- `Vhost.routes`; -- `PathRoute.path_prefix`; -- `PathRoute.match_type`; -- `PathRoute.backend_id`; -- `PathRoute.cache`; -- `PathRoute.acl`; -- `PathRoute.headers`; -- `PathRoute.rewrite`; -- `PathRoute.response`; -- vhost/global ACLs; -- Basic Auth; -- Tinyauth; -- rate limits; -- security headers; -- response compression; -- transform configuration; -- cache configuration; -- listener selection; -- HTTP/1.1, HTTP/2 and HTTP/3 behavior; -- WebSocket behavior; -- backend balancing/health/affinity; -- custom pages; -- revision/CAS configuration publication. - -In particular, the current `PathRewrite` remains an **upstream proxy path rewrite**. It must not be silently changed into an internal web-server rewrite. - -### 2.2 Existing route precedence stays unchanged - -Keep the existing location matching rules: - -1. exact; -2. longest prefix; -3. `prefix_priority` / Nginx `^~` skips regex evaluation; -4. first matching regex/regex-insensitive route in configuration order; -5. longest ordinary prefix; -6. vhost fallback. - -Repeated literal slashes keep the existing normalized routing behavior. Existing rejection of ambiguous encoded separators, encoded `%`, dot segments, controls and malformed path forms must remain. - -### 2.3 Existing vhosts remain proxy vhosts by default - -If the new `web` configuration is absent or disabled: - -- vhost validation remains equivalent to 0.24.2; -- a backend pool remains required; -- the main vhost backend remains the fallback; -- existing Custom Locations remain optional overrides; -- local `return` behavior remains unchanged; -- no local filesystem or FastCGI operation is performed. - -### 2.4 No arbitrary Nginx configuration parser - -Do not add a raw `nginx.conf` execution/parser path. - -RustProxy should expose structured configuration that can represent the common Nginx behaviors needed for application hosting. Unknown fields remain rejected. - -### 2.5 No hidden preset behavior - -A preset generates ordinary RustProxy configuration. After generation, every field is editable. Runtime behavior must never depend on a hidden `wordpress=true` or `drupal=true` flag. - ---- - -## 3. Baseline behavior in 0.24.2 that must be preserved - -The current code already provides useful building blocks and they should be reused rather than replaced: - -- vhost/frontend selection is tied to the accepted local listener address and TLS state; -- exact/wildcard hostname ownership is already separated from path matching; -- main backend fallback is separate from user Custom Locations; -- Custom Locations already support: - - prefix; - - `^~`-style priority prefix; - - exact match; - - case-sensitive regex; - - case-insensitive regex; -- existing proxy rewrites support: - - preserve; - - strip prefix; - - replace prefix; - - regex replacement with captures; -- route ACLs are additive to host/profile/vhost ACLs; -- global, vhost and route header rules already exist; -- a route can already generate a local response without opening an upstream connection; -- authentication and ACL decisions run before upstream I/O; -- the immutable compiled snapshot is published only after validated revisioned configuration writes; -- the response pipeline already centralizes security headers, server headers, diagnostics and compression. - -0.25.0 should add local web origins to this architecture, not create a separate HTTP stack. - ---- - -## 4. User-facing vhost model - -Add a dedicated **WWW** / **Web Server** tab inside the existing vhost editor. - -Recommended tab order: - -```text -Details -Backends -Health -Locations -WWW -Auth -SSL -Cache -Transform -Security -Pages -Advanced -``` - -Do not create separate top-level modules for Drupal, WordPress or PHP. - -### 4.1 UI mode - -The UI may display a derived mode: - -- `Proxy`; -- `Web Server`; -- `Mixed`. - -Prefer deriving the label from actual configuration instead of making a new required runtime `mode` field. - -Examples: - -- no `web` config -> Proxy; -- `web.enabled=true`, local web fallback, no proxy routes -> Web Server; -- `web.enabled=true` plus proxy routes/backends -> Mixed. - -### 4.2 WWW tab — main sections - -#### Web root - -- Enable Web Server; -- document root; -- fallback target; -- index files; -- autoindex; -- default MIME type; -- symlink policy; -- default static security policy; -- effective path preview. - -#### Application preset - -- Custom; -- Static website; -- SPA; -- Generic PHP front controller; -- WordPress; -- Drupal modern; -- Drupal legacy `q=` routing; -- Joomla; -- Laravel; -- Symfony; -- Nextcloud; -- phpMyAdmin. - -The preset is a generator only. - -#### PHP / FastCGI - -- enable PHP; -- PHP-FPM profile; -- Unix socket or TCP endpoint; -- test connection; -- connect/read/write timeout; -- maximum connections; -- request body limit; -- default FastCGI params; -- custom FastCGI params; -- optional PATH_INFO behavior; -- effective `SCRIPT_FILENAME` preview. - -#### Static files - -- index list; -- directory redirect; -- autoindex; -- validator behavior; -- Range support; -- dotfile policy; -- executable/source-file deny policy; -- optional per-location root/alias. - -#### Routing / front controller - -- structured `try_files` editor; -- internal URI fallback; -- named internal location fallback; -- internal rewrites; -- rewrite flow `last` / `break`; -- query-string behavior; -- internal redirect limit. - -#### Advanced - -- named internal locations; -- FastCGI params; -- MIME overrides; -- route-specific web handler configuration; -- conditional rules from an explicit safe allowlist; -- effective behavior preview. - ---- - -## 5. Proposed persisted configuration - -The exact Rust types can be adjusted during implementation, but the compatibility properties below should remain. - -### 5.1 Add an optional vhost web section - -Add an optional field to `Vhost`: - -```rust -#[serde(default, skip_serializing_if = "Option::is_none")] -pub web: Option, -``` - -When absent, behavior is exactly the legacy proxy behavior. - -Suggested shape: - -```json -{ - "web": { - "enabled": true, - "document_root": "/var/www/example/public", - "fallback": "web", - "indexes": ["index.php", "index.html"], - "autoindex": false, - "symlinks": "within_root", - "default_mime": "application/octet-stream", - "php_profiles": [], - "named_locations": [] - } -} -``` - -Suggested fallback values: - -- `proxy` — retain the main backend pool as unmatched-path fallback; -- `web` — local web root is unmatched-path fallback; -- `not_found` — no origin fallback, use the existing 404/page path. - -Rules: - -- if `web` is absent/disabled, fallback is implicitly legacy `proxy`; -- `fallback=proxy` requires at least one proxy backend; -- `fallback=web` does not require an HTTP backend; -- `fallback=not_found` does not require an HTTP backend unless a proxy route references one. - -### 5.2 Add optional local web handling to a PathRoute - -Do not change the meaning of existing fields. - -Add an optional field: - -```rust -#[serde(default, skip_serializing_if = "Option::is_none")] -pub web: Option, -``` - -Action resolution for persisted routes: - -```text -response != null -> existing local Return action -web != null -> new local Web action -otherwise -> existing Proxy action -``` - -Validation must reject ambiguous combinations. - -For a `web` route: - -- `response` must be null; -- `backend_id` must be null unless a future explicit proxy web action is added; -- existing `PathRewrite` must remain `preserve`; internal web rewrite uses a new field/type; -- proxy cache override should initially be rejected/unused for local static/FastCGI routes rather than silently changing meaning; -- route ACL and header rules remain valid and should apply. - -This allows the existing location matcher to be reused for static/PHP routes without creating a second public URL matcher. - -### 5.3 Do not repurpose existing PathRewrite - -Add a separate web rewrite model, for example: - -```json -{ - "web": { - "handler": "static", - "rewrites": [ - { - "pattern": "^/(.*)$", - "replacement": "/index.php", - "query": "q=$1", - "flow": "last" - } - ] - } -} -``` - -The existing proxy `rewrite` field continues to modify only the upstream proxy target. - -### 5.4 Structured try_files - -Prefer a structured model over accepting an arbitrary Nginx directive string. - -Example: - -```json -{ - "try_files": [ - {"kind": "file", "value": "$uri"}, - {"kind": "directory", "value": "$uri"}, - {"kind": "named", "value": "drupal"} - ] -} -``` - -Other supported terminal candidates: - -```json -{"kind": "uri", "value": "/index.php", "query": "$args"} -``` - -```json -{"kind": "status", "value": 404} -``` - -The UI can render a familiar preview: - -```text -try_files $uri $uri/ @drupal; -``` - -without storing or executing raw Nginx syntax. - -### 5.5 Named locations - -Named locations are internal-only and should not become public URL patterns. - -Suggested shape: - -```json -{ - "named_locations": [ - { - "id": "drupal", - "action": { - "type": "internal_redirect", - "uri": "/index.php", - "query": "q=$uri" - } - } - ] -} -``` - -Rules: - -- IDs use the existing safe ID rules where possible; -- named locations cannot be requested directly by a client; -- only `try_files` / internal rewrite may enter them; -- loops are bounded; -- their configuration is compiled into the immutable snapshot. - ---- - -## 6. Necessary internal routing change - -A pure Web Server vhost cannot require a fake HTTP backend. - -0.24.2 currently requires `1..32` backends and the compiled main backend fallback is also used to establish route ownership. 0.25.0 therefore needs a small internal routing refactor, but it must be behavior-preserving for all old configurations. - -### 6.1 Regression-gated refactor - -Introduce an internal compiled action/target concept, for example: - -```rust -enum CompiledRouteAction { - Proxy(CompiledProxyAction), - Return(LocationResponse), - Static(CompiledStaticAction), - FastCgi(CompiledFastCgiAction), -} -``` - -or an equivalent representation. - -This is an internal model only. It does not require rewriting old persisted JSON. - -Compile old configurations exactly as follows: - -```text -PathRoute.response = Some -> Return -PathRoute.response = None and PathRoute.web = None -> Proxy -legacy vhost fallback -> Proxy -``` - -New configurations may additionally compile to Static/FastCGI/local web fallback. - -### 6.2 Host ownership must not depend on having a proxy fallback - -Frontend hostname/listener ownership already exists separately and must remain authoritative. - -For an enabled vhost: - -```text -claimed frontend --> existing custom location selection --> configured fallback target (proxy/web/not_found) -``` - -Do not continue to a broader wildcard merely because a path did not match, preserving the existing claimed-vhost behavior. - -### 6.3 Backend validation becomes conditional - -Legacy proxy vhost: - -```text -web absent/disabled -> require 1..32 backends exactly as today -``` - -Web vhost: - -```text -0..32 backends allowed -``` - -but validation must require a backend if any configured route or fallback uses Proxy. - -No HTTP backend should be required for a pure static/PHP vhost. - -### 6.4 Legacy root normalization - -`PathRoute::is_legacy_main_fallback()` must treat any new web action/configuration as a deliberate customized `/` route. - -A new web route must never be removed as the old synthetic root fallback. - ---- - -## 7. Request pipeline - -The existing outer request/response pipeline is valuable and should be retained. - -Desired high-level order: - -```text -accepted connection --> current listener/TLS/frontend selection --> current host/path normalization --> current redirect/local-host handling --> current host/profile/vhost ACL --> current rate limits --> current Basic Auth / Tinyauth --> current location selection --> current location ACL --> action dispatch: - Return -> existing local return - Static -> new static handler - FastCGI -> new FastCGI handler - Proxy -> existing proxy path unchanged --> current error-page processing --> current global/vhost/location response header rules --> current security headers/server headers/diagnostics --> current compression where safe --> H1/H2/H3 response body -``` - -### 7.1 Critical rule - -Do not fork a second HTTP server pipeline for local web content. - -Static and FastCGI responses must flow through the same outer policies as proxy responses wherever those policies make sense. - -### 7.2 Local web response marker - -Do not mark normal local website files with the existing behavior that forces placeholder/default pages to `no-store` and strips validators. - -Add a distinct response extension/marker for local web-origin responses if needed so that: - -- proxy-cache diagnostics are not incorrectly added; -- `ETag` and `Last-Modified` remain intact; -- application `Cache-Control` remains intact; -- global/vhost/location response headers still apply; -- security headers still apply; -- compression may still apply when safe; -- custom error pages may still replace error responses. - ---- - -## 8. Static file server - -Create a dedicated module instead of growing `proxy/mod.rs` further. - -Suggested files: - -```text -src/web/mod.rs -src/web/static_files.rs -src/web/path.rs -src/web/rewrite.rs -src/web/fastcgi.rs -src/web/fastcgi_protocol.rs -src/web/presets.rs # only if presets are also generated server-side -``` - -### 8.1 Required static features - -- GET; -- HEAD; -- 405 for unsupported methods when the route is a pure static file handler; -- `Allow: GET, HEAD` where appropriate; -- document root; -- per-location root override; -- alias support; -- directory detection; -- trailing-slash directory redirect; -- index resolution; -- MIME type detection; -- configurable MIME overrides; -- default `application/octet-stream`; -- `Content-Length`; -- `Last-Modified`; -- `ETag`; -- `If-None-Match`; -- `If-Modified-Since`; -- `304 Not Modified`; -- byte ranges; -- `206 Partial Content`; -- `416 Range Not Satisfiable`; -- `Accept-Ranges: bytes`; -- `If-Range`; -- streaming file bodies; -- no whole-file buffering; -- directory autoindex optional and OFF by default; -- correct HEAD metadata with no body; -- correct behavior under HTTP/1.1, HTTP/2 and HTTP/3. - -### 8.2 Validators - -Recommended ETag strategy for 0.25.0: - -- metadata-based validator using stable file metadata such as size + modification time; -- do not hash the complete file on every request; -- document whether the tag is weak or strong; -- obey HTTP conditional precedence correctly (`If-None-Match` before date validators). - -### 8.3 Ranges - -P0: - -- one satisfiable byte range; -- suffix ranges; -- open-ended ranges; -- correct 416 with `Content-Range: bytes */size`; -- `If-Range`. - -For syntactically valid multi-range requests, either implement multipart ranges or safely ignore the Range and return a complete `200`. Do not generate malformed multipart responses. - -### 8.4 Index handling - -Example: - -```text -index index.php index.html; -``` - -If a directory resolves to `index.php`, **never serve PHP source as a static file**. - -The index URI must enter the internal web dispatch path so the PHP/FastCGI handler is selected. - -### 8.5 Autoindex - -- default OFF; -- HTML escape names; -- URL encode links; -- never expose filesystem paths outside the configured root; -- do not show denied dotfiles; -- do not follow a symlink that violates the root policy; -- HEAD support; -- bounded directory entry count or streamed/bounded generation to avoid memory abuse. - ---- - -## 9. Filesystem security - -This is a release blocker for 0.25.0. - -### 9.1 Reuse current path validation - -Reuse the existing normalized routing path as the basis for local URL mapping. - -Do not add a second decoder that could allow double decoding. - -Continue rejecting: - -- encoded `/`; -- encoded `\\`; -- encoded `%`; -- dot segments; -- malformed percent escapes; -- control characters; -- ambiguous separators; -- malformed UTF-8. - -### 9.2 Root escape prevention - -A requested path must never escape its configured document root/alias through: - -- `..`; -- percent encoding; -- symlinks; -- rename races where feasible to prevent; -- platform separator differences. - -Prefer a capability/open-beneath style implementation rather than simple string-prefix checks. - -Cross-platform correctness for Linux/BSD is more important than Linux-only `sendfile` optimization. - -### 9.3 Symlink policy - -Suggested explicit values: - -- `deny`; -- `within_root` — recommended default; -- `allow` — advanced opt-in with clear warning. - -Never make `allow` the default. - -### 9.4 Sensitive files - -Provide a secure default/preset policy for common sensitive paths: - -- `.git`; -- `.svn`; -- `.hg`; -- `.env`; -- `.htaccess`; -- `.htpasswd`; -- editor/backup files where configured; -- application-specific config files from presets. - -Do not over-block arbitrary dotfiles if the administrator explicitly opts out, but presets should be secure by default. - -### 9.5 PHP/source disclosure prevention - -PHP-like executable source extensions must not silently fall back to static serving when: - -- PHP-FPM is down; -- the PHP route is invalid; -- FastCGI times out; -- the FastCGI connection fails. - -Return an error (`502`/`504` as appropriate). Never expose source code as a fallback. - -Protect at least common PHP script extensions used by the configured policy (`.php`, and optional configured extensions such as `.phtml`/`.phar`). - ---- - -## 10. Internal web routing - -### 10.1 try_files - -Required behavior: - -```text -try_files $uri $uri/ @drupal -``` - -and: - -```text -try_files $uri $uri/ /index.php?$args -``` - -Candidate checks should be performed relative to the effective route root/alias. - -No candidate may escape the allowed filesystem root. - -### 10.2 Internal redirect - -An internal redirect may change URI/query and run web route selection again. - -Required safeguards: - -- hard maximum redirect/rewrite depth, e.g. 10; -- loop detection/diagnostics; -- no client-visible redirect unless explicitly configured as a 3xx response; -- re-evaluate the effective location after `last`; -- preserve or replace query according to explicit configuration; -- internal redirect target must pass the same path validator. - -### 10.3 `last` and `break` - -Implement explicit structured behavior: - -- `last` — update internal URI and reselect location; -- `break` — stop rewrite evaluation and continue the current location handler with the rewritten internal path. - -Do not reuse the existing proxy rewrite enum for this purpose. - -### 10.4 Safe variables - -Initial variable allowlist should include only values with clear semantics: - -- `$uri` — normalized internal URI path; -- `$request_uri` — original path/query where safe for value expansion, never for filesystem joining directly; -- `$args`; -- `$host`; -- `$scheme`; -- `$request_method`; -- regex captures `$1..$9` for the current web rewrite rule; -- `$document_root` for FastCGI parameter expansion; -- `$request_filename` as a derived safe path representation for conditions, not as an unchecked arbitrary path. - -No arbitrary environment variable expansion from public requests. - -### 10.5 Safe conditional rules - -Do not implement general Nginx `if` scripting. - -Support structured conditions needed by normal web hosting: - -- file exists; -- file does not exist; -- regular file exists; -- directory exists; -- URI regex; -- request method; -- hostname if genuinely needed. - -Example equivalent of: - -```nginx -if (!-e $request_filename) { - rewrite ^/(.*)$ /index.php?q=$1 last; -} -``` - -should be representable as an explicit condition + internal rewrite, not raw script text. - ---- - -## 11. FastCGI / PHP-FPM - -Implement FastCGI as a local web origin, not as an HTTP proxy backend. - -### 11.1 Endpoints - -Support: - -- Unix socket: - -```text -/run/php/php8.4-fpm.sock -``` - -- TCP: - -```text -127.0.0.1:9000 -``` - -Optionally allow remote TCP FastCGI only through explicit configured endpoints. Never derive the endpoint from request data. - -### 11.2 FastCGI protocol - -Implement/test the required FastCGI responder flow: - -- `FCGI_BEGIN_REQUEST`; -- role `FCGI_RESPONDER`; -- `FCGI_PARAMS`; -- `FCGI_STDIN`; -- `FCGI_STDOUT`; -- `FCGI_STDERR`; -- `FCGI_END_REQUEST`; -- FastCGI name/value length encoding; -- record splitting for values/bodies larger than one record; -- bounded stderr capture/logging; -- protocol error handling; -- request IDs; -- connection close/reuse state. - -### 11.3 Connection model - -- async I/O; -- bounded number of FastCGI connections per profile; -- queue timeout; -- connect timeout; -- read timeout; -- write/body idle timeout; -- backpressure; -- cancellation on downstream disconnect; -- no unlimited buffers; -- do not assume FastCGI multiplexing support; -- reuse a connection only after a clean `END_REQUEST` and when configured/safe. - -PHP-FPM compatibility is the primary target. - -### 11.4 Request body - -- stream POST/PUT/PATCH bodies to `FCGI_STDIN`; -- do not buffer normal uploads completely in memory; -- enforce a configurable maximum request size for web/PHP routes; -- handle missing/invalid Content-Length safely; -- preserve streaming behavior for HTTP/2 and HTTP/3; -- abort the FastCGI request/connection safely when the client disconnects. - -### 11.5 CGI/FastCGI response parsing - -Parse CGI-style response headers from `FCGI_STDOUT`: - -- `Status`; -- `Content-Type`; -- `Content-Length` if valid; -- `Location`; -- repeated `Set-Cookie`; -- arbitrary safe end-to-end headers. - -Then: - -- remove/reject hop-by-hop/framing headers that an application must not control; -- do not trust an application-supplied transfer framing header; -- pass the body as a streaming HTTP response; -- map malformed FastCGI output to `502`; -- map connect failure to `502`; -- map timeout to `504`; -- map unavailable/capacity state consistently with RustProxy conventions. - -### 11.6 PHP-FPM profiles - -Suggested profile: - -```json -{ - "id": "php-default", - "endpoint": { - "type": "unix", - "path": "/run/php/php8.4-fpm.sock" - }, - "connect_timeout_ms": 1000, - "response_timeout_seconds": 120, - "write_timeout_seconds": 60, - "max_connections": 32, - "queue_timeout_ms": 5000, - "keep_conn": true, - "max_request_body_bytes": 134217728, - "params": {} -} -``` - -Limits are examples and must be validated against existing RustProxy resource conventions before final defaults are chosen. - ---- - -## 12. FastCGI parameters - -Provide a safe built-in default equivalent to the useful part of common `fastcgi_params` / `fastcgi.conf` setups. - -Required parameters: - -- `GATEWAY_INTERFACE`; -- `SERVER_SOFTWARE`; -- `QUERY_STRING`; -- `REQUEST_METHOD`; -- `CONTENT_TYPE`; -- `CONTENT_LENGTH`; -- `SCRIPT_FILENAME`; -- `SCRIPT_NAME`; -- `REQUEST_URI`; -- `DOCUMENT_URI`; -- `DOCUMENT_ROOT`; -- `SERVER_PROTOCOL`; -- `REQUEST_SCHEME`; -- `HTTPS`; -- `REMOTE_ADDR`; -- `REMOTE_PORT`; -- `SERVER_ADDR`; -- `SERVER_PORT`; -- `SERVER_NAME`. - -Also expose incoming request headers as safe CGI `HTTP_*` parameters after normal RustProxy header validation/sanitization. - -### 12.1 Trust boundary - -`REMOTE_ADDR` must come from RustProxy's already validated effective client identity, not directly from an untrusted forwarding header. - -`SERVER_ADDR` / `SERVER_PORT` must come from the accepted local frontend. - -`SERVER_NAME` comes from the normalized claimed vhost/host. - -### 12.2 SCRIPT_FILENAME - -Default PHP mapping: - -```text -$document_root$fastcgi_script_name -``` - -but calculate it structurally rather than concatenating unchecked request text. - -Before opening FastCGI: - -- resolve against the permitted document root; -- require an existing regular file unless explicitly configured otherwise; -- enforce symlink/root policy; -- reject traversal/ambiguous paths; -- never pass an arbitrary filesystem path supplied by a client. - -### 12.3 Custom params - -Allow per-profile and per-route overrides. - -Precedence suggestion: - -```text -built-in defaults --> PHP profile params --> route params -``` - -Protect parameters that define RustProxy's trust boundary from unsafe browser-controlled replacement where needed. - -### 12.4 PATH_INFO - -Default OFF/safe. - -Provide an explicit structured option for applications that require PATH_INFO, similar in purpose to `fastcgi_split_path_info`, with tests for script/path confusion. - -Never enable permissive PHP path guessing by default. - -Document a secure PHP-FPM/PHP recommendation such as disabling unsafe path fixing where appropriate. - ---- - -## 13. Mapping Nginx concepts to RustProxy - -| Nginx concept | RustProxy 0.25 design | -| --- | --- | -| `server_name` | existing vhost `frontend.hosts` | -| `listen` | existing named listeners | -| `location /x` | existing `PathMatch::Prefix` | -| `location ^~ /x` | existing `PathMatch::PrefixPriority` | -| `location = /x` | existing `PathMatch::Exact` | -| `location ~ regex` | existing `PathMatch::Regex` | -| `location ~* regex` | existing `PathMatch::RegexInsensitive` | -| `root` | vhost web root / route root override | -| `alias` | route web alias | -| `index` | vhost/route web indexes | -| `try_files` | structured web `try_files` | -| `rewrite ... last` | new internal web rewrite + location reselection | -| `rewrite ... break` | new internal web rewrite without reselection | -| `if (-e ...)` | structured file condition | -| `if (!-e ...)` | structured not-exists condition | -| `fastcgi_pass` | PHP/FastCGI profile endpoint | -| `fastcgi_param` | FastCGI profile/route params | -| `add_header` | existing response header rules | -| `return` | existing `LocationResponse` | -| `Cache-Control` | existing response header rules / preset helper | -| `Vary` | existing response header rules plus compression-owned values | -| `status_zone` | RustProxy metrics keyed by bounded vhost/route IDs, not arbitrary dynamic zones | - -Do not duplicate existing header/ACL/return functionality inside the new WWW subsystem. - ---- - -## 14. Drupal examples - -### 14.1 Simple Drupal/front-controller behavior - -Nginx-style intent: - -```nginx -location / { - try_files $uri $uri/ @drupal; - index index.php index.html; -} - -location @drupal { - rewrite ^/(.*)$ /index.php?q=$1 last; -} -``` - -RustProxy should represent this structurally as: - -```text -Web root: /var/www/drupal -Indexes: index.php, index.html -Fallback static handler: - try file $uri - try directory $uri - otherwise -> @drupal - -Named location @drupal: - internal URI -> /index.php - query -> q= - reselect location - -Regex location \.php$: - FastCGI -> php-default - script must exist under document root -``` - -### 14.2 Advanced imagecache behavior - -Nginx-style intent: - -```nginx -location ^~ /files/imagecache/ { - add_header Cache-Control "public, max-age=604800"; - if_modified_since exact; - index index.php; - if (!-e $request_filename) { - rewrite ^/(.*)$ /index.php?q=$1 last; - break; - } - add_header Vary Accept-Encoding; -} -``` - -RustProxy representation: - -```text -Location match: - type: prefix_priority - path: /files/imagecache/ - -Action: - local web/static - -Response headers: - existing HeaderRules: - Cache-Control = public, max-age=604800 - Vary += Accept-Encoding (with normal de-duplication/compression rules) - -Index: - index.php - -Condition: - if requested file does not exist - -Internal rewrite: - regex: ^/(.*)$ - target: /index.php - query: q=$1 - flow: last -``` - -The existing proxy `PathRewrite` is not used for this internal rewrite. - ---- - -## 15. WordPress preset - -Generate ordinary configuration equivalent in intent to: - -```nginx -location / { - try_files $uri $uri/ /index.php?$args; -} - -location ~ \.php$ { - fastcgi_pass ...; -} -``` - -Preset requirements: - -- administrator selects WordPress root; -- default indexes include `index.php`, `index.html`; -- local static fallback; -- front-controller fallback to `/index.php` preserving query args; -- PHP regex route; -- PHP script existence check; -- block `.ht*`; -- block common secret/backup files; -- never serve PHP source statically; -- allow normal `/wp-content` static files; -- no hidden WordPress runtime flag. - ---- - -## 16. Drupal presets - -Provide at least two clear choices: - -### Drupal modern - -- normal static file checks; -- front controller `/index.php`; -- preserve normal query string; -- secure PHP route; -- common private file restrictions appropriate to the chosen document root. - -### Drupal legacy `q=` - -- normal static file checks; -- front controller `/index.php?q=`; -- preserve/append original query according to explicit generated config; -- secure PHP route. - -Do not guess the site's Drupal generation silently. The selected preset should be visible before applying. - ---- - -## 17. Laravel / Symfony presets - -### Laravel - -- document root must point to `public/`; -- static file/directory check; -- `/index.php` front controller; -- PHP-FPM; -- deny direct access outside the public root by construction. - -### Symfony - -- document root `public/`; -- static file check; -- `/index.php` front controller; -- PHP-FPM; -- secure source-file behavior. - -The UI should warn if the administrator selects a known project root while the expected public directory exists beneath it, but never silently rewrite an administrator path without showing the result. - ---- - -## 18. Static / SPA presets - -### Static website - -- web fallback static; -- indexes `index.html`; -- no PHP; -- autoindex off; -- validators/ranges enabled; -- secure dotfile policy. - -### SPA - -Equivalent intent: - -```text -try file $uri -otherwise internal fallback /index.html -``` - -API/static prefixes can still use existing Custom Locations or explicit web locations. - ---- - -## 19. Nextcloud/phpMyAdmin and advanced presets - -Treat these as presets, not special server code. - -They may need: - -- additional blocked paths; -- PATH_INFO handling; -- well-known redirects/rewrites; -- larger upload limits; -- application-specific headers. - -Before shipping a preset, add fixture tests that verify the generated structured configuration. Do not ship a preset based only on an approximate Nginx snippet. - ---- - -## 20. MIME handling - -Add a maintained built-in MIME map for common web types: - -- HTML; -- CSS; -- JavaScript; -- JSON; -- XML; -- SVG; -- images; -- fonts; -- audio/video; -- WASM; -- PDF; -- archives; -- text. - -Rules: - -- match file extension case-insensitively where appropriate; -- administrator may override/add mappings; -- unknown type -> `application/octet-stream`; -- never infer executable behavior from MIME type; -- PHP execution is determined only by an explicit FastCGI route/handler. - ---- - -## 21. Cache and compression behavior - -### 21.1 Browser cache - -Use normal HTTP headers for local static content: - -- `Cache-Control`; -- `Expires` if configured; -- `ETag`; -- `Last-Modified`; -- `Vary` where required. - -Existing route/vhost response header rules should remain the main mechanism for custom cache headers. - -### 21.2 RustProxy object cache - -For 0.25.0 P0, do not require local static files to pass through the proxy object cache. The disk file is already the local origin and the OS page cache provides the primary file cache. - -Likewise, do not silently apply the existing proxy cache semantics to FastCGI unless a dedicated design is added and tested. - -A future FastCGI cache can be added explicitly. - -### 21.3 Compression - -Reuse the existing response compression layer when safe. - -Rules: - -- do not compress a byte-range response incorrectly; -- preserve validator semantics; -- make `Vary: Accept-Encoding` correct; -- do not double-compress a FastCGI response that already has Content-Encoding; -- honor existing no-transform/private compression rules where applicable; -- HEAD remains bodyless. - -Optional precompressed `.br`/`.gz` static file selection can be deferred unless implemented with complete validator/Vary tests. - ---- - -## 22. Headers - -Reuse existing HeaderRules. - -Do not create another web-only header engine. - -For a local web route: - -- response header rules apply to static/FastCGI responses; -- request header rules should apply before FastCGI `HTTP_*` parameter generation; -- protected forwarding/framing headers remain protected; -- canonical client identity remains RustProxy-owned; -- FastCGI output cannot override hop-by-hop transport framing. - ---- - -## 23. Authentication / ACL / rate limits - -Local web content must not bypass current gates. - -The following must continue to run before filesystem content/FastCGI application data is returned: - -- host ACL; -- reusable ACL profile; -- vhost ACL; -- route ACL; -- country policy; -- vhost rate limits; -- Basic Auth; -- Tinyauth. - -Do not create a direct static-file shortcut before authentication. - -Authenticated responses retain current privacy protections. - ---- - -## 24. Error handling - -Reuse existing custom error pages where possible. - -Expected mapping: - -- missing static file -> 404; -- denied path/file policy -> 403/404 according to chosen security policy; -- unsupported static method -> 405; -- invalid Range -> 416; -- FastCGI connect/protocol failure -> 502; -- FastCGI timeout -> 504; -- FastCGI capacity/queue exhaustion -> 503; -- internal rewrite loop -> 500 with bounded diagnostic log; -- invalid internal target -> 400/500 depending on whether it originated from public input or invalid administrator configuration. - -Do not leak absolute filesystem paths, socket paths or PHP-FPM stderr to public clients. - -Operational logs may expose sanitized administrator-useful detail. - ---- - -## 25. Observability - -Add bounded metrics/log fields without high-cardinality public values. - -### 25.1 Request origin/action - -Expose a bounded action category: - -- `proxy`; -- `return`; -- `static`; -- `fastcgi`. - -### 25.2 Static metrics - -Suggested counters: - -- static requests; -- static bytes served; -- 304 count; -- range responses; -- not found; -- denied path; -- index resolutions; -- internal redirects/rewrite loop failures. - -### 25.3 FastCGI metrics - -Suggested counters/gauges: - -- requests; -- active connections; -- queued requests; -- connect errors; -- protocol errors; -- timeouts; -- response latency; -- bytes in/out. - -Use bounded profile/route IDs from configuration as labels. Never use arbitrary URI, script filename or query string as a Prometheus label. - -### 25.4 Live logs - -Add useful bounded fields: - -- route ID; -- action type; -- FastCGI profile ID; -- status; -- duration; -- optional relative script path, sanitized/truncated; -- internal rewrite count. - -Do not log FastCGI secrets or full POST bodies. - ---- - -## 26. Admin UI details - -### 26.1 Main WWW card - -Fields: - -```text -[ ] Enable web server -Document root: [/var/www/example/public] -Unmatched requests: [Serve from web root | Proxy | 404] -Index files: [index.php] [index.html] -Autoindex: [off] -Symlinks: [within root] -Preset: [Custom v] -``` - -Show a warning if the root is not accessible by the RustProxy service user. - -### 26.2 PHP card - -```text -[ ] Enable PHP/FastCGI -Profile: php-default -Endpoint type: Unix socket / TCP -Socket: /run/php/php8.4-fpm.sock -or host:port: 127.0.0.1:9000 -[Test connection] -``` - -Then Advanced: - -- timeouts; -- max connections; -- queue timeout; -- request body limit; -- custom FastCGI params; -- PATH_INFO. - -### 26.3 Front controller builder - -Provide common choices: - -```text -Existing file -> serve it -Existing directory -> serve/index it -Otherwise -> /index.php -Query handling -> preserve -``` - -Advanced representation may show: - -```text -try_files $uri $uri/ /index.php?$args -``` - -but the saved model remains structured. - -### 26.4 Web locations - -Do not hide that URL matching is the same RustProxy location system. - -A location card can show action: - -- Proxy; -- Return; -- Static files; -- PHP/FastCGI. - -The existing **Locations** tab remains the complete routing view. The **WWW** tab provides web-server-oriented defaults, presets and helpers. - -Avoid maintaining two independent location lists. - -### 26.5 Effective behavior preview - -Extend the current location preview with: - -- action: Static / FastCGI; -- effective document root/alias; -- index list; -- try_files chain; -- selected PHP profile; -- internal rewrite summary; -- script filename template; -- ACL/header composition. - ---- - -## 27. API / OpenAPI - -Update: - -- vhost schema; -- route schema; -- WebServerSettings; -- WebRouteSettings; -- FastCGI profile schemas; -- try_files candidates; -- internal rewrite/condition schemas; -- named location schemas; -- status/diagnostic schemas if new metrics are returned. - -Keep: - -- authenticated admin API rules; -- CSRF/revision CAS behavior; -- `deny_unknown_fields` semantics; -- current `/configuration` compatibility behavior; -- no new anonymous admin endpoint. - -A connection test endpoint for FastCGI, if added, must be authenticated and bounded and must not permit an arbitrary user-supplied network destination outside the validated draft/config context. - ---- - -## 28. Configuration validation - -Validation must happen before publishing a snapshot. - -Validate at least: - -- document root is absolute; -- configured path length/control characters; -- alias/root compatibility; -- index filenames contain no separators/traversal; -- MIME override names/values; -- FastCGI endpoint syntax; -- Unix socket path syntax; -- timeout ranges; -- connection/queue limits; -- request body limit; -- unique PHP profile IDs; -- unique named location IDs; -- named fallback references exist; -- rewrite regex compiles within bounded regex limits; -- internal rewrite replacement length; -- try_files item count; -- rewrite/redirect depth configured within hard server maximum; -- web route and local response are mutually exclusive; -- web route and legacy proxy-only settings do not conflict; -- proxy route requires a backend pool; -- `fallback=proxy` requires a backend pool; -- pure web vhost may have zero backends; -- PHP handler references an existing FastCGI profile; -- PHP source cannot accidentally be configured as generic static fallback without an explicit dangerous override, preferably disallow entirely. - -Filesystem existence checks may be shown as UI/runtime diagnostics, but decide carefully whether a temporarily missing mount should invalidate the persisted configuration. Prefer safe runtime `503/404` diagnostics over preventing startup solely because a removable/mounted document root is momentarily absent, unless security depends on canonicalization. - ---- - -## 29. Snapshot/runtime model - -All web configuration must compile into the same immutable snapshot philosophy as the rest of RustProxy. - -Request path must not query SQLite. - -Compile/store: - -- normalized root/alias configuration; -- compiled regexes; -- validated FastCGI endpoints; -- parsed header/param templates; -- named location lookup maps; -- MIME map overlays; -- bounded limits; -- route action enum/representation. - -Keep dynamic state outside desired config: - -- FastCGI connection pool state; -- FastCGI counters; -- observed socket availability; -- filesystem metadata/cache observations if any are added. - -Do not persist live FastCGI connection state. - ---- - -## 30. FastCGI profile lifecycle - -On config publication: - -- reuse compatible runtime pool state when profile identity and endpoint/security parameters are unchanged, if practical; -- otherwise retire the old pool gracefully; -- existing in-flight requests hold their runtime references until completion; -- new requests use the new snapshot/profile; -- no request replay to another PHP-FPM endpoint after partial body transmission. - -This should follow the existing backend lease/publication philosophy. - ---- - -## 31. System/service integration - -RustProxy remains non-root. - -Documentation must explain: - -### Static roots - -The RustProxy service user needs read/search permission on the configured document tree. - -RustProxy must not automatically `chmod`, `chown` or upload site content in 0.25.0. - -### PHP-FPM Unix socket - -The service user needs permission to connect to the PHP-FPM socket. - -Document typical approaches: - -- configure PHP-FPM socket owner/group/mode; -- add RustProxy service user to the appropriate group where operationally acceptable. - -Do not automatically alter PHP-FPM configuration. - -### MAC policies - -Mention SELinux/AppArmor/system policy considerations where relevant, but do not weaken them automatically. - ---- - -## 32. Preset generation rules - -Presets must: - -- modify only the current vhost draft; -- show what will change before/after apply; -- preserve hostname/listener/TLS/auth settings unless a preset explicitly needs a web field; -- never delete backends silently; -- never silently remove custom routes; -- warn about route conflicts; -- create deterministic route IDs; -- produce valid ordinary configuration; -- be editable immediately after generation; -- pass the same validator as handwritten configuration. - -If the vhost already has routes, offer a clear conflict strategy in the UI rather than overwriting them automatically. - ---- - -## 33. Example proposed configuration — pure WordPress - -Illustrative only; final schema names may differ. - -```json -{ - "id": "wordpress", - "enabled": true, - "frontend": { - "hosts": ["www.example.com"], - "listener_ids": ["https"] - }, - "backends": [], - "web": { - "enabled": true, - "document_root": "/srv/www/wordpress", - "fallback": "web", - "indexes": ["index.php", "index.html"], - "autoindex": false, - "symlinks": "within_root", - "default_mime": "application/octet-stream", - "php_profiles": [ - { - "id": "php-default", - "endpoint": { - "type": "unix", - "path": "/run/php/php8.4-fpm.sock" - } - } - ] - }, - "routes": [ - { - "id": "php", - "path_prefix": "\\.php$", - "match_type": "regex", - "enabled": true, - "web": { - "handler": "fastcgi", - "fastcgi_profile": "php-default", - "require_script_file": true - } - }, - { - "id": "root-web", - "path_prefix": "/", - "match_type": "prefix", - "enabled": true, - "web": { - "handler": "static", - "try_files": [ - {"kind": "file", "value": "$uri"}, - {"kind": "directory", "value": "$uri"}, - {"kind": "uri", "value": "/index.php", "query": "$args"} - ] - } - } - ] -} -``` - -A final implementation may use the vhost web fallback for the root static action instead of persisting a synthetic `/` route. Whichever representation is chosen, do not reintroduce the old ambiguity between the vhost fallback and a user Custom Location. - ---- - -## 34. Example proposed configuration — mixed static/PHP/proxy - -Use case: - -- website served locally; -- `/api/` proxied to an application backend; -- `.php` sent to PHP-FPM. - -Conceptually: - -```text -/api/ -> existing Proxy action -*.php -> new FastCGI action -all else -> local Static web fallback -``` - -The `/api/` proxy must use the same existing backend selection, health, retry/capacity, WebSocket and cache rules as 0.24.2. - -Turning on WWW must not route `/api/` through FastCGI or filesystem code when the existing Custom Location selected Proxy. - ---- - -## 35. Source-file change map - -Expected areas. Keep changes focused. - -### `src/vhost.rs` - -- optional `web` vhost settings; -- optional route web settings; -- validation; -- conditional backend requirement; -- legacy synthetic-root normalization updated to recognize deliberate web route configuration. - -### `src/config.rs` - -- compile web settings; -- compile route actions; -- compile local web fallback; -- keep existing match precedence; -- validate cross references; -- preserve claimed-host behavior. - -### `src/proxy/mod.rs` - -Keep existing proxy branch as intact as practical. - -Changes should mainly be: - -- dispatch selected compiled action; -- call the new web module for Static/FastCGI; -- preserve common ACL/auth/response pipeline; -- distinguish local web responses from placeholder/error local responses; -- expose final/effective route to response policy if internal rewrites can select another route. - -Do not implement filesystem/FastCGI protocol details directly in this large module. - -### `src/web/*` - -New implementation modules for: - -- secure path resolution; -- static files; -- conditions; -- try_files; -- internal rewrites; -- FastCGI protocol/client; -- MIME handling. - -### `src/admin/web/vhosts.js` - -- WWW tab; -- web settings editor; -- PHP profile editor; -- preset generator; -- web route actions; -- effective behavior preview; -- validation messages. - -### `src/admin/openapi.json` - -- schema additions only; preserve existing paths/contracts where possible. - -### `langs/en_US.json`, `langs/pl_pl.json` - -- all new UI text goes through discovered language packs; -- no hardcoded PL/EN switch. - -### Documentation - -Add at least: - -```text -docs/WEB_SERVER.md -docs/STATIC_FILES.md -docs/FASTCGI.md -docs/WEB_REWRITES.md -docs/WEB_PRESETS.md -``` - -Update: - -```text -README.md -ROADMAP.md -CHANGELOG.md -docs/VHOSTS.md -docs/CUSTOM_LOCATIONS.md -docs/API.md -docs/SECURITY.md -docs/TEST_REPORT.md -``` - ---- - -## 36. Tests — existing behavior regression gate - -Before considering new WWW functionality accepted, prove old behavior is unchanged. - -Keep/add tests for: - -- exact route precedence; -- `^~` priority prefix; -- regex order; -- longest prefix; -- custom route -> main backend fallback; -- claimed exact host does not fall through to broader wildcard; -- old synthetic root normalization; -- deliberate customized `/` remains a real route; -- existing preserve/strip/replace/regex proxy rewrite behavior; -- existing query preservation; -- local `return`; -- backend pinning; -- balancing; -- ACL composition; -- Basic Auth; -- Tinyauth; -- cache behavior; -- header rules; -- security headers; -- compression; -- WebSockets; -- H1/H2/H3. - -Add fixture tests that deserialize representative 0.24.2 JSON and verify effective compiled behavior under 0.25.0. - ---- - -## 37. Tests — static files - -Required tests: - -- GET existing file; -- HEAD existing file; -- missing file 404; -- directory trailing slash redirect; -- directory index HTML; -- directory index PHP enters FastCGI rather than static source; -- autoindex disabled; -- autoindex enabled; -- MIME known extension; -- MIME unknown extension; -- Content-Length; -- Last-Modified; -- ETag; -- If-None-Match 304; -- If-Modified-Since 304; -- validator precedence; -- Range start/end; -- suffix Range; -- open-ended Range; -- invalid/unsatisfiable Range 416; -- If-Range match/miss; -- HEAD + Range semantics; -- large file streaming; -- client disconnect; -- compression with static file; -- range not incorrectly compressed; -- custom response headers; -- custom error pages; -- H1/H2/H3. - ---- - -## 38. Tests — filesystem security - -Include explicit attacks: - -```text -/../secret -/%2e%2e/secret -/%2fetc/passwd -/%5c..%5csecret -/%252e%252e/secret -//..//secret -/a;b -malformed % escape -invalid UTF-8 escape -symlink -> outside root -symlink chain -> outside root -alias boundary confusion -root prefix string collision (/srv/www vs /srv/www2) -``` - -Also test races/behavior to the extent the selected safe filesystem API allows. - -PHP source must remain undisclosed under every failure case. - ---- - -## 39. Tests — try_files and rewrites - -Required: - -- `$uri` existing file; -- `$uri/` existing directory; -- fallback URI; -- named location; -- status fallback `=404` equivalent; -- query preserve; -- query replace; -- query append if supported; -- regex captures; -- `last` reselects location; -- `break` remains in location; -- maximum internal redirects; -- direct loop A -> A; -- loop A -> B -> A; -- rewritten path still passes ambiguity/path validation; -- condition file exists/not exists; -- condition directory exists; -- route ACL remains enforced after internal reselection. - ---- - -## 40. Tests — FastCGI - -Build a deterministic fake FastCGI peer for automated tests. Real PHP-FPM remains a release-host integration gate. - -Test: - -- Unix socket connect; -- TCP connect; -- BEGIN/PARAMS/STDIN protocol framing; -- long FastCGI param length encoding; -- record splitting; -- GET; -- HEAD; -- POST body streaming; -- large upload; -- chunked/H2/H3 request body adaptation; -- standard PHP-like response; -- `Status` header; -- repeated `Set-Cookie`; -- `Location`; -- stderr handling; -- END_REQUEST; -- malformed response headers; -- premature EOF; -- protocol status failure; -- connect refused; -- connect timeout; -- response timeout; -- queue capacity exhaustion; -- clean connection reuse; -- broken connection not reused; -- downstream cancellation; -- protected hop-by-hop headers stripped; -- `SCRIPT_FILENAME` correct; -- missing script rejected before FastCGI; -- script symlink outside root rejected; -- `REMOTE_ADDR` uses trusted effective identity; -- FastCGI custom params precedence; -- PATH_INFO disabled default; -- PATH_INFO explicit mode. - ---- - -## 41. Tests — presets/UI - -For every shipped preset: - -- generated JSON/schema validates; -- deterministic output; -- expected web root/fallback; -- expected routes; -- PHP handler where needed; -- safe blocked paths; -- no hidden runtime preset flag; -- applying a preset does not silently delete unrelated hostname/TLS/auth settings; -- conflict warning for existing routes; -- Custom mode round-trips generated values; -- Advanced JSON and visual editor edit the same model; -- EN/PL language packs contain every new key; -- `scripts/check_source.py` language key checks pass; -- JS syntax checks pass. - ---- - -## 42. Release-host integration tests - -Before production-ready status, test with real services: - -### PHP-FPM - -- supported distro PHP-FPM over Unix socket; -- TCP PHP-FPM; -- upload through PHP; -- slow PHP timeout; -- FPM restart while RustProxy remains running; -- socket disappears/reappears; -- connection pool recovery. - -### Applications - -At minimum: - -- WordPress install/front page/admin login/static assets/permalinks; -- Drupal front page/static assets/front-controller routing; -- Laravel or Symfony front controller; -- SPA fallback. - -### Transports - -Run application requests over: - -- HTTP/1.1; -- HTTP/2; -- HTTP/3. - -No separate local-web implementation per transport. - ---- - -## 43. Performance/resource requirements - -- no whole-file static buffering; -- no whole-request PHP upload buffering; -- bounded FastCGI connections; -- bounded FastCGI queue; -- bounded stderr capture; -- bounded rewrite depth; -- bounded try_files count; -- bounded index list; -- bounded named locations; -- bounded custom params; -- bounded autoindex generation; -- no request-path SQLite access; -- no request-path external configuration/provider calls; -- cancellation-aware I/O; -- full response/request permits retained for body lifetime consistent with existing resource policy. - -Cross-platform correctness first. `sendfile`/zero-copy may be an optimization after correct async streaming is proven. - ---- - -## 44. Suggested hard limits - -Final values should be selected consistently with existing project limits, but define explicit bounds for at least: - -- max web routes per vhost; -- max try_files candidates per route; -- max internal rewrites per route; -- max internal redirect depth; -- max named locations; -- max PHP profiles; -- max FastCGI custom params; -- max param name/value length; -- max FastCGI stderr captured per request; -- max index entries; -- max MIME overrides; -- max request body per web/PHP route; -- max FastCGI connections/profile; -- max FastCGI queue waiters. - -Do not create attacker-controlled unbounded vectors/maps on the request path. - ---- - -## 45. Implementation sequence - -### Phase 0 — lock current behavior - -- [ ] Add/confirm regression tests for current route/fallback behavior. -- [ ] Add 0.24.2 config fixtures. -- [ ] Add tests proving existing `PathRewrite` semantics. -- [ ] Add tests for ACL/auth/header/security order. -- [ ] Record current H1/H2/H3 behavior. - -### Phase 1 — additive schema and internal action model - -- [ ] Add optional `Vhost.web`. -- [ ] Add optional `PathRoute.web`. -- [ ] Add new config structs with `deny_unknown_fields`. -- [ ] Add validation. -- [ ] Add internal compiled action/target model. -- [ ] Compile all old configs to exactly existing Proxy/Return behavior. -- [ ] Allow zero HTTP backends only when configuration has no Proxy requirement. -- [ ] Update host ownership/fallback selection without changing legacy behavior. -- [ ] Update synthetic-root normalization. -- [ ] Run full regression suite before implementing filesystem access. - -### Phase 2 — static file engine - -- [ ] Secure URL -> filesystem path resolver. -- [ ] Root/alias. -- [ ] GET/HEAD. -- [ ] directory redirects. -- [ ] index. -- [ ] MIME. -- [ ] Content-Length. -- [ ] ETag/Last-Modified. -- [ ] conditional GET. -- [ ] Range/If-Range. -- [ ] streaming body. -- [ ] symlink policy. -- [ ] dotfile/sensitive-file policy. -- [ ] PHP source deny. -- [ ] optional autoindex. -- [ ] local web response marker/pipeline integration. - -### Phase 3 — web routing - -- [ ] structured try_files. -- [ ] named internal locations. -- [ ] safe variables. -- [ ] file/directory conditions. -- [ ] internal rewrite engine. -- [ ] query handling. -- [ ] `last`. -- [ ] `break`. -- [ ] internal redirect limit. -- [ ] final route response-header policy handling. -- [ ] rewrite/loop diagnostics. - -### Phase 4 — FastCGI/PHP-FPM - -- [ ] FastCGI wire protocol. -- [ ] Unix sockets. -- [ ] TCP. -- [ ] connection pool. -- [ ] timeouts/queue/cancellation. -- [ ] PARAMS generation. -- [ ] streaming STDIN. -- [ ] streaming STDOUT. -- [ ] bounded STDERR. -- [ ] CGI response header parser. -- [ ] status/error mapping. -- [ ] PHP script existence/security. -- [ ] FastCGI params. -- [ ] optional PATH_INFO. -- [ ] real PHP-FPM integration test. - -### Phase 5 — WWW dashboard - -- [ ] new tab. -- [ ] web root editor. -- [ ] fallback editor. -- [ ] PHP profiles. -- [ ] FastCGI test UI. -- [ ] static options. -- [ ] try_files builder. -- [ ] rewrite builder. -- [ ] named locations. -- [ ] location action Static/FastCGI. -- [ ] effective behavior preview. -- [ ] route conflict detection. -- [ ] no duplicate independent location system. - -### Phase 6 — presets - -- [ ] Static. -- [ ] SPA. -- [ ] Generic PHP. -- [ ] WordPress. -- [ ] Drupal modern. -- [ ] Drupal legacy `q=`. -- [ ] Joomla. -- [ ] Laravel. -- [ ] Symfony. -- [ ] Nextcloud. -- [ ] phpMyAdmin. -- [ ] fixture tests for every preset. - -### Phase 7 — API/observability/docs - -- [ ] OpenAPI. -- [ ] metrics. -- [ ] Prometheus. -- [ ] live logs. -- [ ] status diagnostics. -- [ ] EN/PL translations. -- [ ] docs. -- [ ] README/ROADMAP/CHANGELOG. - -### Phase 8 — release acceptance - -- [ ] `python3 scripts/preflight.py`. -- [ ] `python3 scripts/check_source.py`. -- [ ] `python3 scripts/release_smoke.py`. -- [ ] `./dev.sh fmt`. -- [ ] `./dev.sh check`. -- [ ] `./dev.sh test`. -- [ ] JS syntax checks. -- [ ] OpenAPI/lang JSON parse. -- [ ] real PHP-FPM Unix/TCP. -- [ ] WordPress runtime. -- [ ] Drupal runtime. -- [ ] H1/H2/H3 runtime. -- [ ] security/path traversal suite. -- [ ] large file/upload streaming/resource test. -- [ ] update `docs/TEST_REPORT.md` with what was actually executed vs blocked. - ---- - -## 46. Explicit non-goals for 0.25.0 - -Unless separately accepted, do not expand scope into: - -- full Nginx config parser; -- `.htaccess` parser; -- Apache config compatibility; -- arbitrary embedded scripting; -- CGI process execution; -- mod_php equivalent; -- Node/Python/Ruby process manager; -- site file upload/file manager; -- automatic CMS installer/updater; -- automatic PHP-FPM configuration changes; -- arbitrary shell commands from web config; -- arbitrary environment variable exposure; -- FastCGI cache without a separate cache design; -- reverse proxy behavior rewrite; -- new authentication stack; -- new TLS stack. - -The feature is a web/static/FastCGI origin inside the existing RustProxy vhost and policy architecture. - ---- - -## 47. Definition of Done - -RustProxy 0.25.0 Web Server is complete when all of the following are true: - -- an unchanged 0.24.2 proxy config behaves the same under 0.25.0; -- a vhost can run with zero HTTP backends when it is a pure web server; -- a vhost can mix local static/PHP and existing proxied locations; -- static files are streamed securely from disk; -- directory/index handling works; -- PHP source cannot leak through static fallback; -- PHP-FPM works over Unix socket and TCP; -- request/response bodies stream with bounded resources; -- FastCGI params have safe trusted values; -- `try_files` handles normal front-controller applications; -- internal rewrites are bounded and do not reuse/alter legacy proxy rewrite semantics; -- Drupal/WordPress presets generate editable ordinary config; -- existing ACL/Auth/Tinyauth/rate/security/header/compression policies still apply; -- existing H1/H2/H3 request pipeline is reused; -- path traversal/symlink/root escape tests pass; -- existing proxy/location/cache/WebSocket regression tests pass; -- UI/API/manual JSON all edit the same canonical model; -- release documentation clearly distinguishes verified runtime tests from static/source checks. - ---- - -## 48. Recommended architectural summary - -The lowest-risk design is: - -```text -Existing vhost/frontend/listener/auth/ACL pipeline - | - v - Existing location matcher - | - +--------+---------+-----------+ - | | | - Proxy Return Web - | | | - existing code existing code +---------------------+ - Static FastCGI - | | - filesystem origin PHP-FPM origin - +----------+----------+ - | - v - existing response policy - headers/security/compression/etc. -``` - -The WWW tab is a configuration UX over this canonical vhost model, not a separate server process and not a second competing routing engine. - -The main compatibility rule is simple: - -> If `web` is not configured, the request must follow the same path and semantics as RustProxy 0.24.2. diff --git a/build.rs b/build.rs index 04d06fb..b3f2f29 100644 --- a/build.rs +++ b/build.rs @@ -16,7 +16,7 @@ fn swagger_assets( ] { println!("cargo:rerun-if-env-changed={name}"); } - let helper = root.join("scripts/swagger_assets.py"); + let helper = root.join("scripts/build/swagger_assets.py"); println!("cargo:rerun-if-changed={}", helper.display()); let interpreters = match env::var_os("RUST_PROXY_PYTHON") { Some(value) if !value.is_empty() => vec![value], diff --git a/dev.sh b/dev.sh index dfece4c..d5cb63b 100755 --- a/dev.sh +++ b/dev.sh @@ -18,7 +18,7 @@ if [[ $# -gt 0 ]]; then shift; fi case "$mode" in help|-h|--help) cat <<'HELP' -RustProxy 0.27.2 +RustProxy 0.27.3 ./dev.sh Build and start proxy + local panel ./dev.sh run [serve flags] Same, with extra server options ./dev.sh admin setup-token Generate/print a one-time setup capability before first login @@ -66,7 +66,7 @@ HELP *) printf 'Unknown command: %s. Use ./dev.sh help\n' "$mode" >&2; exit 2 ;; esac if [[ "$mode" == "backup" ]]; then - exec python3 scripts/backup.py --data-dir "${RUST_PROXY_DATA_DIR:-$ROOT/data}" "$@" + exec python3 tools/legacy/backup.py --data-dir "${RUST_PROXY_DATA_DIR:-$ROOT/data}" "$@" fi if ! command -v python3 >/dev/null 2>&1; then printf 'Python 3.10+ is required for source preflight and upgrade safety checks.\n' >&2 @@ -74,7 +74,7 @@ if ! command -v python3 >/dev/null 2>&1; then fi python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 10) else "Python 3.10+ is required.")' if [[ "$mode" == "doctor" ]]; then - python3 scripts/preflight.py + python3 tools/checks/preflight.py printf 'Source layout: OK\n' for tool in cargo rustc cc python3; do printf '%s: ' "$tool" @@ -82,27 +82,27 @@ if [[ "$mode" == "doctor" ]]; then done exit 0 fi -if [[ "$mode" == "acme" ]]; then exec python3 scripts/acme_provider.py "$@"; fi -if [[ "$mode" == "mock-auth" ]]; then exec python3 scripts/mock_auth.py "$@"; fi -if [[ "$mode" == "mock" ]]; then exec python3 scripts/mock_server.py "$@"; fi -python3 scripts/preflight.py --repair-known +if [[ "$mode" == "acme" ]]; then exec python3 scripts/runtime/acme_provider.py "$@"; fi +if [[ "$mode" == "mock-auth" ]]; then exec python3 tools/dev/mock_auth.py "$@"; fi +if [[ "$mode" == "mock" ]]; then exec python3 tools/dev/mock_server.py "$@"; fi +python3 tools/checks/preflight.py --repair-known if [[ "$mode" == "repair" ]]; then printf 'Source preflight: OK\n'; exit 0; fi if [[ "$mode" == "test" ]]; then - python3 scripts/rust_module_contracts.py - python3 scripts/source_module_contracts.py - python3 scripts/check_source.py - python3 scripts/release_smoke.py - python3 scripts/memory_contracts.py - python3 scripts/web_server_contracts.py - python3 scripts/security_contracts.py - python3 scripts/security_dependency_contracts.py + python3 tools/checks/rust_module_contracts.py + python3 tools/checks/source_module_contracts.py + python3 tools/checks/check_source.py + python3 tools/checks/release_smoke.py + python3 tools/checks/memory_contracts.py + python3 tools/checks/web_server_contracts.py + python3 tools/checks/security_contracts.py + python3 tools/checks/security_dependency_contracts.py if command -v node >/dev/null 2>&1; then - node scripts/web_service_ui_test.mjs + node tools/checks/web_service_ui_test.mjs else printf 'Node.js not found; skipping Web Service UI behavior test.\n' >&2 fi - python3 scripts/docs_check.py - python3 scripts/langs_check.py + python3 tools/checks/docs_check.py + python3 tools/checks/langs_check.py fi if ! command -v cargo >/dev/null 2>&1; then cat >&2 <<'MISSING' @@ -139,7 +139,7 @@ if [[ "$mode" == "test" ]]; then printf 'Checking all Rust targets before tests...\n' >&2 cargo check --locked --all-targets fi -printf 'Building RustProxy 0.27.2...\n' >&2 +printf 'Building RustProxy 0.27.3...\n' >&2 cargo build --locked BIN="$CARGO_TARGET_DIR/debug/rust-proxy" case "$mode" in diff --git a/docker-agent/model.go b/docker-agent/model.go index 73f6c68..6af35e4 100644 --- a/docker-agent/model.go +++ b/docker-agent/model.go @@ -1,6 +1,6 @@ package main -const version = "0.27.2" +const version = "0.27.3" type config struct { Listen string diff --git a/docs/API_ACCESS.md b/docs/API_ACCESS.md index 186e159..07ef34f 100644 --- a/docs/API_ACCESS.md +++ b/docs/API_ACCESS.md @@ -1,4 +1,4 @@ -# Administration API access - 0.27.2 +# Administration API access - 0.27.3 ## Sign-in boundary @@ -132,7 +132,7 @@ promise to erase browser/OS screenshots or data intentionally downloaded by an a ## 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.32.11**, selected by `scripts/swagger_assets.py` and +The default is Swagger UI **5.32.11**, 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 @@ -204,7 +204,7 @@ build server. Browser tests still label their simulated API/Swagger renderer. After running the built server: ```bash -python3 scripts/admin_audit.py --url http://127.0.0.1:9090 +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 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index f8de221..9018fd5 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -42,7 +42,7 @@ committed document at startup; this is not one cross-memory/SQLite transaction. ### Rust source organization Rust modules are kept as small responsibility-oriented files. Every checked-in `.rs` file -is limited to **500 lines**; `scripts/rust_module_contracts.py` enforces the limit across +is limited to **500 lines**; `tools/checks/rust_module_contracts.py` enforces the limit across the repository. 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 @@ -190,7 +190,7 @@ the 0.7 scheduler and automated publication. 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/acme_provider.py` adapts Certbot +`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. diff --git a/docs/CACHE.md b/docs/CACHE.md index c572cce..e5b68ce 100644 --- a/docs/CACHE.md +++ b/docs/CACHE.md @@ -77,10 +77,10 @@ When cold storage is enabled, `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 paths/metadata rather than the body allocation. 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 +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.py`) +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 diff --git a/docs/CONTAINER.md b/docs/CONTAINER.md index 44e1906..44df0e0 100644 --- a/docs/CONTAINER.md +++ b/docs/CONTAINER.md @@ -9,7 +9,7 @@ each Docker host, so the proxy container never needs Docker-socket access. See Build locally with a normal OCI builder: ```bash -docker build -t rustproxy:0.27.2 . +docker build -t rustproxy:0.27.3 . ``` Persist `/var/lib/rustproxy`; it contains the SQLite database, certificates and the @@ -22,7 +22,7 @@ docker volume create rustproxy-data docker run --rm -d --name rustproxy \ -p 8080:8080 \ -v rustproxy-data:/var/lib/rustproxy \ - rustproxy:0.27.2 + rustproxy:0.27.3 docker exec rustproxy rust-proxy admin setup-token ``` diff --git a/docs/FILE_LOGS.md b/docs/FILE_LOGS.md index 978cf77..24e5baf 100644 --- a/docs/FILE_LOGS.md +++ b/docs/FILE_LOGS.md @@ -1,6 +1,6 @@ # Stored log explorer -RustProxy 0.27.2 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. +RustProxy 0.27.3 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 diff --git a/docs/HTTP3.md b/docs/HTTP3.md index 0a7bc87..35d261e 100644 --- a/docs/HTTP3.md +++ b/docs/HTTP3.md @@ -74,6 +74,9 @@ All changes to socket/ALPN/QUIC transport parameters require restart. 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. diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 8641b00..2ab354b 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,13 +1,22 @@ -# Installing and updating RustProxy 0.27.2 +# Installing and updating RustProxy 0.27.3 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. -Ready-to-use Debian packages are published separately at `https://rustproxy.linuxiarz.pl`. The Debian package-build repository and metadata are intentionally maintained outside this source tree. +## Official installation repositories -## Debian 13+ one-line source install/update +Use the project-maintained package repositories as the normal installation/update path: + +- **DEB/RPM:** `https://rustproxy.linuxiarz.pl/` +- **OPNsense plugin:** `https://rustproxy-opn.linuxiarz.pl/` + +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. + +## Optional: Debian 13+ one-line source install/update ```bash # auto: first install or managed update @@ -179,7 +188,7 @@ A source-tree preflight can still inspect a distribution-provided fallback envir ```bash sudo -u rustproxy env PATH=/usr/local/bin:/usr/bin:/bin \ - python3 scripts/acme_provider.py --doctor + python3 scripts/runtime/acme_provider.py --doctor ``` The provider script is embedded in the RustProxy binary; this command is diagnostic only. diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index aba671e..bd241c4 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -46,7 +46,7 @@ The administration server keeps the same strict Host/Origin/request-marker/CSRF 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.py`, `scripts/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. +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 @@ -80,7 +80,7 @@ There is no schema or configuration-format migration. This patch changes only ad 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/upgrade.py` is used while RustProxy is stopped, an obsolete +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. @@ -189,7 +189,7 @@ removed in 0.7.2 (see the build notes above). Source-tree updates still back up 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 scripts/admin_audit.py --url http://127.0.0.1:9090` after deployment. +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. --- @@ -302,7 +302,7 @@ unzip RustProxy-0.4.0.zip -d /home/mg/rustproxy-040-staging cd /home/mg/rustproxy-040-staging/RustProxy ``` -`scripts/backup.py` is a legacy release-test helper and is not a complete Secret +`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`. A missing database means there is no database to back up. Do not initialize an empty @@ -354,7 +354,7 @@ 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` / `scripts/backup.py` snapshot helper described by +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. @@ -378,7 +378,7 @@ 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 them with scripts/langs_check.py. No symlinked +correctly block a build; review them with tools/checks/langs_check.py. 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 diff --git a/docs/NPM_MIGRATION.md b/docs/NPM_MIGRATION.md index 246f893..80964a8 100644 --- a/docs/NPM_MIGRATION.md +++ b/docs/NPM_MIGRATION.md @@ -1,6 +1,6 @@ # Nginx Proxy Manager migration -RustProxy ships `scripts/migrate_npm.py` for a one-time, dry-run-first migration of +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 @@ -9,7 +9,7 @@ reads the current revision, merges generated vhosts into the existing configurat and writes only when `--apply` is supplied. `migrate_npm.py` is the CLI entry point and requires the sibling modules -`scripts/migrate_npm_core.py` and `scripts/migrate_npm_api.py`. They use only the +`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. @@ -58,7 +58,7 @@ Set the password in the environment or omit it and use the interactive prompt: export RUSTPROXY_USERNAME=admin export RUSTPROXY_PASSWORD='...' -python3 scripts/migrate_npm.py \ +python3 scripts/migration/migrate_npm.py \ --database /path/to/npm/data/database.sqlite \ --rustproxy https://rustproxy.example.net:9090 ``` @@ -89,7 +89,7 @@ same Force SSL policy to every imported host. Select explicit listener IDs when After reviewing `npm-migration-plan.json`: ```bash -python3 scripts/migrate_npm.py \ +python3 scripts/migration/migrate_npm.py \ --database /path/to/npm/data/database.sqlite \ --rustproxy https://rustproxy.example.net:9090 \ --apply @@ -109,9 +109,9 @@ RustProxy directory: docker run --rm -it \ --network host \ -v /path/to/npm/data:/npm:ro \ - -v "$PWD/scripts:/rustproxy-scripts:ro" \ + -v "$PWD/scripts/migration:/rustproxy-migration:ro" \ python:3.13-alpine \ - python /rustproxy-scripts/migrate_npm.py \ + python /rustproxy-migration/migrate_npm.py \ --database /npm/database.sqlite \ --rustproxy https://127.0.0.1:9090 \ --insecure-admin-tls diff --git a/docs/README.md b/docs/README.md index 6dcc2f2..afa5d89 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,7 @@ This is the canonical index for the current project documentation. The authentic - [0.25.1 release audit](V0.25.1_AUDIT.md) - historical Traffic/module-split release scope and compiler-regression guards. - [0.25.0 specification audit](V0.25.0_AUDIT.md) - Web Server foundation point-by-point implementation status. - [0.25.0 pre-production security audit](V0.25.0_SECURITY_PREPROD_AUDIT.md) - threat-oriented source/configuration review, fixes made in-session and mandatory runtime security gates. +- [0.27.3 implementation note](V0.27.3_IMPLEMENTATION.md) - HTTP/3 HEAD cache parity, COLD/rate-limit memory reduction, backup scope and package-path update. - [0.27.2 implementation note](V0.27.2_IMPLEMENTATION.md) - managed Certbot relocation/renewal repair. - [0.27.1 implementation note](V0.27.1_IMPLEMENTATION.md) - first-admin command, managed/fallback certificate runtime and account-scoped route-list sorting. - [0.27.0 implementation note](V0.27.0_IMPLEMENTATION.md) - compression defaults/scoped policy, persistent Docker publications and visible cache occupancy meter fill. @@ -70,7 +71,7 @@ This is the canonical index for the current project documentation. The authentic ## Historical or version-labelled context -Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md); [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) is retained as the prior managed-runtime/list-sorting milestone; [V0.27.0_IMPLEMENTATION.md](V0.27.0_IMPLEMENTATION.md) is retained as the prior compression/Docker-publication milestone; [V0.26.7_IMPLEMENTATION.md](V0.26.7_IMPLEMENTATION.md) is retained as the prior cache/backend/container milestone; [V0.26.6_IMPLEMENTATION.md](V0.26.6_IMPLEMENTATION.md) is retained as the prior access-policy/administration milestone; [V0.26.5_IMPLEMENTATION.md](V0.26.5_IMPLEMENTATION.md) is retained as the prior interface-scaling/listener-navigation milestone, [V0.26.4_IMPLEMENTATION.md](V0.26.4_IMPLEMENTATION.md) as the prior administration/backend-lifecycle milestone, [V0.26.3_IMPLEMENTATION.md](V0.26.3_IMPLEMENTATION.md) as the prior appearance/UI milestone, [V0.26.2_IMPLEMENTATION.md](V0.26.2_IMPLEMENTATION.md) as the prior Logs/UI milestone and [V0.26.1_IMPLEMENTATION.md](V0.26.1_IMPLEMENTATION.md) as the prior protocol-attribution milestone. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain historical evidence. +Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md); [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability. Older context documents such as [INTEGRATIONS_040](INTEGRATIONS_040.md), [MANAGEMENT](MANAGEMENT.md), [SECURITY_AUDIT](SECURITY_AUDIT.md), [ACME_RFC2136](ACME_RFC2136.md) and older version audits remain available for traceability but should not override the current functional/technical documents above. diff --git a/docs/ROADMAP_HISTORY.md b/docs/ROADMAP_HISTORY.md index 730d501..12eb7f1 100644 --- a/docs/ROADMAP_HISTORY.md +++ b/docs/ROADMAP_HISTORY.md @@ -147,7 +147,6 @@ Current direction: 0.23.3 -> 0.24.0 - [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. -- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on the release host. See [the 0.24.0 specification audit](V0.24.0_AUDIT.md). diff --git a/docs/SECRET_STORE_BACKUP.md b/docs/SECRET_STORE_BACKUP.md index f601d7e..198c11e 100644 --- a/docs/SECRET_STORE_BACKUP.md +++ b/docs/SECRET_STORE_BACKUP.md @@ -39,7 +39,7 @@ The encrypted backup contains the durable application state needed for recovery, - 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 and SQLite WAL/SHM sidecars. Symlinks are accepted only when their target is relative and resolves inside the staged RustProxy data tree; absolute, escaping or dangling links fail backup/restore. Restore recreates accepted links as links rather than dereferencing them. +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 remains included; the virtual environment is rebuilt when needed. Symlinks are accepted only when their target is relative and resolves inside the staged RustProxy data tree; absolute, escaping or dangling links fail backup/restore. Restore recreates accepted 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. diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md index d5beb41..2065dc2 100644 --- a/docs/TEST_REPORT.md +++ b/docs/TEST_REPORT.md @@ -1,45 +1,38 @@ -# Verification report - RustProxy 0.27.2 +# Verification report - RustProxy 0.27.3 ## Scope -RustProxy 0.27.2 is a focused repair for managed ACME/Certbot execution while keeping SQLite schema 8 and the saved configuration format unchanged. +RustProxy 0.27.3 fixes HTTP/3 `HEAD` cache eligibility, reduces persistent COLD-cache and rate-limit/IP memory retention, excludes rebuildable managed Python environments from backups, and updates the documented installation path. SQLite schema 8, the saved configuration format and COLD format 3 remain unchanged. -0.27.1 created the private environment under `/acme-python.installing`, installed Certbot there, validated it, and then renamed the directory to `/acme-python`. pip console scripts contain an absolute interpreter path, so `bin/certbot` could retain a shebang pointing at the removed staging directory and fail with `OSError` during issuance/renewal. +The H3 repair is intentionally narrow: cache policy already accepts both `GET` and `HEAD`; the forwarding path now performs the existing bounded empty-body probe for both methods. This prevents an initially unpolled HTTP/3 receive body from being mistaken for request content. -0.27.2 validates and launches managed Certbot through the final venv interpreter with `python -I -m certbot`. Existing 0.27.1 environments can therefore be reused without deleting `/var/lib/rustproxy/acme-python`. The system/distribution Certbot fallback remains unchanged. +The COLD index no longer stores full response headers or origin validator values for every disk object. These are loaded and validated from the existing metadata JSON only on an actual COLD read. The in-memory SHA-256 is 32 bytes and the map-owned key is not duplicated inside `ColdEntry`. Rate limiting now groups IP buckets below one vhost string, sweeps expired fixed-window buckets during requests and periodic runtime maintenance, and shrinks sparse hash tables before allocator trimming. -The 0.27.1 first-administrator, runtime-status and route/list sorting behavior remains unchanged. +Compression was reviewed but its wire behavior was not changed: it remains streaming through `tower_http`, uses `CompressionLevel::Fastest`, and does not aggregate a complete response body before encoding. Its primary RAM cost is therefore per concurrently active compressed response/codec state, not long-lived retained objects. -## Regression coverage - -Static/source contracts cover the managed runtime path and pinned package set, distro fallback markers and the new module-based Certbot launch path. A dedicated relocation regression reproduces the stale-shebang failure after renaming a venv and verifies that module execution through the final interpreter remains valid. Existing release-smoke, language/OpenAPI, source-module, security, web-server, browser-service and Docker-agent gates remain part of the Rust-independent verification set. +Application-coupled Python is now separated under `scripts/runtime`, `scripts/build`, `scripts/deploy` and `scripts/migration`; QA/dev/audit/legacy helpers live under `tools/`, while OPNsense/RouterOS helpers live under `integrations/`. Release checks validate the new paths and archive contents. ## Verification status in this workspace -Rust/Cargo is not installed in this workspace. Rust-independent checks are run here; compiler/type/runtime gates remain explicitly open until this source is checked on a Rust-enabled release host. +Rust/Cargo is not installed in this workspace. Rust-independent checks are run here; compiler/type/runtime gates and real H2/H3 protocol execution remain open until this source is checked on a Rust-enabled host. -| Check | Status | -|---|---| -| managed ACME relocated-venv regression (`release_smoke.py`) | PASS | -| changed administration JavaScript syntax (`node --check`) | PASS | -| language/OpenAPI JSON parse | PASS | -| `python3 scripts/langs_check.py` | PASS | -| `python3 scripts/memory_contracts.py` | PASS | -| `python3 scripts/web_server_contracts.py` | PASS | -| `python3 scripts/security_contracts.py` | PASS | -| `python3 scripts/security_dependency_contracts.py` | PASS | -| `python3 scripts/source_module_contracts.py` | PASS | -| `python3 scripts/rust_module_contracts.py` | PASS | -| `python3 scripts/docs_check.py` | PASS | -| `python3 scripts/release_smoke.py` | PASS | -| `node scripts/web_service_ui_test.mjs` | PASS | -| `go test ./...` in `docker-agent` | PASS | -| `python3 scripts/check_source.py` | PASS; explicitly reports Rust parsing/types/runtime as not verified | -| `./dev.sh test` | Rust-independent gates PASS; wrapper exits 127 at the Rust stage because Cargo is unavailable | -| `./dev.sh check` | BLOCKED: Rust/Cargo toolchain unavailable | -| `cargo fmt --check` | BLOCKED: Rust/Cargo toolchain unavailable | -| `cargo clippy --all-targets --all-features -- -D warnings` | BLOCKED: Rust/Cargo toolchain unavailable | -| `cargo test --all-targets --all-features` | BLOCKED: Rust/Cargo toolchain unavailable | -| complete release build | BLOCKED at Rust compilation: Rust/Cargo toolchain unavailable | +The final status table is refreshed from the release checks performed for this archive. Static/source contracts do not substitute for Rust parsing, type checking, unit tests or real protocol runtime tests. -The 0.27.2 source archive must not be described as compiler-verified or production-ready until the Rust-enabled release-host gates pass. Static/source contracts do not substitute for Rust parsing, type checking, Clippy, unit tests or real protocol runtime tests. +## Release checks + +| Check | Result | +| --- | --- | +| Rust/source module contracts | PASS | +| Bounded-memory/cache/rate-limit/compression contracts | PASS | +| Documentation and language-pack checks | PASS | +| Web Server, security and dependency source contracts | PASS | +| `tools/checks/check_source.py` | PASS (compiler/runtime explicitly not verified) | +| `tools/checks/release_smoke.py` | PASS | +| Python syntax (`py_compile`) for changed release scripts | PASS | +| Browser Web Service UI regression (`node tools/checks/web_service_ui_test.mjs`) | PASS | +| Docker Agent (`go test ./...`) | PASS | +| `./dev.sh test` static/pre-compiler stages | PASS | +| Rust `cargo fmt/check/test`, linking and Rust unit tests | NOT VERIFIED - Cargo/Rust is not installed in this workspace | +| Live rebuilt HTTP/2 + HTTP/3 `HEAD` cache test | NOT VERIFIED - requires a compiled 0.27.3 binary and QUIC-capable runtime | + +`./dev.sh test` exits with status 127 only when it reaches the Rust toolchain gate; all earlier release/static checks complete successfully. diff --git a/docs/V0.24.3_AUDIT.md b/docs/V0.24.3_AUDIT.md index 57de4b6..d8893d8 100644 --- a/docs/V0.24.3_AUDIT.md +++ b/docs/V0.24.3_AUDIT.md @@ -8,7 +8,7 @@ | Requirement | Source implementation | Status | |---|---|---| -| Bound glibc arena proliferation for managed systemd installs | `deploy/rustproxy.service` and `scripts/deploy.py::service_unit` set `MALLOC_ARENA_MAX=2`; Debian packaging moved to the external packaging repository in 0.27.0, and `/etc/rustproxy/environment` remains the administrator override | implemented in source | +| Bound glibc arena proliferation for managed systemd installs | `deploy/rustproxy.service` and `scripts/deploy/deploy.py::service_unit` set `MALLOC_ARENA_MAX=2`; Debian packaging moved to the external packaging repository in 0.27.0, and `/etc/rustproxy/environment` remains the administrator override | implemented in source | | Avoid a duplicate identity transform buffer | `src/transform/mod.rs::decode` returns `Cow<[u8]>`; identity input is borrowed and compressed input owns only the decoded buffer | implemented in source | | Reduce JS/CSS minifier peak allocations | `src/transform/js.rs` and `src/transform/css.rs` iterate with `Peekable` rather than collecting `Vec`, and return the completed `String` allocation with `into_bytes()` | implemented in source | | Release temporary transform buffers earlier | successful changed transforms drop the decoded/collected origin buffers before final response construction | implemented in source | diff --git a/docs/V0.24.6_AUDIT.md b/docs/V0.24.6_AUDIT.md index 1764a39..eee701a 100644 --- a/docs/V0.24.6_AUDIT.md +++ b/docs/V0.24.6_AUDIT.md @@ -50,7 +50,7 @@ This audit is source/static in the packaging workspace because Rust/Cargo and he ## Added regression coverage -Rust tests cover preset serialization/database reopen, preset-only cache identity stability, transform memory-budget math, bounded decompression, false Content-Length aggregation, proactive pending-task limits, stale revalidation limits and single-flight COLD cleanup. `scripts/memory_contracts.py` independently verifies the source-level bounds above when Cargo is unavailable and is part of `./dev.sh test`. +Rust tests cover preset serialization/database reopen, preset-only cache identity stability, transform memory-budget math, bounded decompression, false Content-Length aggregation, proactive pending-task limits, stale revalidation limits and single-flight COLD cleanup. `tools/checks/memory_contracts.py` independently verifies the source-level bounds above when Cargo is unavailable and is part of `./dev.sh test`. ## Release-host acceptance gate diff --git a/docs/V0.24.7_AUDIT.md b/docs/V0.24.7_AUDIT.md index 2d29042..6ba2735 100644 --- a/docs/V0.24.7_AUDIT.md +++ b/docs/V0.24.7_AUDIT.md @@ -68,7 +68,7 @@ The dedicated Transform dashboard also shows current process-wide reserved bytes - local atomic inflight reservation obeys a runtime concurrency ceiling; - SQLite close/reopen preserves transform preset, memory budget and concurrency limit; - cache identity ignores resource-only changes but rotates for transform representation/eligibility changes; -- `scripts/memory_contracts.py` verifies the new dynamic memory/concurrency bounds without Rust; +- `tools/checks/memory_contracts.py` verifies the new dynamic memory/concurrency bounds without Rust; - source/release smoke checks verify the Transform UI, OpenAPI fields and release identity. - source/release smoke checks reject `anyhow::Context` on the boxed bounded-body collection error and require explicit `anyhow::Error` mapping. diff --git a/docs/V0.25.0_AUDIT.md b/docs/V0.25.0_AUDIT.md index 58b48ff..793a976 100644 --- a/docs/V0.25.0_AUDIT.md +++ b/docs/V0.25.0_AUDIT.md @@ -97,12 +97,12 @@ The Reader initially embedded a generated `OUT_DIR/docs-content.json`. A clean/i ## Verification performed in this workspace -- `python3 scripts/check_source.py` - PASS. -- `python3 scripts/release_smoke.py` - PASS. -- `python3 scripts/memory_contracts.py` - PASS. -- `python3 scripts/web_server_contracts.py` - PASS. -- `python3 scripts/docs_check.py` - PASS. -- `python3 scripts/langs_check.py` - PASS. +- `python3 tools/checks/check_source.py` - PASS. +- `python3 tools/checks/release_smoke.py` - PASS. +- `python3 tools/checks/memory_contracts.py` - PASS. +- `python3 tools/checks/web_server_contracts.py` - PASS. +- `python3 tools/checks/docs_check.py` - PASS. +- `python3 tools/checks/langs_check.py` - PASS. - `node --check` for app, Settings, Docs Reader, Web Server and vhost administration JavaScript - PASS. - OpenAPI and both language JSON files parse - PASS. - `bash -n dev.sh` - PASS. diff --git a/docs/V0.25.0_SECURITY_PREPROD_AUDIT.md b/docs/V0.25.0_SECURITY_PREPROD_AUDIT.md index 42e0e1c..e79926c 100644 --- a/docs/V0.25.0_SECURITY_PREPROD_AUDIT.md +++ b/docs/V0.25.0_SECURITY_PREPROD_AUDIT.md @@ -23,15 +23,15 @@ The reference/generated systemd units were also aligned with the stronger Debian Passed in the audit environment: -- `python3 scripts/check_source.py` -- `python3 scripts/web_server_contracts.py` -- `python3 scripts/security_contracts.py` -- `python3 scripts/security_dependency_contracts.py` (offline minimum-version gates for RustSec items reviewed on 2026-10-04) -- `node scripts/web_service_ui_test.mjs` (preset selection + Save serialization regression) -- `python3 scripts/memory_contracts.py` -- `python3 scripts/docs_check.py` -- `python3 scripts/langs_check.py` -- `python3 scripts/release_smoke.py` +- `python3 tools/checks/check_source.py` +- `python3 tools/checks/web_server_contracts.py` +- `python3 tools/checks/security_contracts.py` +- `python3 tools/checks/security_dependency_contracts.py` (offline minimum-version gates for RustSec items reviewed on 2026-10-04) +- `node tools/checks/web_service_ui_test.mjs` (preset selection + Save serialization regression) +- `python3 tools/checks/memory_contracts.py` +- `python3 tools/checks/docs_check.py` +- `python3 tools/checks/langs_check.py` +- `python3 tools/checks/release_smoke.py` - `node --check` for all administration JavaScript - `bash -n` for managed shell scripts - `go test ./...` for `docker-agent` @@ -41,7 +41,7 @@ Passed in the audit environment: - hard-coded secret scan for common private-key/token patterns: no production secret found; documentation placeholders only - manual review of the locked major network/security dependencies against current RustSec advisories, plus an offline regression deny-list for the reviewed 2026 malicious-crate campaign -`./dev.sh test` runs every non-Rust phase successfully and then stops because `cargo/rustc` are unavailable. `scripts/admin_audit.py` was also attempted, but no compiled/running RustProxy administration listener exists in this workspace, so its live probes are **blocked/unexecuted**, not passed. +`./dev.sh test` runs every non-Rust phase successfully and then stops because `cargo/rustc` are unavailable. `tools/audit/admin_audit.py` was also attempted, but no compiled/running RustProxy administration listener exists in this workspace, so its live probes are **blocked/unexecuted**, not passed. ## Dependency review @@ -119,7 +119,7 @@ Do not treat this audit as production approval until all of these pass on the ex 1. Install stable Rust/Cargo and run `./dev.sh doctor`, `./dev.sh check`, `./dev.sh test`, `cargo test --locked` and a release build. 2. Run `cargo audit` (and preferably OSV/Trivy or equivalent) against the exact `Cargo.lock`; resolve vulnerabilities and review unmaintained/yanked transitive crates. -3. Start the exact release binary under the target systemd unit and run `scripts/admin_audit.py` against loopback. +3. Start the exact release binary under the target systemd unit and run `tools/audit/admin_audit.py` against loopback. 4. Run active malformed HTTP tests for duplicate/ambiguous framing, oversized headers, slow headers/bodies, oversized request bodies, H2/H3 stream floods, WebSocket idle/connection exhaustion and Range edge cases. 5. Fuzz/property-test `routing_path`, rewrite/`try_files`, `.htaccess`, CGI header parsing and backup restore parsers. 6. Run filesystem escape tests using symlink races, nested links, renames and mount/bind-mount edge cases on the production kernel/filesystem. diff --git a/docs/V0.25.1_AUDIT.md b/docs/V0.25.1_AUDIT.md index 1a3592d..bafacc0 100644 --- a/docs/V0.25.1_AUDIT.md +++ b/docs/V0.25.1_AUDIT.md @@ -25,23 +25,23 @@ The current source tree contains the intended corrections: - the split Web Server response-header fragment imports sibling `htaccess` explicitly and no longer retains the stale `ACCEPT_ENCODING`/`FromStr` imports; - split proxy/config/Prometheus fragments no longer export unnecessary `mut` bindings or unused values from their `include!` tuples. -`python3 scripts/rust_module_contracts.py` checks these conditions without a compiler. A real `cargo check --locked --all-targets` remains mandatory on the release host. +`python3 tools/checks/rust_module_contracts.py` checks these conditions without a compiler. A real `cargo check --locked --all-targets` remains mandatory on the release host. ## Verification gates Rust-independent acceptance for this package includes: ```text -python3 scripts/rust_module_contracts.py -python3 scripts/source_module_contracts.py -python3 scripts/check_source.py -python3 scripts/release_smoke.py -python3 scripts/memory_contracts.py -python3 scripts/web_server_contracts.py -python3 scripts/security_contracts.py -python3 scripts/security_dependency_contracts.py -python3 scripts/docs_check.py -python3 scripts/langs_check.py +python3 tools/checks/rust_module_contracts.py +python3 tools/checks/source_module_contracts.py +python3 tools/checks/check_source.py +python3 tools/checks/release_smoke.py +python3 tools/checks/memory_contracts.py +python3 tools/checks/web_server_contracts.py +python3 tools/checks/security_contracts.py +python3 tools/checks/security_dependency_contracts.py +python3 tools/checks/docs_check.py +python3 tools/checks/langs_check.py go test ./... # from docker-agent/ ``` diff --git a/docs/V0.26.6_IMPLEMENTATION.md b/docs/V0.26.6_IMPLEMENTATION.md index 6e4c478..d78d759 100644 --- a/docs/V0.26.6_IMPLEMENTATION.md +++ b/docs/V0.26.6_IMPLEMENTATION.md @@ -14,7 +14,7 @@ After `/api/acme/issue` accepts an issuance request, the UI remembers that job I ## NPM migration helper -`scripts/migrate_npm.py` imports sibling modules `scripts/migrate_npm_core.py` and `scripts/migrate_npm_api.py`. They use the Python standard library and require no third-party package installation, but all three files must stay together. The documented container command now mounts the complete `scripts` directory. +`scripts/migration/migrate_npm.py` imports sibling modules `scripts/migration/migrate_npm_core.py` and `scripts/migration/migrate_npm_api.py`. They use the Python standard library and require no third-party package installation, but all three files must stay together. The current documented container command mounts the dedicated `scripts/migration` directory. ## ACL policy composition diff --git a/docs/V0.27.2_IMPLEMENTATION.md b/docs/V0.27.2_IMPLEMENTATION.md index 89ca6d8..0ce060f 100644 --- a/docs/V0.27.2_IMPLEMENTATION.md +++ b/docs/V0.27.2_IMPLEMENTATION.md @@ -25,3 +25,10 @@ The existing system/distribution Certbot fallback continues to execute its disco ## Upgrade behavior No ACME profile, account, certificate or credential migration is required. An `acme-python` environment created by 0.27.1 can be reused as-is because 0.27.2 bypasses the stale console-script shebang and imports Certbot from the environment's installed Python modules. + + +## 0.27.3 correction + +The module-based workaround described above was invalid for current Certbot packages: Certbot 5.8.0 installs a `certbot` console entry point but does not expose a runnable `certbot.__main__`, so `python -m certbot` fails even when installation and plugin imports are valid. + +0.27.3 no longer relocates a freshly built managed environment after pip creates its console scripts. The environment is created/rebuilt directly at `/acme-python`, validation executes `/acme-python/bin/certbot --version`, and issuance/renewal use that same final console entry point. An older environment with a stale relocated shebang fails validation and is rebuilt at the final path. diff --git a/docs/V0.27.3_IMPLEMENTATION.md b/docs/V0.27.3_IMPLEMENTATION.md new file mode 100644 index 0000000..22aeede --- /dev/null +++ b/docs/V0.27.3_IMPLEMENTATION.md @@ -0,0 +1,58 @@ +# 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, and promotes package repositories as the normal installation path. SQLite schema 8, saved configuration compatibility and the COLD cache on-disk format remain unchanged. + +## 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. + +## 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. diff --git a/grafana/README.md b/grafana/README.md index aca7cf9..a9b2b7f 100644 --- a/grafana/README.md +++ b/grafana/README.md @@ -19,7 +19,7 @@ classes, cache outcomes, capture-state gauges and configured-route traffic. Raw are intentionally absent from Prometheus labels. Import the JSON in Grafana and select a Prometheus datasource when prompted. The -dashboard expects RustProxy 0.27.2 metric names and a scrape interval around 15s. +dashboard expects RustProxy 0.27.3 metric names and a scrape interval around 15s. Collectors can be disabled in RustProxy settings; panels for disabled collectors will simply show no series. diff --git a/grafana/rustproxy-overview.json b/grafana/rustproxy-overview.json index a5066b6..e4a1d38 100644 --- a/grafana/rustproxy-overview.json +++ b/grafana/rustproxy-overview.json @@ -4529,9 +4529,9 @@ }, "timepicker": {}, "timezone": "browser", - "title": "RustProxy 0.27.2 - Operations", + "title": "RustProxy 0.27.3 - Operations", "uid": "rustproxy-0170", "version": 5, "weekStart": "", - "description": "RustProxy 0.27.2 operations dashboard including backend client lifecycle/retry diagnostics, bounded Traffic analytics, country and User-Agent-family visibility, and built-in Web Server traffic." + "description": "RustProxy 0.27.3 operations dashboard including backend client lifecycle/retry diagnostics, bounded Traffic analytics, country and User-Agent-family visibility, and built-in Web Server traffic." } diff --git a/install.sh b/install.sh index 3b2a8b5..7a2f701 100755 --- a/install.sh +++ b/install.sh @@ -2,4 +2,4 @@ # Install an already built binary; never download or compile code as root. set -euo pipefail ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -exec python3 "$ROOT/scripts/deploy.py" install "$@" +exec python3 "$ROOT/scripts/deploy/deploy.py" install "$@" diff --git a/integrations/README.md b/integrations/README.md new file mode 100644 index 0000000..fbe09c2 --- /dev/null +++ b/integrations/README.md @@ -0,0 +1,7 @@ +# External integrations + +Platform-specific helpers that integrate RustProxy with software outside the application itself. +They are not required by the RustProxy runtime. + +- `opnsense-acme/` - OPNsense os-acme-client certificate deployment helper. +- `routeros-oci-build/` - RouterOS-oriented OCI build/publish helper. diff --git a/scripts/os-acme-client/README.md b/integrations/opnsense-acme/README.md similarity index 100% rename from scripts/os-acme-client/README.md rename to integrations/opnsense-acme/README.md diff --git a/scripts/os-acme-client/opnsense-rustproxy-cert-deploy.sh b/integrations/opnsense-acme/opnsense-rustproxy-cert-deploy.sh similarity index 100% rename from scripts/os-acme-client/opnsense-rustproxy-cert-deploy.sh rename to integrations/opnsense-acme/opnsense-rustproxy-cert-deploy.sh diff --git a/scripts/routeros-oci-build/.env.routeros.example b/integrations/routeros-oci-build/.env.routeros.example similarity index 100% rename from scripts/routeros-oci-build/.env.routeros.example rename to integrations/routeros-oci-build/.env.routeros.example diff --git a/scripts/routeros-oci-build/Dockerfile.routeros b/integrations/routeros-oci-build/Dockerfile.routeros similarity index 90% rename from scripts/routeros-oci-build/Dockerfile.routeros rename to integrations/routeros-oci-build/Dockerfile.routeros index c0a541a..8bf9fcb 100644 --- a/scripts/routeros-oci-build/Dockerfile.routeros +++ b/integrations/routeros-oci-build/Dockerfile.routeros @@ -29,7 +29,7 @@ RUN apt-get update \ && install -d -o rustproxy -g rustproxy -m 0750 /var/log/rustproxy COPY --from=build /build/target/release/rust-proxy /usr/local/bin/rust-proxy -COPY scripts/routeros-oci-build/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +COPY integrations/routeros-oci-build/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod 0755 \ /usr/local/bin/rust-proxy \ diff --git a/scripts/routeros-oci-build/Dockerfile.routeros.dockerignore b/integrations/routeros-oci-build/Dockerfile.routeros.dockerignore similarity index 100% rename from scripts/routeros-oci-build/Dockerfile.routeros.dockerignore rename to integrations/routeros-oci-build/Dockerfile.routeros.dockerignore diff --git a/scripts/routeros-oci-build/README.md b/integrations/routeros-oci-build/README.md similarity index 82% rename from scripts/routeros-oci-build/README.md rename to integrations/routeros-oci-build/README.md index 25a9392..2026600 100644 --- a/scripts/routeros-oci-build/README.md +++ b/integrations/routeros-oci-build/README.md @@ -27,19 +27,20 @@ Copy this directory as: RustProxy/ Cargo.toml Cargo.lock - scripts/ + integrations/ routeros-oci-build/ build-routeros-oci.sh Dockerfile.routeros Dockerfile.routeros.dockerignore + .env.routeros.example .env ``` Create `.env`: ```bash -cp scripts/routeros-oci-build/.env.example \ - scripts/routeros-oci-build/.env +cp integrations/routeros-oci-build/.env.routeros.example \ + integrations/routeros-oci-build/.env ``` Then edit the Zot address and credentials. @@ -63,8 +64,8 @@ Credentials are sent to `docker login` using `--password-stdin`. ## Check configuration ```bash -ENV_FILE=scripts/routeros-oci-build/.env \ - bash scripts/routeros-oci-build/build-routeros-oci.sh config +ENV_FILE=integrations/routeros-oci-build/.env \ + bash integrations/routeros-oci-build/build-routeros-oci.sh config ``` The repository root is detected using Git. If that is unavailable, the script walks upward until it finds `Cargo.toml`. @@ -72,8 +73,8 @@ The repository root is detected using Git. If that is unavailable, the script wa ## Build and push multi-arch image ```bash -ENV_FILE=scripts/routeros-oci-build/.env \ - bash scripts/routeros-oci-build/build-routeros-oci.sh push +ENV_FILE=integrations/routeros-oci-build/.env \ + bash integrations/routeros-oci-build/build-routeros-oci.sh push ``` With RustProxy version `0.25.0` and the defaults this publishes: @@ -89,15 +90,15 @@ Each tag points to the same amd64 + arm64 OCI manifest. ## Local single-architecture build ```bash -ENV_FILE=scripts/routeros-oci-build/.env \ - bash scripts/routeros-oci-build/build-routeros-oci.sh load amd64 +ENV_FILE=integrations/routeros-oci-build/.env \ + bash integrations/routeros-oci-build/build-routeros-oci.sh load amd64 ``` or: ```bash -ENV_FILE=scripts/routeros-oci-build/.env \ - bash scripts/routeros-oci-build/build-routeros-oci.sh load arm64 +ENV_FILE=integrations/routeros-oci-build/.env \ + bash integrations/routeros-oci-build/build-routeros-oci.sh load arm64 ``` `load arm64` loads an ARM64 image into Docker; it does not attempt to start the container. diff --git a/scripts/routeros-oci-build/build-routeros-oci.sh b/integrations/routeros-oci-build/build-routeros-oci.sh similarity index 98% rename from scripts/routeros-oci-build/build-routeros-oci.sh rename to integrations/routeros-oci-build/build-routeros-oci.sh index bbdfb0f..e7c99b0 100755 --- a/scripts/routeros-oci-build/build-routeros-oci.sh +++ b/integrations/routeros-oci-build/build-routeros-oci.sh @@ -42,7 +42,7 @@ PROJECT_ROOT="$(resolve_project_root)" || { } # Supports the invocation: -# ENV_FILE=scripts/routeros-oci-build/.env bash scripts/routeros-oci-build/build-routeros-oci.sh push +# ENV_FILE=integrations/routeros-oci-build/.env bash integrations/routeros-oci-build/build-routeros-oci.sh push ENV_FILE="${ROUTEROS_OCI_ENV_FILE:-${ENV_FILE:-$SCRIPT_DIR/.env}}" if [[ "$ENV_FILE" != /* ]]; then ENV_FILE="$PROJECT_ROOT/$ENV_FILE" diff --git a/scripts/routeros-oci-build/docker-entrypoint.sh b/integrations/routeros-oci-build/docker-entrypoint.sh similarity index 100% rename from scripts/routeros-oci-build/docker-entrypoint.sh rename to integrations/routeros-oci-build/docker-entrypoint.sh diff --git a/langs/README.md b/langs/README.md index 4f56146..20d5fd0 100644 --- a/langs/README.md +++ b/langs/README.md @@ -34,7 +34,7 @@ triggers a Cargo rebuild. Duplicate locales, invalid metadata, non-string values and oversized packs cause a build error. Limits: 128 packs, 256 KiB per pack, 4096 keys per pack, 8 MiB total bundle. -Run `python3 scripts/langs_check.py` before a build. The checker also validates +Run `python3 tools/checks/langs_check.py` before a build. The checker also validates that every static UI translation key has an English fallback. Extra JSON packs survive the managed updater when the new release does not contain the same filename. Keep a separate backup too; shipped filenames are replaced and the previous bytes are retained in the source backup. diff --git a/langs/en_US.json b/langs/en_US.json index 0adb095..6e23bba 100644 --- a/langs/en_US.json +++ b/langs/en_US.json @@ -183,7 +183,7 @@ "saveCertificate": "Apply certificate", "reloadCertificates": "Reload certificate files", "certificateHelp": "Upload a PEM pair, reference files already in data/certs, or deploy an externally issued certificate with the installed RustProxy binary. Reload re-reads configured files without changing the configuration revision.", - "certificateHint": "External Certbot hook (standard service install): 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", + "certificateHint": "External Certbot hook (standard service install): /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", "emptyCertificates": "No explicit SNI bindings. The generated default certificate is used while the TLS listener is enabled.", "certificateSafety": "Certificates secure TLS connections, not routing. Assign a certificate to a vhost. Changes affect new handshakes.", "wsActive": "WebSocket tunnels", @@ -2148,6 +2148,10 @@ "acme.runtime_python_missing": "Python is not available on this system. Automatic ACME generation and renewal are unavailable; PEM import and existing certificate files still work.", "acme.runtime_pip_missing": "Python is available, but RustProxy could not create a local pip environment and no distribution Certbot is available. Install Python with venv/pip or Certbot packages. PEM import and existing files still work.", "acme.runtime_setup_failed": "RustProxy could not create the local Python/pip environment and no distribution Certbot is available. Check network/package access or install Certbot from the distribution. PEM import and existing files still work.", + "acme.runtime_venv_failed": "Python and pip are available, but RustProxy could not create its private environment in the data directory. Check venv/virtualenv support and data-directory permissions. No system Certbot is available.", + "acme.runtime_venv_pip_failed": "The private Python environment was created, but pip/ensurepip could not be started inside it. Check the Python venv/ensurepip installation. No system Certbot is available.", + "acme.runtime_certbot_install_failed": "The private Python environment works, but pip could not install the Certbot packages. Check PyPI/proxy/DNS/TLS access. No system Certbot is available.", + "acme.runtime_certbot_validation_failed": "ACME packages were installed, but Certbot or one of the required plugins failed validation. Remove the acme-python directory and restart RustProxy, or check compatibility with the local Python.", "acme.runtime_data_dir_missing": "The RustProxy data directory is unavailable, so a private Python environment cannot be created. PEM import and existing certificate files still work." } -} \ No newline at end of file +} diff --git a/langs/pl_pl.json b/langs/pl_pl.json index 3a110f3..45b5707 100644 --- a/langs/pl_pl.json +++ b/langs/pl_pl.json @@ -183,7 +183,7 @@ "saveCertificate": "Zastosuj certyfikat", "reloadCertificates": "Wczytaj pliki certyfikatów", "certificateHelp": "Wgraj parę PEM, wskaż pliki w data/certs albo wdrażaj certyfikat z zewnętrznego wystawcy zainstalowaną binarką RustProxy. Ponowne wczytanie odczytuje skonfigurowane pliki bez zmiany rewizji.", - "certificateHint": "Hook Certbota (standardowa instalacja usługi): 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", + "certificateHint": "Hook Certbota (standardowa instalacja usługi): /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", "emptyCertificates": "Brak jawnych powiązań SNI. Aktywny listener TLS używa wygenerowanego certyfikatu domyślnego.", "certificateSafety": "Certyfikaty samopodpisane służą do testów. Certyfikat nie tworzy trasy. Zmiana dotyczy nowych połączeń TLS.", "wsActive": "Tunele WebSocket", @@ -2148,6 +2148,10 @@ "acme.runtime_python_missing": "Python nie jest dostępny w tym systemie. Automatyczne generowanie i odnawianie ACME są niedostępne; import PEM i istniejące pliki certyfikatów nadal działają.", "acme.runtime_pip_missing": "Python jest dostępny, ale RustProxy nie może utworzyć lokalnego środowiska pip, a systemowy Certbot nie jest dostępny. Zainstaluj Python z venv/pip albo pakiety Certbota. Import PEM i istniejące pliki nadal działają.", "acme.runtime_setup_failed": "RustProxy nie może utworzyć lokalnego środowiska Python/pip, a systemowy Certbot nie jest dostępny. Sprawdź dostęp do sieci/pakietów albo zainstaluj Certbota z dystrybucji. Import PEM i istniejące pliki nadal działają.", + "acme.runtime_venv_failed": "Python i pip są dostępne, ale RustProxy nie może utworzyć prywatnego środowiska w katalogu danych. Sprawdź obsługę venv/virtualenv oraz uprawnienia do katalogu danych. Systemowy Certbot nie jest dostępny.", + "acme.runtime_venv_pip_failed": "Prywatne środowisko Python zostało utworzone, ale nie udało się uruchomić w nim pip/ensurepip. Sprawdź instalację Python venv/ensurepip. Systemowy Certbot nie jest dostępny.", + "acme.runtime_certbot_install_failed": "Prywatne środowisko Python działa, ale pip nie może zainstalować pakietów Certbota. Sprawdź dostęp do PyPI/proxy/DNS/TLS. Systemowy Certbot nie jest dostępny.", + "acme.runtime_certbot_validation_failed": "Pakiety ACME zostały zainstalowane, ale Certbot lub jedna z wymaganych wtyczek nie przechodzi walidacji. Usuń katalog acme-python i uruchom RustProxy ponownie albo sprawdź zgodność lokalnego Pythona.", "acme.runtime_data_dir_missing": "Katalog danych RustProxy jest niedostępny, więc nie można utworzyć prywatnego środowiska Python. Import PEM i istniejące pliki certyfikatów nadal działają." } -} \ No newline at end of file +} diff --git a/make_zip.py b/make_zip.py index 4dd125d..1b59fa6 100755 --- a/make_zip.py +++ b/make_zip.py @@ -12,7 +12,7 @@ import zipfile ROOT = Path(__file__).resolve().parent MANAGED = [ - 'src', 'tests', 'scripts', 'nagios', 'docker-agent', 'migrations', 'examples', 'docs', 'langs', 'deploy', 'grafana', + 'src', 'tests', 'scripts', 'tools', 'integrations', 'nagios', 'docker-agent', 'migrations', 'examples', 'docs', 'langs', 'deploy', 'grafana', '.github', '.cargo', 'Cargo.toml', 'Cargo.lock', 'build.rs', 'rust-toolchain.toml', 'UPDATE_CHECK.toml', 'dev.sh', 'upgrade.sh', 'install.sh', 'install-debian13.sh', 'update.sh', 'README.md', 'ROADMAP.md', 'CHANGELOG.md', 'AGENTS.md', '.env.example', '.gitignore', '.dockerignore', @@ -67,7 +67,7 @@ def release_datetime() -> tuple[int, int, int, int, int, int]: def deterministic_mode(rel: Path) -> int: executable = rel.as_posix() in EXECUTABLE_ROOT or ( - rel.parts and rel.parts[0] == 'scripts' and rel.suffix in {'.py', '.sh'} + rel.parts and rel.parts[0] in {'scripts', 'tools', 'integrations'} and rel.suffix in {'.py', '.sh'} ) or ( rel.parts and rel.parts[0] == 'nagios' and (rel.suffix in {'.py', '.sh'} or rel.name.startswith('check_')) ) or ( @@ -86,7 +86,7 @@ def add_file(archive: zipfile.ZipFile, rel: Path, data: bytes, timestamp: tuple[ def main() -> None: parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.27.2.zip') + parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.27.3.zip') output = parser.parse_args().output.resolve() paths = files() checksum = ROOT / 'SHA256SUMS' diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..5625f63 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,11 @@ +# Application scripts + +This directory contains scripts coupled to RustProxy application/build/deployment behavior. +External development and QA helpers live under `tools/`; external platform integrations live under `integrations/`. + +- `runtime/` - application-owned helpers embedded or invoked by RustProxy (`acme_provider.py`). +- `build/` - build-time asset preparation used by `build.rs`. +- `deploy/` - install/update and stopped source-tree upgrade implementation. +- `migration/` - application migration utilities, currently the Nginx Proxy Manager importer. + +Keep these paths stable because Rust source, `build.rs` and shell entry points reference them directly. diff --git a/scripts/swagger_assets.py b/scripts/build/swagger_assets.py similarity index 99% rename from scripts/swagger_assets.py rename to scripts/build/swagger_assets.py index 41c466a..d58bbf9 100755 --- a/scripts/swagger_assets.py +++ b/scripts/build/swagger_assets.py @@ -85,7 +85,7 @@ def download(destination: Path) -> None: """TLS verification uses Python's default trust store; never executes a shell.""" started = time.monotonic() opener = build_opener(HttpsRedirects()) - request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.27.2"}) + request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.27.3"}) with opener.open(request, timeout=30) as response, destination.open("wb") as output: length = response.headers.get("Content-Length") if length and int(length) > MAX_ARCHIVE: diff --git a/scripts/deploy.py b/scripts/deploy/deploy.py similarity index 100% rename from scripts/deploy.py rename to scripts/deploy/deploy.py diff --git a/scripts/deploy_core.py b/scripts/deploy/deploy_core.py similarity index 99% rename from scripts/deploy_core.py rename to scripts/deploy/deploy_core.py index 824a77b..77964cb 100755 --- a/scripts/deploy_core.py +++ b/scripts/deploy/deploy_core.py @@ -31,7 +31,7 @@ import uuid import urllib.request import urllib.parse -ROOT = Path(__file__).resolve().parents[1] +ROOT = Path(__file__).resolve().parents[2] UNIT_PATH = Path('/etc/systemd/system/rustproxy.service') SHELL_PATH_PROFILE = Path('/etc/profile.d/rustproxy-path.sh') SERVICE = 'rustproxy.service' @@ -97,7 +97,7 @@ def digest(path: Path) -> str: def fetch_https(url: str, max_bytes: int) -> bytes: if urllib.parse.urlparse(url).scheme != 'https': raise ValueError('GeoIP downloads require HTTPS.') - request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.27.2'}) + request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.27.3'}) with urllib.request.urlopen(request, timeout=30) as response: if urllib.parse.urlparse(response.geturl()).scheme != 'https': raise ValueError('GeoIP download redirected away from HTTPS.') diff --git a/scripts/deploy_runtime.py b/scripts/deploy/deploy_runtime.py similarity index 99% rename from scripts/deploy_runtime.py rename to scripts/deploy/deploy_runtime.py index 8eb8d3e..512903a 100755 --- a/scripts/deploy_runtime.py +++ b/scripts/deploy/deploy_runtime.py @@ -43,12 +43,18 @@ def _run_user(account, data: Path, args: list[str], timeout: int) -> subprocess. def _acme_python(data: Path) -> Path: return data / 'acme-python' / 'bin/python' +def _acme_certbot(data: Path) -> Path: + return data / 'acme-python' / 'bin/certbot' + def _acme_runtime_valid(data: Path, account) -> bool: python = _acme_python(data) if not python.is_file(): return False + certbot = _acme_certbot(data) + if not certbot.is_file(): + return False checks = ([str(python), '-I', '-c', 'import certbot, certbot_dns_cloudflare, certbot_dns_ovh, certbot_dns_rfc2136'], - [str(python), '-I', '-m', 'certbot', '--version']) + [str(certbot), '--version']) try: return all(_run_user(account, data, command, 30).returncode == 0 for command in checks) except (OSError, subprocess.SubprocessError): diff --git a/scripts/upgrade.py b/scripts/deploy/upgrade.py similarity index 93% rename from scripts/upgrade.py rename to scripts/deploy/upgrade.py index aa82da6..ddba023 100755 --- a/scripts/upgrade.py +++ b/scripts/deploy/upgrade.py @@ -9,7 +9,7 @@ import shutil import sys import uuid -MANAGED = ["build.rs", "langs", "deploy", "debian", "grafana", "make_zip.py", "src", "tests", "scripts", "nagios", "docker-agent", "migrations", "examples", "docs", ".github", ".cargo", +MANAGED = ["build.rs", "langs", "deploy", "debian", "grafana", "make_zip.py", "src", "tests", "scripts", "tools", "integrations", "nagios", "docker-agent", "migrations", "examples", "docs", ".github", ".cargo", "Cargo.toml", "Cargo.lock", "rust-toolchain.toml", "dev.sh", "upgrade.sh", "install.sh", "update.sh", "README.md", "ROADMAP.md", "CHANGELOG.md", "AGENTS.md", "SHA256SUMS", ".env.example", ".gitignore"] @@ -46,7 +46,7 @@ def upgrade(source: Path, target: Path, *, stopped: bool, data_dir: Path | None for name in MANAGED: if (target / name).is_symlink(): raise ValueError(f"Managed target {name} is a symlink; refusing to replace it.") - backup = target / ".upgrade-backup" / ("before-0.27.2-" + uuid.uuid4().hex[:12]) + backup = target / ".upgrade-backup" / ("before-0.27.3-" + uuid.uuid4().hex[:12]) backup.mkdir(parents=True, mode=0o700) installed: list[str] = [] moved: list[str] = [] @@ -84,7 +84,7 @@ def upgrade(source: Path, target: Path, *, stopped: bool, data_dir: Path | None remove_legacy = legacy.exists() if remove_legacy and (legacy.is_symlink() or not legacy.is_file()): raise ValueError("Obsolete renewal observation sidecar must be a regular file.") - (backup / "upgrade.json").write_text(json.dumps({"from_directory":str(target), "to_version":"0.27.2", "managed_paths":MANAGED, "removed_obsolete_runtime_state": remove_legacy}, indent=2)+"\n", encoding="utf-8") + (backup / "upgrade.json").write_text(json.dumps({"from_directory":str(target), "to_version":"0.27.3", "managed_paths":MANAGED, "removed_obsolete_runtime_state": remove_legacy}, indent=2)+"\n", encoding="utf-8") if remove_legacy: legacy.unlink() except BaseException: @@ -112,10 +112,10 @@ def main() -> None: os.umask(0o077) data_dir = args.data_dir or (Path(os.environ["RUST_PROXY_DATA_DIR"]) if os.environ.get("RUST_PROXY_DATA_DIR") else None) try: - backup = upgrade(Path(__file__).resolve().parents[1], args.target, stopped=args.stopped, data_dir=data_dir) + backup = upgrade(Path(__file__).resolve().parents[2], args.target, stopped=args.stopped, data_dir=data_dir) except (OSError, ValueError) as error: parser.exit(2, f"Upgrade refused or rolled back: {error}\n") - print(f"Installed RustProxy 0.27.2. Previous managed source files: {backup}") + print(f"Installed RustProxy 0.27.3. Previous managed source files: {backup}") print("Preserved data, .env, .git, target and other unmanaged files. No database migration was run.") print(f"Next: cd {args.target}; ./dev.sh test; ./dev.sh") diff --git a/scripts/migrate_npm.py b/scripts/migration/migrate_npm.py similarity index 100% rename from scripts/migrate_npm.py rename to scripts/migration/migrate_npm.py diff --git a/scripts/migrate_npm_api.py b/scripts/migration/migrate_npm_api.py similarity index 100% rename from scripts/migrate_npm_api.py rename to scripts/migration/migrate_npm_api.py diff --git a/scripts/migrate_npm_core.py b/scripts/migration/migrate_npm_core.py similarity index 100% rename from scripts/migrate_npm_core.py rename to scripts/migration/migrate_npm_core.py diff --git a/scripts/routeros-oci-build/.env b/scripts/routeros-oci-build/.env deleted file mode 100644 index 8c40ca4..0000000 --- a/scripts/routeros-oci-build/.env +++ /dev/null @@ -1,39 +0,0 @@ -# Zot / OCI registry. Do not include a path in REGISTRY. -REGISTRY=zot.linuxiarz.pl -IMAGE_NAME=rustproxy - -# Alternative shorthand instead of REGISTRY + IMAGE_NAME: -# OCI_IMAGE=zot.example.net/network/rustproxy - -# Zot Basic Auth / htpasswd or token credentials. -OCI_USERNAME=admin -OCI_PASSWORD= -# OCI_TOKEN= - -# RouterOS targets used by this helper. -BUILD_PLATFORMS=linux/amd64,linux/arm64 -#BUILD_PLATFORMS=linux/amd64 -BUILDER=rustproxy - -# RustProxy currently requires Rust 1.88. -RUST_VERSION=1.88 -DEBIAN_SUITE=trixie - -# Empty OCI_TAG means the script uses Cargo.toml package version. -OCI_TAG= -OCI_LATEST=1 -OCI_EXTRA_TAGS= - -# Build behavior. -OCI_PULL=1 -OCI_NO_CACHE=0 -OCI_VERIFY=1 -OCI_PROGRESS=plain - -# 1 stores BuildKit cache in Zot as IMAGE_NAME:buildcache. -# Recommended for CI/ephemeral buildx builders. -OCI_REGISTRY_CACHE=0 - -# Optional BuildKit config, useful for an internal Zot with a custom CA. -# BUILDER_CONFIG=/etc/buildkit/buildkitd.toml - diff --git a/scripts/acme_provider.py b/scripts/runtime/acme_provider.py similarity index 99% rename from scripts/acme_provider.py rename to scripts/runtime/acme_provider.py index cc34f90..e34cf13 100755 --- a/scripts/acme_provider.py +++ b/scripts/runtime/acme_provider.py @@ -168,10 +168,9 @@ def credentials(value: dict) -> bytes: def certbot_prefix(executable: str | None = None) -> list[str]: if executable: return [executable] - managed_python = os.environ.get("RUSTPROXY_CERTBOT_PYTHON") - if managed_python: - managed_certbot = str(Path(managed_python).with_name("certbot")) - return [managed_python, "-I", managed_certbot] + managed_certbot = os.environ.get("RUSTPROXY_CERTBOT_EXECUTABLE") + if managed_certbot: + return [managed_certbot] discovered = shutil.which("certbot") if discovered: return [discovered] diff --git a/scripts/acme_rfc2136.py b/scripts/runtime/acme_rfc2136.py similarity index 100% rename from scripts/acme_rfc2136.py rename to scripts/runtime/acme_rfc2136.py diff --git a/src/acme/runtime_bootstrap.rs b/src/acme/runtime_bootstrap.rs new file mode 100644 index 0000000..aee53a2 --- /dev/null +++ b/src/acme/runtime_bootstrap.rs @@ -0,0 +1,128 @@ +async fn create_virtual_environment( + python: &Path, + staging: &Path, + system_pip: bool, + shutdown: &tokio_util::sync::CancellationToken, +) -> bool { + let mut create = Command::new(python); + create.arg("-m").arg("venv").arg(staging); + if command_success(create, Duration::from_secs(180), shutdown).await { + return true; + } + if !system_pip { + return false; + } + let _ = tokio::fs::remove_dir_all(staging).await; + // Fall back to an application-local virtualenv bootstrap. + let bootstrap = staging.with_extension("virtualenv-bootstrap"); + let _ = tokio::fs::remove_dir_all(&bootstrap).await; + let install_bootstrap = |break_system_packages: bool| { + let mut install = Command::new(python); + install.args([ + "-m", + "pip", + "install", + "--disable-pip-version-check", + "--no-input", + "--no-cache-dir", + "--retries", + "3", + "--timeout", + "30", + ]); + // Some distro pip builds enforce PEP 668 even for --target. Retrying with + // --break-system-packages is safe here because the destination is the + // private bootstrap directory; distribution site-packages are untouched. + if break_system_packages { + install.arg("--break-system-packages"); + } + install.arg("--target").arg(&bootstrap).arg("virtualenv>=20,<21"); + install + }; + let installed = command_success( + install_bootstrap(false), + Duration::from_secs(300), + shutdown, + ) + .await; + if !installed { + let _ = tokio::fs::remove_dir_all(&bootstrap).await; + if !command_success( + install_bootstrap(true), + Duration::from_secs(300), + shutdown, + ) + .await + { + let _ = tokio::fs::remove_dir_all(&bootstrap).await; + return false; + } + } + let mut create = Command::new(python); + create + .arg("-c") + .arg("import runpy,sys; sys.path.insert(0,sys.argv[1]); sys.argv=['virtualenv',sys.argv[2]]; runpy.run_module('virtualenv',run_name='__main__')") + .arg(&bootstrap) + .arg(staging); + let created = command_success(create, Duration::from_secs(180), shutdown).await; + let _ = tokio::fs::remove_dir_all(&bootstrap).await; + created +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum BuildFailure { + CreateEnvironment, + PreparePip, + InstallPackages, + Validate, +} + +async fn build_managed_environment( + python: &Path, + target: &Path, + system_pip: bool, + shutdown: &tokio_util::sync::CancellationToken, +) -> Result<(), BuildFailure> { + let legacy_stage = target.with_extension("installing"); + let _ = tokio::fs::remove_dir_all(&legacy_stage).await; + let _ = tokio::fs::remove_dir_all(target).await; + let _ = tokio::fs::remove_file(target).await; + if !create_virtual_environment(python, target, system_pip, shutdown).await { + let _ = tokio::fs::remove_dir_all(target).await; + return Err(BuildFailure::CreateEnvironment); + } + let target_python = venv_python(target); + let mut pip_check = Command::new(&target_python); + pip_check.args(["-m", "pip", "--version"]); + if !command_success(pip_check, Duration::from_secs(30), shutdown).await { + let mut ensurepip = Command::new(&target_python); + ensurepip.args(["-m", "ensurepip", "--upgrade"]); + if !command_success(ensurepip, Duration::from_secs(120), shutdown).await { + let _ = tokio::fs::remove_dir_all(target).await; + return Err(BuildFailure::PreparePip); + } + } + let mut pip = Command::new(&target_python); + pip.args([ + "-m", + "pip", + "install", + "--disable-pip-version-check", + "--no-input", + "--no-cache-dir", + "--retries", + "3", + "--timeout", + "30", + ]); + pip.args(CERTBOT_PACKAGES.iter().copied()); + if !command_success(pip, Duration::from_secs(600), shutdown).await { + let _ = tokio::fs::remove_dir_all(target).await; + return Err(BuildFailure::InstallPackages); + } + if !managed_environment_valid(target, shutdown).await { + let _ = tokio::fs::remove_dir_all(target).await; + return Err(BuildFailure::Validate); + } + Ok(()) +} diff --git a/src/acme/runtime_env.rs b/src/acme/runtime_env.rs index 64060d5..696952b 100644 --- a/src/acme/runtime_env.rs +++ b/src/acme/runtime_env.rs @@ -60,7 +60,7 @@ impl Manager { struct RuntimeLaunch { python: PathBuf, prepend_path: Option, - managed_certbot_python: Option, + managed_certbot_executable: Option, } fn find_on_path(name: &str) -> Option { @@ -107,6 +107,15 @@ fn venv_python(directory: &Path) -> PathBuf { venv_bin(directory).join("python") } +#[cfg(windows)] +fn venv_certbot(directory: &Path) -> PathBuf { + venv_bin(directory).join("certbot.exe") +} +#[cfg(not(windows))] +fn venv_certbot(directory: &Path) -> PathBuf { + venv_bin(directory).join("certbot") +} + async fn command_success( mut command: Command, timeout: Duration, @@ -145,9 +154,12 @@ async fn managed_environment_valid( if !command_success(imports, Duration::from_secs(20), shutdown).await { return false; } - let certbot = venv_bin(directory).join("certbot"); - let mut version = Command::new(&python); - version.arg("-I").arg(certbot).arg("--version"); + let certbot = venv_certbot(directory); + if !certbot.is_file() { + return false; + } + let mut version = Command::new(&certbot); + version.arg("--version"); command_success(version, Duration::from_secs(20), shutdown).await } @@ -172,107 +184,7 @@ async fn system_has_pip(python: &Path, shutdown: &tokio_util::sync::Cancellation command_success(command, Duration::from_secs(20), shutdown).await } -async fn create_virtual_environment( - python: &Path, - staging: &Path, - system_pip: bool, - shutdown: &tokio_util::sync::CancellationToken, -) -> bool { - let mut create = Command::new(python); - create.arg("-m").arg("venv").arg(staging); - if command_success(create, Duration::from_secs(180), shutdown).await { - return true; - } - if !system_pip { - return false; - } - let _ = tokio::fs::remove_dir_all(staging).await; - // Fall back to an application-local virtualenv bootstrap. - let bootstrap = staging.with_extension("virtualenv-bootstrap"); - let _ = tokio::fs::remove_dir_all(&bootstrap).await; - let mut install = Command::new(python); - install.args([ - "-m", - "pip", - "install", - "--disable-pip-version-check", - "--no-input", - "--no-cache-dir", - "--retries", - "3", - "--timeout", - "30", - "--target", - ]); - install.arg(&bootstrap).arg("virtualenv>=20,<21"); - if !command_success(install, Duration::from_secs(300), shutdown).await { - let _ = tokio::fs::remove_dir_all(&bootstrap).await; - return false; - } - let mut create = Command::new(python); - create - .arg("-c") - .arg("import runpy,sys; sys.path.insert(0,sys.argv[1]); sys.argv=['virtualenv',sys.argv[2]]; runpy.run_module('virtualenv',run_name='__main__')") - .arg(&bootstrap) - .arg(staging); - let created = command_success(create, Duration::from_secs(180), shutdown).await; - let _ = tokio::fs::remove_dir_all(&bootstrap).await; - created -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum BuildFailure { - Bootstrap, - Install, -} - -async fn build_managed_environment( - python: &Path, - target: &Path, - system_pip: bool, - shutdown: &tokio_util::sync::CancellationToken, -) -> Result<(), BuildFailure> { - let legacy_stage = target.with_extension("installing"); - let _ = tokio::fs::remove_dir_all(&legacy_stage).await; - let _ = tokio::fs::remove_dir_all(target).await; - let _ = tokio::fs::remove_file(target).await; - if !create_virtual_environment(python, target, system_pip, shutdown).await { - let _ = tokio::fs::remove_dir_all(target).await; - return Err(BuildFailure::Bootstrap); - } - let target_python = venv_python(target); - let mut pip_check = Command::new(&target_python); - pip_check.args(["-m", "pip", "--version"]); - if !command_success(pip_check, Duration::from_secs(30), shutdown).await { - let mut ensurepip = Command::new(&target_python); - ensurepip.args(["-m", "ensurepip", "--upgrade"]); - if !command_success(ensurepip, Duration::from_secs(120), shutdown).await { - let _ = tokio::fs::remove_dir_all(target).await; - return Err(BuildFailure::Bootstrap); - } - } - let mut pip = Command::new(&target_python); - pip.args([ - "-m", - "pip", - "install", - "--disable-pip-version-check", - "--no-input", - "--no-cache-dir", - "--retries", - "3", - "--timeout", - "30", - ]); - pip.args(CERTBOT_PACKAGES.iter().copied()); - if !command_success(pip, Duration::from_secs(600), shutdown).await - || !managed_environment_valid(target, shutdown).await - { - let _ = tokio::fs::remove_dir_all(target).await; - return Err(BuildFailure::Install); - } - Ok(()) -} +include!("runtime_bootstrap.rs"); async fn ensure_runtime(state: &App) -> Result { let _permit = state @@ -309,7 +221,7 @@ async fn ensure_runtime(state: &App) -> Result { return Ok(RuntimeLaunch { python: venv_python(&managed), prepend_path: Some(venv_bin(&managed)), - managed_certbot_python: Some(venv_python(&managed)), + managed_certbot_executable: Some(venv_certbot(&managed)), }); } let distro_certbot = find_on_path("certbot"); @@ -357,7 +269,7 @@ async fn ensure_runtime(state: &App) -> Result { return Ok(RuntimeLaunch { python: venv_python(&managed), prepend_path: Some(venv_bin(&managed)), - managed_certbot_python: Some(venv_python(&managed)), + managed_certbot_executable: Some(venv_certbot(&managed)), }); } if distro_certbot_available { @@ -374,13 +286,23 @@ async fn ensure_runtime(state: &App) -> Result { return Ok(RuntimeLaunch { python, prepend_path: None, - managed_certbot_python: None, + managed_certbot_executable: None, }); } - let message = if build == Err(BuildFailure::Bootstrap) && !system_pip { - "acme.runtime_pip_missing" - } else { - "acme.runtime_setup_failed" + tracing::warn!( + python = %python.display(), + managed = %managed.display(), + system_pip, + failure = ?build, + "managed ACME runtime setup failed; no system Certbot fallback available" + ); + let message = match build { + Err(BuildFailure::CreateEnvironment) if !system_pip => "acme.runtime_pip_missing", + Err(BuildFailure::CreateEnvironment) => "acme.runtime_venv_failed", + Err(BuildFailure::PreparePip) => "acme.runtime_venv_pip_failed", + Err(BuildFailure::InstallPackages) => "acme.runtime_certbot_install_failed", + Err(BuildFailure::Validate) => "acme.runtime_certbot_validation_failed", + Ok(()) => "acme.runtime_setup_failed", }; state.acme.set_runtime(RuntimeStatus { status: "unavailable", @@ -428,19 +350,20 @@ async fn worker(state: &App, directory: PathBuf, raw: Zeroizing) -> Resu command .arg("-I") .arg("-c") - .arg(include_str!("../../scripts/acme_provider.py")) + .arg(include_str!("../../scripts/runtime/acme_provider.py")) .arg("--cert-dir") .arg(&directory) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::null()) .kill_on_drop(true); + command.env_remove("RUSTPROXY_CERTBOT_EXECUTABLE"); command.env_remove("RUSTPROXY_CERTBOT_PYTHON"); if let Some(prepend) = launch.prepend_path.as_deref() { command.env("PATH", merged_path(prepend)?); } - if let Some(python) = launch.managed_certbot_python.as_deref() { - command.env("RUSTPROXY_CERTBOT_PYTHON", python); + if let Some(certbot) = launch.managed_certbot_executable.as_deref() { + command.env("RUSTPROXY_CERTBOT_EXECUTABLE", certbot); } #[cfg(unix)] { diff --git a/src/admin/documentation/documents.rs b/src/admin/documentation/documents.rs index c877bbd..aaa6fea 100644 --- a/src/admin/documentation/documents.rs +++ b/src/admin/documentation/documents.rs @@ -409,6 +409,7 @@ pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[ "/docs/V0.25.1_AUDIT.md" )), ), + reader_doc!("docs/V0.27.3_IMPLEMENTATION.md", "technical"), reader_doc!("docs/V0.27.2_IMPLEMENTATION.md", "technical"), reader_doc!("docs/V0.27.1_IMPLEMENTATION.md", "historical"), reader_doc!("docs/V0.27.0_IMPLEMENTATION.md", "technical"), diff --git a/src/admin/openapi.json b/src/admin/openapi.json index c5761f6..79dd04a 100644 --- a/src/admin/openapi.json +++ b/src/admin/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "RustProxy Administration API", - "version": "0.27.2", + "version": "0.27.3", "description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust." }, "servers": [ diff --git a/src/admin/web/index.html b/src/admin/web/index.html index dc3b349..047543c 100644 --- a/src/admin/web/index.html +++ b/src/admin/web/index.html @@ -2823,7 +2823,7 @@ -