diff --git a/AGENTS.md b/AGENTS.md
index 73edf51..f5453ec 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.0.
+compatibility. Current source version: 0.27.1.
## Delivery
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60382b7..5bfa6d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,15 @@
> test-only changes and packaging implementation notes were moved to
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
+## 0.27.1
+
+- Show ready-to-run first-administrator setup-token commands instead of requiring a `$DATA_DIR` placeholder substitution.
+- Move Certbot automation to a RustProxy-managed Python environment under the application data directory, initialized automatically with `venv`/`pip` and an app-local `virtualenv` bootstrap when needed.
+- Retain distribution Certbot as a fallback when available and expose managed/system/unavailable runtime state in the Certificates panel, including explicit missing-Python/pip diagnostics for minimal systems such as OPNsense.
+- Add account-scoped column and direction sorting to vhosts, Docker vhosts, redirects, 404 hosts and streams with feature-specific columns; Redirects and 404 hosts use the same search/sort/action toolbar layout as Vhosts.
+- Replace the development-only Certbot hook example with the installed production binary plus explicit data directory, and expose the official source/package repositories plus Python/pip/Certbot runtime state in the startup banner and About/runtime views.
+- Keep SQLite schema 8; administrator sort preferences are additive/defaulted and require no migration.
+
## 0.27.0
- Enable downstream response compression by default for zstd, Brotli and gzip; keep deflate opt-in. Client q-values remain authoritative and equal-quality encodings use `zstd -> br -> gzip -> deflate` priority. Existing `Content-Encoding` is passed through unchanged.
diff --git a/Cargo.lock b/Cargo.lock
index c25e959..3d3f5fc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1688,7 +1688,7 @@ dependencies = [
[[package]]
name = "rust-proxy"
-version = "0.27.0"
+version = "0.27.1"
dependencies = [
"anyhow",
"arc-swap",
diff --git a/Cargo.toml b/Cargo.toml
index 07ecef1..a600bbe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rust-proxy"
-version = "0.27.0"
+version = "0.27.1"
edition = "2021"
rust-version = "1.88"
description = "RustProxy - independent reverse proxy with local administration"
diff --git a/Dockerfile b/Dockerfile
index 78de1a4..96c2116 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,13 +7,8 @@ RUN if [ ! -f Cargo.lock ]; then cargo generate-lockfile; fi \
FROM debian:trixie-slim AS runtime
RUN apt-get update \
- && apt-get install -y --no-install-recommends ca-certificates python3 python3-venv openssl \
- && python3 -m venv /opt/rustproxy-certbot \
- && /opt/rustproxy-certbot/bin/pip install --no-cache-dir \
- 'certbot==5.8.0' \
- 'certbot-dns-cloudflare==5.8.0' \
- 'certbot-dns-ovh==5.8.0' \
- 'certbot-dns-rfc2136==5.8.0' \
+ && apt-get install -y --no-install-recommends ca-certificates python3 python3-venv python3-pip openssl certbot \
+ python3-certbot-dns-cloudflare python3-certbot-dns-ovh python3-certbot-dns-rfc2136 \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --system --gid 10001 rustproxy \
&& useradd --system --uid 10001 --gid rustproxy --home /var/lib/rustproxy --shell /usr/sbin/nologin rustproxy \
@@ -24,7 +19,7 @@ USER rustproxy:rustproxy
ENV RUST_PROXY_DATA_DIR=/var/lib/rustproxy \
RUST_PROXY_PROXY_LISTEN=0.0.0.0:8080 \
RUST_PROXY_ADMIN_LISTEN=127.0.0.1:9090 \
- PATH=/opt/rustproxy-certbot/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
VOLUME ["/var/lib/rustproxy"]
EXPOSE 8080 8443 9090
ENTRYPOINT ["/usr/local/bin/rust-proxy"]
diff --git a/README.md b/README.md
index 9df95cc..ca92630 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-# RustProxy 0.27.0
+# RustProxy 0.27.1
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.0 release source.**
+**0.27.1 release source.**
## Layer-4 streams
@@ -11,18 +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.0
+## What changed in 0.27.1
-- Enable downstream compression by default with `zstd`, Brotli and gzip codecs; deflate remains opt-in. Honor client `q` values first and use deterministic `zstd -> br -> gzip -> deflate` server preference when quality is equal. Existing `Content-Encoding` is never recompressed.
-- Stop treating authentication/cookie/cache-privacy headers as blanket compression blockers. `Cookie`, `Authorization`, `Set-Cookie`, `private` and `no-store` may be compressed; `no-transform`, ranges, SSE, upgrades and already encoded responses remain excluded.
-- Add dynamic compression inheritance/overrides to ordinary vhosts and Web Server services, including per-scope codec filters. Docker vhosts intentionally expose only one Compression checkbox; their codec set and priority come from global Response settings. Global Response settings remain the hard codec ceiling and own the minimum size.
-- Keep an explicitly published Docker vhost routed until the administrator unpublishes it. A missing container/service now keeps the hostname and returns `503 Service Unavailable`; an agent transport failure reuses the last successful inventory when available, and service return is reconciled automatically.
-- Make `/cache` occupancy meters render their filled portion consistently in Chromium/WebKit and Firefox instead of relying only on native `accent-color`.
-- Keep SQLite schema 8. New saved compression fields are additive/defaulted, so older 0.26.x documents continue to load. Fresh installations default the `Server` response header to exactly `RustProxy`.
+- Show a complete first-administrator setup-token command for the default `./data` path during first launch, plus a standard service-installation example using `/var/lib/rustproxy`.
+- Manage Certbot in a RustProxy-owned Python environment under `/acme-python`. Missing environments are initialized automatically with `venv`/`pip`; when `venv` is split out by a distribution, RustProxy can bootstrap `virtualenv` into its own data directory.
+- Fall back to a runnable distribution `certbot` when the managed environment cannot be prepared. The Certificates panel reports preparing/managed/system/unavailable state and explicitly reports missing Python/pip while keeping PEM import and existing-certificate workflows available.
+- Add account-scoped column + direction sorting for ordinary vhosts, Docker vhosts, redirects, 404 hosts and streams, with tab-specific sort columns matching each feature. Redirects and 404 hosts use the same search/sort/action toolbar presentation as Vhosts.
+- Use the installed production RustProxy binary (not `dev.sh`) in the Certbot deploy-hook example. The startup banner and About/runtime views now show the official source repository (`https://git.linuxiarz.pl/gru/RustProxy`), package repository (`https://rustproxy.linuxiarz.pl/`) and Python/pip/Certbot certificate-runtime state.
+- Keep SQLite schema 8. The new list-sort preferences are additive/defaulted and require no database migration.
-0.27.0 retains the 0.26.7 cache/backend/container fixes below.
+0.27.1 retains the 0.27.0 proxy/compression and Docker-publication behavior. Historical details are in [CHANGELOG.md](CHANGELOG.md).
-### Retained 0.26.7 release notes
+### Historical 0.26.7 release notes
- Show explicit HOT and COLD cache occupancy progress on `/cache`, including object-count and byte/disk limits from Cache settings plus percentage usage.
- Preserve compatible upstream client pools across unrelated active-snapshot rebuilds so generation age, request count and `Rotations` diagnostics do not restart at zero unless the backend connection policy actually changes.
@@ -88,7 +88,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.0 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.1 verification status is maintained in [docs/TEST_REPORT.md](docs/TEST_REPORT.md).
## Documentation
@@ -168,7 +168,7 @@ CARGO_BUILD_JOBS=1 CARGO_PROFILE_RELEASE_LTO=false \
Ready Debian packages are published separately at `https://rustproxy.linuxiarz.pl`; Debian package-build metadata is intentionally not shipped in this source tree.
-The 0.27.0 source archive preserves the resolved `Cargo.lock` supplied with the source tree and
+The 0.27.1 source archive preserves the resolved `Cargo.lock` supplied with the source tree and
updates only this package's own version entry. This packaging environment has no Cargo, so the
dependency graph was not re-resolved here. On the compiler-enabled deployment host, run the
locked check/test/release gates below and retain the verified lockfile. `dev.sh` can generate a
@@ -414,7 +414,7 @@ python3 scripts/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.0.zip
+python3 make_zip.py /tmp/RustProxy-0.27.1.zip
```
## New management and test-origin guide
diff --git a/ROADMAP.md b/ROADMAP.md
index 95e283d..6fdbfb2 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,5 +1,17 @@
# RustProxy roadmap
+## 0.27.1 - Portable certificate runtime and list sorting
+
+Current direction: 0.27.0 -> 0.27.1
+
+- [x] Show complete default and standard-service setup-token commands during first-administrator onboarding.
+- [x] Initialize the Certbot Python environment under the RustProxy data directory with app-owned `venv`/`pip` state and a local `virtualenv` bootstrap when stdlib `venv` is unavailable.
+- [x] Fall back to distribution Certbot binaries when usable and report missing Python/pip or setup failures in the Certificates panel.
+- [x] Add account-scoped column + direction sorting for vhosts, Docker vhosts, redirects, 404 hosts and streams; keep Redirects/404 toolbar placement identical to Vhosts.
+- [x] Use the installed production binary in the Certbot hook example and expose official source/package repository links plus Python/pip/Certbot runtime state in application information.
+- [x] Keep SQLite schema 8 and additive/defaulted administrator-preference compatibility.
+- [ ] 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
Current direction: 0.26.7 -> 0.27.0
diff --git a/SHA256SUMS b/SHA256SUMS
index c64ab63..1bb8af8 100644
--- a/SHA256SUMS
+++ b/SHA256SUMS
@@ -1,18 +1,18 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
-e2fd35a4ed8c601bab52efb6befbe6e0fc9718cc6616644f11f90d9dcee69db3 AGENTS.md
-6cf887df4445e9655f54950586880eacaa5767d7076387b9fb20e975bdeac7c7 CHANGELOG.md
-2803cc34dda0304778ba0de378ec9653c1da9ba4b14200395de52022de55839e Cargo.lock
-aacc4711428f707ddc18bdbbda6257d8d7b5a1f77fce5ef2a2cc0d3042fc40d5 Cargo.toml
-239713948df0d54504989f1ab30f5bf0128b90b4e5ac45d50a132aa033323baf Dockerfile
-825e748c6c4e81820038819bbf66c117180fe8016a75bd33fb922d9621c50929 README.md
-8942bd9bbeb8647b7d4c2783e116be4944537932f0eb039ee423634e30234c9c ROADMAP.md
+6479dfef0344de33714f8503944876a3641c8381e9a7ccaaa4faed06b65e67c6 AGENTS.md
+59ff94da1b39a85b9738c8265ed3b871f483451871182d73c8a678509ee222c1 CHANGELOG.md
+0b0ed48165c10747c1a2f524fbdb27488f3edc7ff0e6fdccc845625d885ef61c Cargo.lock
+f6d19f4e9b1453cc3b80d47f6cb27870b205f9aa4592f82a872c53da44e1308d Cargo.toml
+75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile
+37da924319eef96622afb996a528989780a3733cc44eda7944e4f914a81eb70e README.md
+fbeb203b4ca4f02c9d35c02561cd9c098bdd2bf58688ea7039e987e24ee8e8e1 ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
928ba6a504bc431ae100c6e587861e5088b28822d2cb19da5d437b39d26c864a build.rs
385ac7970fa94f043d0bcf04a91184e6e2c807587b85e55517ed064bb4bddba6 deploy/README.md
-7b7fdf383bbaea4f37b5c120e98a24e9a075fba56ae7838a31a5152c7edcde02 deploy/rustproxy.service
-b17933ea587cd696a21f956359ba720880fb7aad6980e4ac52cc7158ef105233 dev.sh
+31d4e0cd454060b46f359fdb84a14b16a5fe0d3baabb387dd584d76e555558bb deploy/rustproxy.service
+18121e6fb922fb0e8d8b582486cb029256b305bd0f14bb989f9a16484b619674 dev.sh
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
@@ -21,25 +21,25 @@ bf687a851374d8f1fa59d050f3f76532f7c12f16a6daa7d3c36102c2d9a8ec9c docker-agent/d
9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh
b68234472bec5cedd67c7cfe1de7d8806a965dfd4101f944aa6320132f18c14e docker-agent/main.go
3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go
-e9a3a2dc8c99df585358a2fcb2716e9fda0d234753209d805a89101a445560d4 docker-agent/model.go
+dcd840bb3fd542379d3554ddc78e597c64bff22a4bd832b466f0db867840c17e docker-agent/model.go
e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go
634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service
5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go
-e2d3f7a9a3d5d47bada8bb50a9e7b3e1c237463197665c97aea83fb288a46a0d docs/ACME_PROVIDERS.md
+875faa3969df9409d79f050a84b4183496f557edda3d34aaec19fe556cf8c471 docs/ACME_PROVIDERS.md
ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md
-d699f2261a0d7854e49e9b8bae44fa37008a7d2f6340a73cdc37e840722ea211 docs/API.md
-9faba7a4f082cb253175e3fd4b66d174643978b29084a1d03f13ed20895c5ecc docs/API_ACCESS.md
+c7b75ed137094cade2b2047c1c8d304e936e0fe1121ff91a858f77c2e969fa39 docs/API.md
+386d1d9b120e05b8b776af87237294d1674ac6f37c6ca4fbd74368014d09c0d7 docs/API_ACCESS.md
0f7f159ad5b4b5277f80c19f3c47f4e7823ea439ef02e5913e1391f1b34d129f docs/ARCHITECTURE.md
86469d17f9786c3e6f20c66791b47e10e11695d2f1a88c66c9ded157a585c85f docs/CACHE.md
6266163e17d2921468abdae92c1620266af8351321530e2c9b7dca6f3dd89c88 docs/CACHE_DIAGNOSTICS.md
c6003dcbc0b95e2cfa9523b7017186cc8b4826248b588af77111ac5eb15174f9 docs/CHANGELOG_TECHNICAL_ARCHIVE.md
-f2e03c3fc70607101bd69cf85f7d6ca31075b2c0f3e36a8119ff9218951517bb docs/CONTAINER.md
+0031861b52c00fc1cffc73df1410c9dee5838d445699326deddc2488ad1193ad docs/CONTAINER.md
9538a52d508d8d7053c1387c42eef60e6d5dc1879c300b6fe951b3374c16884c docs/CUSTOM_LOCATIONS.md
16d8f59908c66e595d6003495cbf5c068c68b10aa9495eb8612c38b2de0e4a7c docs/DOCKER_DISCOVERY.md
9ea8db4d034ecb573976dd323da29f044538525c230df9f43a47be1ac867b24c docs/ERROR_LOGGING.md
-bd429bc96634de84178b6b59b225fe22fe5c402acdd85d84a15685fea2dce3db docs/FILE_LOGS.md
+adb4c6c43023f09d291eee1027944453eb7667b4c702339346a75aa23c986b5a docs/FILE_LOGS.md
df6ecd4a50796640243676ad4b3a87789f6e5d20cbebea6255e92fbfff1c7131 docs/HTTP3.md
-5f4f4b66b1d35ebdf5437216e50937249bebb1fc6c58ade88630e527b5cb409f docs/INSTALL.md
+69b7ede915818a52bb68f94983f0fcefe77ff6c58d776c92758e2f7007678eab docs/INSTALL.md
3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md
4bb56add544f15d54695cf797cd16a51c09024360cdeef803a88988f4a54b3a5 docs/LIVE_LOGS.md
7c35c4cdbe7bbb81d94f3bf6aeb22b8a866af3e7f0d62bbedc299e4d6f264e0d docs/MANAGEMENT.md
@@ -50,17 +50,17 @@ e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND
0c2241828e01b12f749562e2ac206a4519908fff63f46eba207b1cffa932dc5b docs/NPM_MIGRATION.md
712cb1cbf84515384b081fd2929e86e40af7c10f65bc69919b78b7b3e2fd12b5 docs/PHP_FPM.md
572c70fbd2f61fecd80a8ff9fa5de4a94c4f158de5e90da8e920f99b051241fa docs/PROMETHEUS.md
-1c31f47dc3529a962d83975dd44f978c32ac5982161a2f98f126a9cb90e05d6c docs/README.md
+72a1cfc792f4cc35d3463c312a88bf0053689df0bb52d89889bb7ed15e35462b docs/README.md
6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md
0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md
-ce5afb4f531bb5fb4c453deb49f643f2a563f32f7908c995e5bc8e0d7de995b7 docs/RENEWAL.md
+be661dbf81af840d4d8aeeb5347c24c7faecdecc73a8cf0f6f09053f1abe04c1 docs/RENEWAL.md
585ef0ac4492e03adca46949823e5a7538f56d9768090cd4dc080bfce4305b52 docs/ROADMAP_HISTORY.md
4bf7b3ea0bfe6c6b0ae0b7884b48b10ba29ff3a71611ec96957a3327d3c6ec88 docs/SECRET_STORE_BACKUP.md
5dbdf45fddba198a28f511dbbd2f12bf7d2b88c41c3cbedbc20dfea3858779d3 docs/SECURITY.md
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
632976125cbd9e96e91d1771331c73d1ac5adaa796476a20f8546971787c2529 docs/SETTINGS.md
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
-db4c14a0c8f8734339f8d7c1ecc8dcb451e2c0e590172e40ec65fe8bc537df84 docs/TEST_REPORT.md
+6f1d1ced81d32c2efae10b0743e6fbd45648734439ce1adbc4a62baa07341dd8 docs/TEST_REPORT.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
8ee6187a5460ca45643b7fd789e27bffd264449e3a92ff70e9359edee011bbbb docs/TRANSFORM.md
a1e9c6d38d21067d1ce7115ca3395ae8ff1f6a4c40fb0ebf00a6940ef79df439 docs/TRANSPORT.md
@@ -96,6 +96,7 @@ da5ec87b9482f2fc506c4334d9365f3bfaec89254a14e5585dd6460326b20e10 docs/V0.26.4_I
dd9b5dcdbd53a04fd32458caaf03b56aa335f0153b1a0e332fdf230fec12680e 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
bc2ac0117528ed4616c68cf7fe05cb1d9bbd36ecaa8573828cb3b3136923f265 docs/VHOSTS.md
09b60fb7b40db4d0139fa2a9e69a2aa9f28d4e61b8643521cfc7e936d7df8774 docs/VHOST_ACCESS.md
4026a4adeee70f98a96cc7aa8d8be16cda42ffb2e5890d38f1ba638134ab0675 docs/WEB_SERVER.md
@@ -110,14 +111,14 @@ b26833cb4251f9390a44192ecc827ff7f052fd19016a5ccf7498c94503ab4ce3 examples/mock-
5a8f3a8a054abc89c6e78896b9fe2f903212199a24dc3d2d2bcbd0696a31bcde examples/transport.json
d242de9614fbb52933b4accd32f4715ca94107abe4a3b1d7e088362b07a6ba60 examples/vhosts-balanced.json
af2bdb1343a8141069e94ccd2c7f92574ad848437e2f9fdb5c7576a9a18ef4a5 examples/vhosts-private.json
-f30e15ebe2102337933dad6f8cd334261716787073499168b389df504700a2b1 grafana/README.md
-50e88b2944efcba3f872ee6e9a8ac84e222fa5d5bd20137dde16f9193350c78e grafana/rustproxy-overview.json
-30763fc61c38992a44bc24ad6932b88fd3b322724fce799db1f17f9a747eb59a install-debian13.sh
+9cab73d7de1f54bc9e9e8b889a6eeaf2c293ab454c93ce904e9f7f5c6b7c588c grafana/README.md
+2e23d77477bd3eea895c2afe8c5e836559f435419c066e995dec9910c5bbb942 grafana/rustproxy-overview.json
+fe1fc76bcb639cac0609a8969cd92725cfd77d87f1d8519756ef16825e04914a install-debian13.sh
858b39fa6886b71d20690bf369e76580922364b3cc7f0cf6ba9b040df9cd76e3 install.sh
29cada0936762f26495064d1524a668262ed729b21f20aebddc8b6e5cfea363b langs/README.md
-40566677252af80fe4a91c3ff8e359ba41231302c182350020d6c75298c6b847 langs/en_US.json
-e09969817592093ea69b0cd8ec5fe661f0e686143b69b97f7b29d5f5c79920fe langs/pl_pl.json
-2cd6a1edc556ef1712b07fc88e7ea9fdb8869ca2d73175bcd367906791cbafb1 make_zip.py
+55a50c4071a05e4c24fef7fe9da10c0d9a3d5aaf19bafdb39fbacfbaa0b953e8 langs/en_US.json
+41707ded727ff98fc3672f69f7cb16e7950eca8efa248fd328108ca1b262c22e langs/pl_pl.json
+95f980b8420d14addf02c4c0139986ba317239426a3c91a1a821df410147c2c4 make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
5c62da30fa9db594a4d656c77ae676d3c388072b6a85f91170ec7d911d7cde43 migrations/0004_admin_preferences.sql
@@ -136,11 +137,11 @@ edb6ee1445b01cd6f3a74a3a607f39fb107d411b49df028d2e0f4679e093de31 scripts/acme_p
e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/acme_rfc2136.py
a5b4451e45ce975685772aa8409b64a57a20a7548466584c3ea7fb975e64729c scripts/admin_audit.py
20e83f8b6347b819d1d1ea3de5c691c36f0816954a4210b0d3f39557fefe1931 scripts/backup.py
-c68afcc307b28e1ae12d4c841d9fe5759ecfe1e338ba7d42012cdc8366fe20cc scripts/check_source.py
+4ab1fc13e7447e752c476bc8bfd15ad4745900bf2a40fc5469174aacbc3aaef6 scripts/check_source.py
187ee92c8029cdd59e89b4447cf4d170e856017a2091ceb85ae7c22dd1a79c72 scripts/deploy.py
-5bb760451359cd1673d286fdffea9f0e9151af41e573371720ce4e47d86d8f48 scripts/deploy_core.py
+11321aa4c5744dd2a3322123c00182e9565891e3b2742cda5bf3fc437b3d0c60 scripts/deploy_core.py
f014472588e889488976970dbaa95a8745b6907516aa81a7cca21d0f81eaaad3 scripts/deploy_runtime.py
-d1fc7ba88777cca4e94ee6f3af14286f86084f3aeb0f8b746b02f8f59d29dcb8 scripts/docs_check.py
+f818039f8a2e35922596122cde671a5e1af264b03d48938a3ca8261ec3c8e431 scripts/docs_check.py
d391681dc3f6786552ca44929979286a5c76a2b86785635871b59d2eb4d73389 scripts/langs_check.py
2e35b7c78887fd56f83ca9017caa168630963a9f47da71dfc53b1a8f9ad436c2 scripts/memory_contracts.py
2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migrate_npm.py
@@ -150,8 +151,8 @@ d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a scripts/mock_a
2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 scripts/mock_server.py
2d4c859aae5ba357a6d30024c8487ae8f59f01b192711f13f9c2f8fee0f48b50 scripts/os-acme-client/README.md
c73b814a42bb4ff703441dbd177ee625055eeb115f9b5bb0c2150aede573a159 scripts/os-acme-client/opnsense-rustproxy-cert-deploy.sh
-42c5c863d01d157a8cf159218ded01ffcaac2c1f6d23be396a5708007fb39790 scripts/preflight.py
-99f14c33ac4ce569eda718cee6145825847585f2ed6e18ffb12337c30e25a635 scripts/release_smoke.py
+f7474501b9ab7102d9f9b51c6d62c83dac3e6111e982f12e13480bcfd0b1c909 scripts/preflight.py
+3019322686f3eccb141a9587b5bff78aabf46fb2ef766d3ddf1de274fea4dc8e 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
@@ -163,36 +164,37 @@ ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed scripts/rust_s
1c83054336b3508d7ad72cd259f039394682642439877fe69639c0b76be09154 scripts/security_contracts.py
295b6f2625b1875950ea3eecccd142f49be327afdd00c92641286e6de530ec51 scripts/security_dependency_contracts.py
6fefb5c459aacdef3dcabe9c5e6e7718a2145a0190b165c29d7a46216ac53bad scripts/source_module_contracts.py
-dddc1312ec792f57dfcd8bb7ba37c502b6f0fc8b5f56542d58b677c0fd1a9faf scripts/swagger_assets.py
+6c1b64db378fd6ab351882ecf91d7be3db7114b6650a88d402408b34794260cc scripts/swagger_assets.py
538b5f6cfa738355a729940ee3d92e7ab3dc25ca69ae330ddaa08f831775b8ba scripts/test_compression.py
-553f1e989ab5cd5e0e9c68bc1f65cb2e27ebf13dd042333924879429f4b568fb scripts/upgrade.py
+19bfa4dca422cebe8b41d9374f2e5ad480a34228995eddcf12acfc21e0ebed6f scripts/upgrade.py
0aaa80453e25aae7ea953fadb91b3ae3eaff627977c81c9ab5a2779f9a93582f scripts/web_assets.py
-b2df6504a3f6939dbaac42737c323e3e8a605dd4c6df68d7576bea90931602dd scripts/web_server_contracts.py
+71bc04c6d71ce14362c7668e6798e0f13f2fda052a081ae52d04d7845e208adb scripts/web_server_contracts.py
c245f8f832bd2c36a3fbd5e66fe98e7367c70a8db840c79e6b640f1d9d74f2ac scripts/web_service_ui_test.mjs
ad62f73bb42906d617cc1b479f02d4648c7324cb89fc11d2c6aa6bf13774eec9 src/acl.rs
ea272433e7f92e983509a8ab1aa54242440abef21df526c2f99944ef279e09b9 src/acl/tests.rs
-992126cb77249a6a173a71b2e5387f218b987545a04b26bc0340f922b4600ec1 src/acme.rs
+6f5d3137bf24451da05c1db9f0f9e2a451305db17b6bc2590815ad54a6511829 src/acme.rs
587097620b2f25ce7c6f59f907b47660184b095e3040c7615e74509a82e7cdd8 src/acme/jobs.rs
-dbce5e3d3899e8c3c5dcc353c7ad1d2bf76953abca2a6d9d5ae310d206bb1bb1 src/acme/model.rs
+3f158f46e80b403b658b4e9b1e3710e38c70e47b2d476b8223d9c394ffeeba95 src/acme/model.rs
c5334de6c732d76b2bea7bfc17b3ac305a250bbaad31a5420d6a7d7abb200449 src/acme/providers.rs
b9e57db4bd80ef37564dfc533a96897d8a494671d53acea9fb4bf28d23ba370d src/acme/providers/tests.rs
+a5dad04694780f0c6da3c277a110930f3c2ed925fc122d4be770678055931017 src/acme/runtime_env.rs
7acbaf34e7b953b753ab074893e47b5024f0cd756dc4c7a1074067dd936c2e8e src/acme/tests.rs
55ef7ceb44c25c7a8ddcbfd5e0d4b647526f9bdcde901ee08db5314e4d055b39 src/admin/access.rs
-85f00f8157e83a152cc531f4cfecd0b1472ce269be9c8eb1ac17ad0693748b42 src/admin/certificates.rs
+d18efa3f797f62b3916265e30ba546725a41e7ccfd9b123965ff9f522e3baa88 src/admin/certificates.rs
1de4c7c116eeab2452ccccbaba3f2b7b63c6eb1c1e146ab870d19c5a5ca16f80 src/admin/documentation.rs
-6403ff8ed09aa22544d2f61292cda03e3c9a1de3dc5f46342531ba5526b51af0 src/admin/documentation/documents.rs
+9cac4393318cd34afcfdd02527291aaeb8eb373d38f9b219f71cb963c6d8e883 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
-9f62c508b8f527b19bb2b9d7592b1d82b17e8d98f5b9eaadbf24bf56f3f12418 src/admin/openapi.json
+dd40e2e8f1cac463532b113f91b4474a97a118fcd96aa27ee9f26bd78821064b src/admin/openapi.json
a53309daf118381031122ca70764f7a2f92adc44af3fc91df5f946e6856f76ae src/admin/operations.rs
-e9ea015aabdfdbb2f8dd72ea734002b5d648dc1bbef4cc4a0b85d2bcc059e9a5 src/admin/preferences.rs
+a01160fd4f6743a9e8c1aa2e03ddf1514ff68a192ac9523c25be09252d2ac7a4 src/admin/preferences.rs
b4fa393b780b2676b5d72e0f90cd7290292f424b8062d4b5983e722d6ca72c32 src/admin/public.rs
684d5d28e0d2b10fb2baf2a56857b226d6334cc3346e1ca86de173ff914bee73 src/admin/router.rs
333d5f9f4c27941c535ac689e5826264ffa2bb86b00832b597a9bb9f811f01e8 src/admin/session.rs
-f7938b202919a49fd2ff86ecdf09efadae12e0ffc0a4246a72a76218db11aa88 src/admin/status.rs
+1abc9e1aa8bcc7eec9139daa9c70f3ad0c4796622a7754a1623018d37d4aa13e src/admin/status.rs
cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs
59ea2bb773c86c3d8240ccc212a8bf7f670e17e360def9e773906469ef135ac5 src/admin/tests.rs
5df98246c46135a176b4b8b705c92fd328719e82eb7342f088256abb976d5ae7 src/admin/tests/part_01.rs
@@ -202,15 +204,15 @@ bf1757ec16c144c7791c8e08ac2c93118aa53a88209315a6928a63ef2aaeec31 src/admin/test
5f0062abc42a3b81c79088dc0824ab4c81a22c6ea765e77d2527d07d13bffd58 src/admin/tests/part_05.rs
f869e4da5de149fc04ce1aab658befeaf3208c862f149ca976235d15832c3cdb src/admin/tests/part_06.rs
5fa006413fd93529ac9aaa4ffb28bf6b0b88d0a55a0bf0c49576db4a06fe9fc3 src/admin/tests/part_07.rs
-5073eb90cd689fac9710dc17d00f97340afc292f6d588dfa8e1565f7abf0c16f src/admin/web/acme.js
+2ae5bc857dd62882f16d1cfcee9691c9ba8afc149f4c0435addd48b564487517 src/admin/web/acme.js
446011c2c7afddf7734ceb268c75a3b4146dbe8e915b11986495e956b6c62c69 src/admin/web/app/config-history.js
-9eccd075880950e9100c7a8cca936d92f0c7106ce5140515deb3cc57f03c2bf0 src/admin/web/app/core.js
-6f15cbcfa47adf98b5da962811fbbc6731880bf99f128b372b6f9f41391eeed1 src/admin/web/app/preferences.js
+e701a8abb1893c5db18f8ef5195d323dd9aae8a820eb4c198409874d24280652 src/admin/web/app/core.js
+cb5c4c2dd4964795b61393b05537cc1528c72d7c1390953f4d119a5e55c65c7c src/admin/web/app/preferences.js
5891cbb5bfe7219ecb85d6f8d1794be1c3e4593fd3caa0668b53bd4121d083ce src/admin/web/app/runtime.js
adb56a17d6e1d517693d26c02353ed10ae277d9ce39ce15d9809ca91c9a736b1 src/admin/web/app/settings.js
74777adaba5a448f3a9abf3a46d91efc6eb81ff6f3b5441e5b99a9e1d6c09af0 src/admin/web/cache-panel.js
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
-29861dc0739d3f27339ac2bc713355cb630998ea715cb98f150eefe19a888f58 src/admin/web/docker.js
+720e201a29d4b3d2e487aa9b857ddcfe785f6f26190827ae5c265ef8c308a86a src/admin/web/docker.js
d4aef376cb44b2a13174dca1cb9383f64c04b0b93279f4f0b4805e76002548a1 src/admin/web/docs.css
60a4944a0a24b889296991b8af44b467f776fc85ec3597eb2e2940ef1f6fc713 src/admin/web/docs.html
6adb291fcc98d516fd9745d9673db16e408854a32cbf41335ae956bda11d46de src/admin/web/docs.js
@@ -223,22 +225,22 @@ f7d0955ab18c15e04369204e789f341d2e832f927c1ff20a3b2b9e35e7898f00 src/admin/web/
e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/error_504.html
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
af6e7a2e890df2fb7385a374bcb3fba52fb5a53dbe18adc1fb7865660fcba842 src/admin/web/file-logs.js
-631860ab3793ffd827c7cf19ddd049d14419509f2c2d8b27994b0e54baa0e32a src/admin/web/host404.js
-80608bd7d854fef3857480b77bdf27398b08a4263393a6bd8659191bb0b28a96 src/admin/web/index.html
+b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
+8f306bfa5635dd126a965125e11179f5475b2feec3471f36b460eb67c571bb19 src/admin/web/index.html
24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js
-cdab0c11a28deebdf401c0453fbc76b675e9e643722106fb20e812bb9648ccde src/admin/web/login-keys.json
-a253d202dc9a147f2add34c64e0f35c38fdc7f667f9908933ce5f959b1a92a2e src/admin/web/login.html
+7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
+af0fbd94d9b88ce1cce28e39dfd2c35de031adcba412dd4a9a17d9b416c256dd src/admin/web/login.html
45ca44ca82e43373f018629839287645218f225747f02b9b4a5d80e2bc8821cf src/admin/web/login.js
e169d77359785ba604ce1a728420571fdc9b491fc1c0088642399466fbed8c1b src/admin/web/management.js
-a2dbdd205b899b92cb9817f76d0c79a27b153c80ac0fd60da435c7a4fddbb958 src/admin/web/redirects.js
-f1ab20afa09f75f3635511756e36afea46a7917f04937d6f4e89c4e9f3f43204 src/admin/web/settings.js
-0ffc345ffbbd0a60b80fd39413a4d2ca1023012c0f73ff61af6f5456c5072030 src/admin/web/streams.js
-96460ed220d7f3fb332e88f4a437d3bd71ddb81580a4faf99061d1e0d9e384f2 src/admin/web/style.css
+90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.js
+20670b3167a56e0c79b061ea1b9af74a0d1e4dedb4b57cb8c211463610df28eb src/admin/web/settings.js
+bb67e77af5514478485cfb69f339d63d7eb0cbadd093fa054814a416260ccc1d src/admin/web/streams.js
+2a2543859506ac69b41e706c3e2eeb4b500b054c7302a902f71e598cc67d9007 src/admin/web/style.css
a8a73a4fa849ae49391fcd6143e900c515c8b577f71b55cbae9701fa9f5bbe47 src/admin/web/traffic-panel.js
e686d68fd6a368110a85dc4c5dc64e8093950d20d63e9aa44790c03dea6fc193 src/admin/web/transform-panel.js
f746b0aa758c1f9fbf5e656ab269f3ffe32675b90c83f0ed5b1e398322095e34 src/admin/web/vhosts/core.js
4cf331c897574f7cba035e0af12f3fc5e2c8a6efae790f8ca3be77d29e4fa593 src/admin/web/vhosts/editor.js
-e0011edc708d245cb514be170c1bbe846a0298b81d86276fe29af05010d215a4 src/admin/web/vhosts/render.js
+b17f6a06681102c507aa7bdfb48ca5a713a687de3a8ac2babf148e69943ce033 src/admin/web/vhosts/render.js
319b1f0b8b9591d54da7a9be86633b33520a259ba96e7eb32fa2a28817e768da src/admin/web/web-server.js
98c00d99822f9be54fc58afb6d4b577901712effd2c5eddce014312681864625 src/auth/files.rs
b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files/tests.rs
@@ -342,12 +344,12 @@ a16613114273c54b6d2a16a94fa8748550e91468585469642622bd2c86087baa src/logging/fo
7c0d8e8f46f96cbaf3a5cb5fd47953f9185d7558dd71e599676bc63578b7ca99 src/logging/runtime.rs
17425ebbca6bbbb0dcaeb65ebc44b1e51488a4092b7b6add556e764c6d486d6a src/logging/tests.rs
3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs
-46732ed6d9cf2a33031624f941b8318d2676fb1b6bb5142e96820301f9fa1ac4 src/main/admin_runtime.rs
-e9ea5c293b55474425ca7aa287fc07cd978c144e4054cfa450c08cab0f3e49a2 src/main/bootstrap.rs
+c66a9803e43bdd87101825e8c71ab33f890ac4c3428467ba5d43c17fdfa1439e src/main/admin_runtime.rs
+0cc35b47d2264816009160e2416a4052d0118fee4c41c35d428dc088ee68a352 src/main/bootstrap.rs
5a98d0e249338e73aa57ae2e352dc16db2f79c2d042c1544ce1c2022da76a31c src/main/proxy_runtime.rs
4dd7c1084a6214c0f2513ebf86fe8ef144470695557772bfe25979edd64491ab src/main/shutdown.rs
0a6578a0f4023b9f8d73043bf942dc5e096ef9afb8c0d2f131c14f65d08ac01d src/main/shutdown/tests.rs
-e18fec9a56171c6fd2e47eb849b2fc602c37fc3bd73893615ed9c3e1b6f4854c src/main/tasks.rs
+a72eb4ae7ffecd5e1e566fbece270a7956bb3393d3fc18be108de959e65a99c0 src/main/tasks.rs
49d7e104585c8791ac9a203dddd95802784b68c0ba65d6f90f3d57154b4c7871 src/metrics.rs
201ab702722d6d21585fe8bb49939150d61bfac7ed8dc198b98c2cc6098ffb5f src/metrics/global.rs
49b11df65b4fb0b5b727f9f867436584680b45e3f4fbd53f74bc0cab8b9a88ec src/metrics/vhost.rs
@@ -390,7 +392,7 @@ d40ad63939097d2b91339127d5835a74b618d83b7ba31db0c512f05a82107d6e src/redirect.r
811c83ae9fd0f5a13d35b5443cd37843f82b9d600bee4bb136ba9b87732a5347 src/redirect/tests.rs
d0939e1b0829c25356a0b67ecb3a178c74936dd07baa15fee24eb32544db6cad src/runtime.rs
4e0b25c2b86eefddd76c64b39dc1c1cd6d243addc32cdea99e6c3ac84ed879a6 src/runtime/certificates.rs
-117b68f9ae6322eb2037651a2b47de50c5172dbdd12507a07cf7ee4699697b44 src/runtime/constructors.rs
+de52ef274756a1543d8913d8ff36640a2091ea675882b0216d0ab99df0a2d0dc src/runtime/constructors.rs
31dc5cdea00efd63594dc6becd5a9103594696145cbc8e7b7594fb407214ad9d src/runtime/docker.rs
c052b35673d905219a71130b44a5dc0027bf9b853bff0b6bdbed4be4064550ea src/runtime/tests.rs
03144b1b00fb45c5616ca9c9b0e57013e86c1f07f129830aaec98a73b274199f src/runtime/types.rs
@@ -444,7 +446,7 @@ c42d000d1d576721af3ffe930589388693001b8113554a7658f6dcbc665b5d1b src/update_che
a67e58a597a5eeb7efe605ae6e438a8f84ff6a1cedf8cd228141cb95551872a8 src/upstream.rs
225e3078e6775ffaa6dc6f12bc87503a123983cbb347edf38cae4c37ff02a5db src/upstream/client.rs
4aa2420a92d33c50f7027f57ac4eccdc050bdb7e9ec8fa6e0eaa809228b71cd8 src/upstream/client_tests.rs
-3a4b097c0d5c0d88fbe881d84bd26a89789f68e4c46ea68b9e4a923dd4977d0b src/upstream/tests.rs
+6f932b8e59de8a5c89d3b29b838eb2a30f6e3ddd342fec29e39af1ce1b1c7d11 src/upstream/tests.rs
e4c1ff8f53112e7cfad101a266355f7e47b82895480db431662f4cc59a1d9702 src/vhost.rs
7529fe4ccf44fc8fc3e24b591c53ff41860e3f168cb227d9bbffb17797c01f38 src/vhost/model.rs
7127e081d884863b21c06f700b54884f90ec04efdfce835591b930f2d210faf6 src/vhost/tests.rs
@@ -454,7 +456,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
-024dc2d6988aa6d8dd440feb8f4bab96cc86f8a68aba0f51451d8248de70e174 src/web_server/fastcgi/protocol.rs
+fbc035f68621ac6f3c31e363712c4e06f9bc4382d94511bf93f0fdba39b627b2 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
@@ -464,14 +466,14 @@ f1d3e427c53f8b5be5768d85e69ead746d83647fcd3b355acc98e3f9d5e217e0 src/web_server
80f946570c14c8915fd21bfdb1a84dfb6a14c633ab6060acb828fab4c7c21daa src/web_server/mod.rs
67ab8bef567452f123c4b9b46a942b39c93f1f765c128edca7ee808ff1c5e304 src/web_server/request.rs
c5cb561b0f276e8082f904b9446fa5885f6b3c0608f25ea2ef425061e9f7f6b4 src/web_server/response.rs
-e57fb290cbb9f0e292666c9d54e703ef627fafbeb1ebabeabe2f348b25c6cb5c src/web_server/response/file.rs
+bbd66ca7e3cab7b5a8b42402567b99fe060f19d513bb2de9f4423aaa14513d18 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
-167d1627cb562ea7a9afc046b82ab1d51587cd37effac46e9661b05ac99d7f77 src/web_server/tests/part_02.rs
+bb09d550a0dacb6169ffabe2877f510e3a1b7ebd5a6bf527c0f19395c77b753e src/web_server/tests/part_02.rs
e54f44f6ca1ed2c41e620de9195d32c4f8bb6936c3881745316a07160f87b4fa src/web_server/tests/part_03.rs
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
1725c10331745dc04a07675784d385260da7b14567b8dba3cbae5e891eb5dadf tests/http2.rs
diff --git a/deploy/rustproxy.service b/deploy/rustproxy.service
index a6cc967..dd8dc66 100644
--- a/deploy/rustproxy.service
+++ b/deploy/rustproxy.service
@@ -12,9 +12,9 @@ WorkingDirectory=/var/lib/rustproxy
UMask=0077
Environment=RUST_LOG=rust_proxy=info,warn
Environment=MALLOC_ARENA_MAX=2
-Environment=PATH=/opt/rustproxy-certbot/bin:/usr/local/bin:/usr/bin:/bin
+Environment=PATH=/usr/local/bin:/usr/bin:/bin
EnvironmentFile=-/etc/rustproxy/environment
-ExecStart=/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy --admin-listen 127.0.0.1:9090 serve --proxy-listen 0.0.0.0:80 --tls-listen 0.0.0.0:443
+ExecStart=/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy --admin-listen 127.0.0.1:9090 serve --proxy-listen 0.0.0.0:80 --tls-listen 0.0.0.0:443 --allow-remote-admin
Restart=on-failure
RestartSec=3
KillSignal=SIGINT
diff --git a/dev.sh b/dev.sh
index 58fc50b..4544770 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.0
+RustProxy 0.27.1
./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
@@ -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.0...\n' >&2
+printf 'Building RustProxy 0.27.1...\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 b309bc0..56d7d1f 100644
--- a/docker-agent/model.go
+++ b/docker-agent/model.go
@@ -1,6 +1,6 @@
package main
-const version = "0.27.0"
+const version = "0.27.1"
type config struct {
Listen string
diff --git a/docs/ACME_PROVIDERS.md b/docs/ACME_PROVIDERS.md
index 595258a..4bb4ec9 100644
--- a/docs/ACME_PROVIDERS.md
+++ b/docs/ACME_PROVIDERS.md
@@ -26,16 +26,23 @@ certificate to a vhost. No CA operation occurs on startup or merely opening a fo
| PEM import | Upload fullchain/certificate and corresponding private key, then validate |
| Existing server files | Advanced: reference a PEM pair already in the certificate directory |
-Python 3 and Certbot must be executable by the RustProxy service user. Install
-plugins in that SAME Certbot environment. The application does not install packages
-or execute sudo. Check the CLI installation with `bash dev.sh acme --doctor` and
-`certbot plugins`; this does not validate provider credentials or DNS reachability.
-DNS uses the API/plugin appropriate to the selected provider, not a Cloudflare
-Tunnel integration or a hand-written replacement for ACME.
+RustProxy now owns its preferred Certbot environment under `/acme-python`.
+When it is missing, RustProxy creates a virtual environment and installs pinned Certbot
+5.8.0 plus `certbot-dns-cloudflare`, `certbot-dns-ovh` and
+`certbot-dns-rfc2136` 5.8.0 with pip. No distribution Python files are modified and
+RustProxy never executes sudo. If the stdlib `venv` module is unavailable but system
+pip works, `virtualenv` is bootstrapped only inside the RustProxy data directory.
-The shipped OCI image already contains Certbot plus the Cloudflare, OVH and RFC2136
-plugins in one dedicated Python environment. Host/source installations remain under
-the operator's package-management policy and must provide the plugins they use.
+If local setup fails, a runnable system `certbot` from `PATH` is used as fallback. The
+Certificates panel reports whether the managed environment, the system fallback, or no
+ACME runtime is active. Systems without Python (for example some appliance layouts)
+retain PEM import and existing-file certificate modes even though ACME issuance/renewal
+is unavailable. `bash dev.sh acme --doctor` and `certbot plugins` inspect only the
+current shell/system fallback; they do not validate provider credentials or DNS reachability.
+
+The shipped OCI image contains Python, venv/pip and distribution Certbot packages as an
+offline fallback. Its preferred managed environment is still created in the persisted
+`/var/lib/rustproxy` data volume.
## HTTP-01 requirements and scope
diff --git a/docs/API.md b/docs/API.md
index 4ca9e1c..983d397 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -406,7 +406,7 @@ For a certificate owned by a local external issuer such as system Certbot, prefe
CLI deploy hook, which calls that loopback API after bounded local file checks:
```bash
-rust-proxy tls deploy --id shared-cert \
+/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
```
@@ -444,7 +444,7 @@ curl --fail --show-error \
For an on-host Certbot hook, a token is optional: invoke the installed binary directly instead:
```bash
-rust-proxy tls deploy --id shared-cert \
+/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
```
diff --git a/docs/API_ACCESS.md b/docs/API_ACCESS.md
index bc9432a..1ea3e61 100644
--- a/docs/API_ACCESS.md
+++ b/docs/API_ACCESS.md
@@ -1,4 +1,4 @@
-# Administration API access - 0.27.0
+# Administration API access - 0.27.1
## Sign-in boundary
diff --git a/docs/CONTAINER.md b/docs/CONTAINER.md
index 88a26da..791b26f 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.0 .
+docker build -t rustproxy:0.27.1 .
```
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.0
+ rustproxy:0.27.1
docker exec rustproxy rust-proxy admin setup-token
```
@@ -32,11 +32,11 @@ RustProxy configuration and choose a certificate, then bind the administration
listener to a non-loopback address. Publish the admin port only after HTTPS is
active. Plain remote HTTP keeps the legacy explicit risk gate.
-The runtime image includes Python, OpenSSL and a dedicated Certbot environment with
-all DNS plugins currently supported by RustProxy: Cloudflare, OVH and RFC2136.
-HTTP-01 needs no DNS plugin. The Certbot core and all three DNS plugins are pinned to
-the same release so they stay in one compatible Python environment; no image extension
-is required for the built-in certificate providers.
+The runtime image includes Python, venv/pip, OpenSSL and distribution Certbot packages
+for Cloudflare, OVH and RFC2136 fallback. RustProxy prefers its own pinned Certbot
+environment under `/var/lib/rustproxy/acme-python`; because `/var/lib/rustproxy` is the
+persisted volume, that environment survives container replacement. If pip setup is not
+possible, the image can fall back to the distribution Certbot binaries already in `PATH`.
For access-log files, prefer a writable path under `/var/lib/rustproxy` or mount a
separate writable log volume. The built-in JSON file sink is disabled by default.
diff --git a/docs/FILE_LOGS.md b/docs/FILE_LOGS.md
index 60dad76..3932596 100644
--- a/docs/FILE_LOGS.md
+++ b/docs/FILE_LOGS.md
@@ -1,6 +1,6 @@
# Stored log explorer
-RustProxy 0.27.0 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.1 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/INSTALL.md b/docs/INSTALL.md
index c7c2c70..90bd87c 100644
--- a/docs/INSTALL.md
+++ b/docs/INSTALL.md
@@ -1,4 +1,4 @@
-# Installing and updating RustProxy 0.27.0
+# Installing and updating RustProxy 0.27.1
The scripts prepare a Linux/systemd installation. This source archive is **not a
verified production build**: Rust/Cargo, real CA operations, live systemd startup
@@ -31,7 +31,7 @@ Plain remote HTTP requires both an explicit non-loopback `RUSTPROXY_ADMIN_LISTEN
Equivalent prerequisite commands are:
```bash
-apt install -y build-essential gcc git ca-certificates python3 pkg-config wget sudo python3-certbot-dns-rfc2136 python3-certbot-dns-cloudflare
+apt install -y build-essential gcc git ca-certificates python3 python3-venv python3-pip pkg-config wget sudo certbot python3-certbot-dns-rfc2136 python3-certbot-dns-cloudflare python3-certbot-dns-ovh
wget -qO- --https-only --secure-protocol=TLSv1_2 https://sh.rustup.rs | sh
```
@@ -154,25 +154,35 @@ editable desired-state entries.
## ACME prerequisites
-Automatic renewal runs inside RustProxy, not a separately installed cron timer.
-Install `certbot`, the DNS plugins you use (`certbot-dns-cloudflare`,
-`certbot-dns-ovh`, `certbot-dns-rfc2136`), Python and OpenSSL on the server. HTTP-01
-uses Certbot's webroot authenticator, not a competing standalone web server.
-Certbot and its plugins must be in the **same Python environment**. An optional
-root-owned virtual environment at `/opt/rustproxy-certbot` is on the service PATH;
-otherwise use `/usr/local/bin` or `/usr/bin`. Avoid an executable under an
-administrator's home directory: the unit uses `ProtectHome=true`.
+Automatic renewal runs inside RustProxy, not a separately installed cron timer. RustProxy
+prefers a private Python environment at `/acme-python`. When that environment is
+missing or incomplete, the service creates it automatically and installs the pinned Certbot
+core plus the Cloudflare, OVH and RFC2136 plugins with `pip`. The application never executes
+`sudo` and never modifies distribution-owned Python site-packages.
-Check the effective service account's environment rather than only your shell:
+The host should provide Python 3 with either the standard `venv` module or `pip`; RustProxy can
+bootstrap `virtualenv` into its own data directory when a distribution splits `venv` into a
+separate package. OpenSSL remains a local certificate-inspection fallback. If the private
+environment cannot be created, RustProxy uses a runnable distribution `certbot` from `PATH`
+when one is available.
+
+If Python is absent (a possible layout on appliances such as OPNsense), or Python is present
+but no local environment can be created and no system Certbot exists, the Certificates panel
+shows ACME as unavailable. PEM upload and references to existing certificate files remain
+available.
+
+On the standard service installation the managed environment therefore lives at
+`/var/lib/rustproxy/acme-python`. Do not run a second Certbot renewal timer over RustProxy's
+`certs/acme` profiles. See [renewal](RENEWAL.md).
+
+A source-tree preflight can still inspect a distribution-provided fallback environment with:
```bash
-sudo -u rustproxy env PATH=/opt/rustproxy-certbot/bin:/usr/local/bin:/usr/bin:/bin \
+sudo -u rustproxy env PATH=/usr/local/bin:/usr/bin:/bin \
python3 scripts/acme_provider.py --doctor
```
-The provider scripts are embedded in the binary; the source file above is just a
-preflight command. The service owns its complete private `certs/acme` tree. Do not
-run a second Certbot renewal timer over those same profiles. See [renewal](RENEWAL.md).
+The provider script is embedded in the RustProxy binary; this command is diagnostic only.
### Using a separate system Certbot
@@ -182,14 +192,14 @@ A Certbot deploy hook can publish every successful renewal to all vhosts that al
that id:
```bash
-/usr/bin/rust-proxy tls deploy --id shared-cert \
+/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \
--cert /etc/letsencrypt/live/example/fullchain.pem \
--key /etc/letsencrypt/live/example/privkey.pem
```
The command reads the Certbot files locally and sends them to the authenticated loopback `POST /api/certificates/deploy` endpoint. The running RustProxy service writes the private generation with its own filesystem identity and hot-publishes it for new public TLS/H2/H3 handshakes. No per-vhost edit is needed. If another
local process instead replaces the exact files already referenced by RustProxy, run
-`/usr/bin/rust-proxy tls reload` (or authenticated `POST /api/certificates/reload`) afterward. If the replacement belongs to one configured manual certificate ID, automation may instead call `POST /api/certificates/{certificate_id}/reload`; the same endpoint accepts a scoped Certificate API Key ID + Bearer token and does not change the configuration revision.
+`/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls reload` (or authenticated `POST /api/certificates/reload`) afterward. If the replacement belongs to one configured manual certificate ID, automation may instead call `POST /api/certificates/{certificate_id}/reload`; the same endpoint accepts a scoped Certificate API Key ID + Bearer token and does not change the configuration revision.
Administration TLS is startup-fixed and may still require a service restart.
Optional environment overrides go in a root-owned `/etc/rustproxy/environment`
diff --git a/docs/README.md b/docs/README.md
index fb482c7..efb9702 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.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.
- [0.26.7 implementation note](V0.26.7_IMPLEMENTATION.md) - HOT/COLD occupancy meters, backend rotation continuity and container Certbot packaging.
- [0.26.6 implementation note](V0.26.6_IMPLEMENTATION.md) - composed ACL allow semantics, certificate-form reset, backend table preferences and NPM migration helper documentation.
@@ -68,7 +69,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.0_IMPLEMENTATION.md](V0.27.0_IMPLEMENTATION.md); [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.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md); [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.
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/RENEWAL.md b/docs/RENEWAL.md
index 5a4e91a..5ccc004 100644
--- a/docs/RENEWAL.md
+++ b/docs/RENEWAL.md
@@ -82,7 +82,7 @@ For a certificate intentionally owned by a separate system Certbot instead of th
renewal subsystem, use a manual certificate id and a Certbot deploy hook that runs, for example:
```bash
-/usr/bin/rust-proxy tls deploy --id shared-cert \
+/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
```
diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md
index 29b19fe..0881f19 100644
--- a/docs/TEST_REPORT.md
+++ b/docs/TEST_REPORT.md
@@ -1,24 +1,22 @@
-# Verification report - RustProxy 0.27.0
+# Verification report - RustProxy 0.27.1
## Scope
-RustProxy 0.27.0 changes downstream compression policy, Docker-vhost publication continuity, cache-meter rendering, fresh-install response identity and source-package layout while keeping SQLite schema 8.
+RustProxy 0.27.1 changes first-administrator onboarding, ACME/Certbot runtime preparation and account-scoped sorting for vhosts, Docker vhosts, redirects, 404 hosts and streams while keeping SQLite schema 8.
-Dynamic response compression defaults to zstd, Brotli and gzip enabled, with deflate opt-in. Client `Accept-Encoding` quality values are authoritative; equal quality uses deterministic `zstd -> br -> gzip -> deflate` server preference. Existing `Content-Encoding`, `no-transform`, range responses/requests, SSE and upgrades remain excluded. Cookie, authorization, `Set-Cookie`, `private` and `no-store` no longer disable compression as blanket rules.
+The first-run administrator screen now presents a complete command using the default `./data` directory and a second standard-service example using `/var/lib/rustproxy`.
-Ordinary vhosts and Web Server services can inherit, enable or disable dynamic compression and independently inherit/enable/disable zstd, Brotli, gzip and deflate. Docker vhosts expose one Compression checkbox only and use the codec set/priorities from global **Settings -> Response**. Global codec switches remain the hard availability ceiling and `min_bytes` remains global.
+Certificate automation now prefers a RustProxy-owned Python environment at `/acme-python`. Missing environments are prepared with `venv` and pip; when a distribution omits the stdlib `venv` module but provides pip, RustProxy can bootstrap `virtualenv` into its own data directory rather than changing distribution site-packages. Certbot 5.8.0 and the Cloudflare, OVH and RFC2136 plugins are installed into that managed environment. A runnable distribution Certbot remains the fallback. The Certificates panel reports managed/system/preparing/unavailable state and missing Python/pip diagnostics; manual PEM import and existing-file workflows do not depend on ACME runtime availability.
-An explicitly published Docker assignment keeps its generated hostname even when its service disappears. A successful inventory without the assigned service materializes an unavailable route that returns HTTP 503 instead of removing the hostname. Agent polling errors retain the previous successful inventory when one exists, and a returning service is reconciled automatically. The cache overview progress elements have explicit WebKit/Chromium and Firefox progress-value styling so their visual fill follows the numeric meter value.
-
-Fresh installations now default the response `Server` policy to `custom` with the exact value `RustProxy`. Existing stored settings are not rewritten, and a configuration document predating the `server_header` field still deserializes with the historical preserve behavior. Debian package-build metadata is no longer shipped in the main source tree; ready packages are maintained separately at `https://rustproxy.linuxiarz.pl`.
+The route/list pages gain feature-specific sort-column and sort-direction controls. Their values are saved in the existing account-scoped administrator preferences and default safely when absent. Redirects and 404 hosts use the same search/sort/action toolbar structure as Vhosts.
## Regression coverage
-Compression unit coverage checks defaults, q-value negotiation, equal-quality priority, each supported codec, existing-encoding passthrough, scoped per-codec resolution and the revised cookie/auth/cache-privacy policy. Docker unit coverage checks missing published service -> retained 503, failed agent observation -> retained last-known backend, the single compression switch and ignoring legacy Docker per-codec values. Settings coverage checks the fresh-install `Server: RustProxy` default and legacy missing-field compatibility. Static/UI contracts require ordinary-vhost and Web-Service codec controls to hide/disable when their scope is explicitly OFF, require Docker vhosts to expose only the single Compression checkbox, and retain the 503 path, visible cache progress styling, external Debian-packaging separation and reproducible archives without a `debian/` tree.
+Static/source contracts cover the new first-run commands, ACME runtime status UI/API, managed environment path and pinned package set, distro fallback markers, preference fields and list-specific sort controls. They also reject the development-only `./dev.sh tls deploy` UI hook and require the production binary/data-directory example, both official repository links and Python/pip/Certbot runtime metadata in the startup banner and authenticated application information. Existing release-smoke, language/OpenAPI, source-module, security, web-server, browser-service and Docker-agent gates remain part of the Rust-independent verification set.
## Verification status in this workspace
-Rust/Cargo is not installed in this workspace. Rust-independent checks pass 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 remain explicitly open until this source is checked on a Rust-enabled release host.
| Check | Status |
|---|---|
@@ -43,4 +41,4 @@ Rust/Cargo is not installed in this workspace. Rust-independent checks pass here
| `cargo test --all-targets --all-features` | BLOCKED: Rust/Cargo toolchain unavailable |
| complete release build | BLOCKED at Rust compilation: Rust/Cargo toolchain unavailable |
-The 0.27.0 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.
+The 0.27.1 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.
diff --git a/docs/V0.27.1_IMPLEMENTATION.md b/docs/V0.27.1_IMPLEMENTATION.md
new file mode 100644
index 0000000..3028699
--- /dev/null
+++ b/docs/V0.27.1_IMPLEMENTATION.md
@@ -0,0 +1,41 @@
+# RustProxy 0.27.1 implementation note
+
+## Scope
+
+0.27.1 improves first-run administration, makes ACME/Certbot setup independent of one distribution packaging layout, and adds per-user list sorting to the main routing views. SQLite remains schema 8; the saved administrator-preference document gains only defaulted fields.
+
+## First administrator
+
+The initial login/setup screen now shows a directly runnable token command for the default data path:
+
+```bash
+rust-proxy --data-dir ./data admin setup-token
+```
+
+A standard service-installation example is shown below it:
+
+```bash
+rust-proxy --data-dir /var/lib/rustproxy admin setup-token
+```
+
+This removes the previous `DATA_DIR` placeholder from the primary command while still documenting the installed-service path.
+
+## Managed certificate runtime
+
+RustProxy now prefers a private Python environment at `/acme-python`. On startup, and again on demand before an ACME job, it validates that environment and initializes it when missing. The managed environment pins Certbot 5.8.0 plus the Cloudflare, OVH and RFC2136 plugins.
+
+Initialization first uses the available `python3`/`python` stdlib `venv`. If a distribution separates `venv` but provides pip, RustProxy installs `virtualenv` only into a temporary directory below its own data directory and uses it to create the managed environment; it does not modify distribution-owned site-packages. The managed environment receives its own pip packages.
+
+If managed setup cannot be completed, RustProxy tests a distribution `certbot` from `PATH` and uses it as the fallback when runnable. If Python is unavailable, or neither managed nor system Certbot can be used, the Certificates panel shows an unavailable state and a specific diagnostic. Manual PEM import and use of existing certificate files remain independent of the ACME runtime. This makes minimal systems, including appliances that intentionally omit Python/pip, fail visibly instead of silently.
+
+## List sorting
+
+The administrator preference document now stores sort column and direction separately for ordinary vhosts, Docker vhosts, 404 hosts and streams. Each page exposes only columns meaningful to that feature and reuses the backend-table column/direction selection pattern. Preferences are account-scoped and reapplied when the administrator session/preferences are loaded.
+
+## Compatibility
+
+No SQLite migration is required. Schema 8 remains current. Existing saved configuration continues to load; missing sort preferences receive defaults. Historical distribution Certbot packages remain useful as an offline/runtime fallback, while the application-owned environment is the preferred path.
+
+## Verification limitation
+
+This workspace does not provide Cargo/Rust, so compiler/type/Clippy/unit-test gates must still be run on a Rust-enabled release host. Rust-independent source, JavaScript, documentation, packaging and Go-agent checks are tracked in [TEST_REPORT](TEST_REPORT.md).
diff --git a/grafana/README.md b/grafana/README.md
index 2782306..84e1bfa 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.0 metric names and a scrape interval around 15s.
+dashboard expects RustProxy 0.27.1 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 7926c1b..7c09019 100644
--- a/grafana/rustproxy-overview.json
+++ b/grafana/rustproxy-overview.json
@@ -4529,9 +4529,9 @@
},
"timepicker": {},
"timezone": "browser",
- "title": "RustProxy 0.27.0 - Operations",
+ "title": "RustProxy 0.27.1 - Operations",
"uid": "rustproxy-0170",
"version": 5,
"weekStart": "",
- "description": "RustProxy 0.27.0 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.1 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-debian13.sh b/install-debian13.sh
index 25df77d..c9d5d41 100755
--- a/install-debian13.sh
+++ b/install-debian13.sh
@@ -74,8 +74,8 @@ echo "RustProxy bootstrap mode: $MODE"
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y \
- build-essential gcc git ca-certificates python3 pkg-config wget sudo \
- python3-certbot-dns-rfc2136 python3-certbot-dns-cloudflare
+ build-essential gcc git ca-certificates python3 python3-venv python3-pip pkg-config wget sudo \
+ certbot python3-certbot-dns-rfc2136 python3-certbot-dns-cloudflare python3-certbot-dns-ovh
created_builder=0
if [[ -n ${SUDO_USER:-} && ${SUDO_USER} != root ]] && id "${SUDO_USER}" >/dev/null 2>&1; then
diff --git a/langs/en_US.json b/langs/en_US.json
index e431289..c61e08d 100644
--- a/langs/en_US.json
+++ b/langs/en_US.json
@@ -182,8 +182,8 @@
"defaultCertificate": "Use as the default certificate",
"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 `./dev.sh tls deploy --id ID --cert FULLCHAIN --key PRIVKEY`. Reload re-reads configured files without changing the configuration revision.",
- "certificateHint": "External Certbot hook: ./dev.sh tls deploy --id shared-cert --cert /etc/letsencrypt/live/example/fullchain.pem --key /etc/letsencrypt/live/example/privkey.pem",
+ "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-proxyopt/rustproxy/current/ --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",
@@ -2009,7 +2009,15 @@
"aboutLicense": "License",
"aboutLicenseValue": "Open Source",
"aboutOpenSourceText": "RustProxy is an open-source project. Its source code is publicly available for inspection, development and community contribution.",
- "aboutRepository": "Source repository",
+ "aboutRepository": "Official source repository",
+ "aboutPackagesRepository": "Official package repository",
+ "aboutPythonRuntime": "Certificate Python runtime",
+ "runtimePython": "Python",
+ "runtimePip": "pip",
+ "runtimeCertbot": "Certbot",
+ "runtimeAcmeMode": "ACME mode",
+ "runtimeAvailable": "Available",
+ "runtimeUnavailable": "Unavailable",
"vhPoolRecycleRequests": "Recycle upstream pool after requests (0 = off)",
"vhPoolRecycleSeconds": "Maximum upstream pool age (seconds, 0 = off)",
"vhHttp1MaxBuffer": "HTTP/1 upstream max read buffer (bytes)",
@@ -2125,6 +2133,21 @@
"buttonStyleNormal": "Normal",
"buttonStyleCompact": "Compact",
"buttonStyleHelp": "Normal uses the refreshed controls; Compact restores the smaller source-style buttons across all actions and dialog tabs.",
- "entityEnableHint": "Controls whether this item is active."
+ "entityEnableHint": "Controls whether this item is active.",
+ "setupCommandExample": "Standard service installation example:",
+ "certificateRuntimeTitle": "ACME runtime",
+ "certificateRuntimePreparing": "Preparing",
+ "certificateRuntimeManaged": "Managed Python",
+ "certificateRuntimeSystem": "System fallback",
+ "certificateRuntimeUnavailable": "Unavailable",
+ "certificateRuntimePath": "Managed environment",
+ "acme.runtime_checking": "Checking the local Python environment for certificate management.",
+ "acme.runtime_initializing": "Creating a private Python environment in the RustProxy data directory and installing Certbot modules with pip.",
+ "acme.runtime_ready": "The private Python/pip environment for certificate management is ready.",
+ "acme.runtime_fallback": "The private environment could not be used. RustProxy is using the distribution Python and Certbot available in PATH.",
+ "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_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 642b92d..3a110f3 100644
--- a/langs/pl_pl.json
+++ b/langs/pl_pl.json
@@ -182,8 +182,8 @@
"defaultCertificate": "Użyj jako certyfikatu domyślnego",
"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 poleceniem `./dev.sh tls deploy --id ID --cert FULLCHAIN --key PRIVKEY`. Wczytanie ponownie odczytuje skonfigurowane pliki bez zmiany revision.",
- "certificateHint": "Hook Certbota: ./dev.sh tls deploy --id shared-cert --cert /etc/letsencrypt/live/example/fullchain.pem --key /etc/letsencrypt/live/example/privkey.pem",
+ "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",
"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",
@@ -2009,7 +2009,15 @@
"aboutLicense": "Licencja",
"aboutLicenseValue": "Open Source",
"aboutOpenSourceText": "RustProxy jest projektem open source. Kod źródłowy jest publicznie dostępny do wglądu, rozwoju i współtworzenia.",
- "aboutRepository": "Repozytorium źródłowe",
+ "aboutRepository": "Oficjalne repozytorium źródłowe",
+ "aboutPackagesRepository": "Oficjalne repozytorium pakietów",
+ "aboutPythonRuntime": "Środowisko Python certyfikatów",
+ "runtimePython": "Python",
+ "runtimePip": "pip",
+ "runtimeCertbot": "Certbot",
+ "runtimeAcmeMode": "Tryb ACME",
+ "runtimeAvailable": "Dostępny",
+ "runtimeUnavailable": "Niedostępny",
"vhPoolRecycleRequests": "Odśwież pulę upstream po liczbie żądań (0 = wył.)",
"vhPoolRecycleSeconds": "Maks. wiek puli upstream (sekundy, 0 = wył.)",
"vhHttp1MaxBuffer": "Maks. bufor odczytu HTTP/1 upstream (bajty)",
@@ -2125,6 +2133,21 @@
"buttonStyleNormal": "Normalny",
"buttonStyleCompact": "Kompaktowy",
"buttonStyleHelp": "Normalny używa odświeżonych przycisków; Kompaktowy przywraca mniejsze przyciski ze źródłowego interfejsu we wszystkich akcjach i zakładkach modali.",
- "entityEnableHint": "Określa, czy ten element jest aktywny."
+ "entityEnableHint": "Określa, czy ten element jest aktywny.",
+ "setupCommandExample": "Przykład dla standardowej instalacji usługi:",
+ "certificateRuntimeTitle": "Środowisko ACME",
+ "certificateRuntimePreparing": "Przygotowanie",
+ "certificateRuntimeManaged": "Python aplikacji",
+ "certificateRuntimeSystem": "Fallback systemowy",
+ "certificateRuntimeUnavailable": "Niedostępne",
+ "certificateRuntimePath": "Środowisko aplikacji",
+ "acme.runtime_checking": "Sprawdzanie lokalnego środowiska Python do zarządzania certyfikatami.",
+ "acme.runtime_initializing": "Tworzenie prywatnego środowiska Python w katalogu danych RustProxy i instalowanie modułów Certbota przez pip.",
+ "acme.runtime_ready": "Prywatne środowisko Python/pip do zarządzania certyfikatami jest gotowe.",
+ "acme.runtime_fallback": "Nie można użyć prywatnego środowiska. RustProxy korzysta z systemowego Pythona i Certbota dostępnego w PATH.",
+ "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_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 1897766..dcf1071 100755
--- a/make_zip.py
+++ b/make_zip.py
@@ -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.0.zip')
+ parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.27.1.zip')
output = parser.parse_args().output.resolve()
paths = files()
checksum = ROOT / 'SHA256SUMS'
diff --git a/scripts/check_source.py b/scripts/check_source.py
index 18fc1db..4128611 100755
--- a/scripts/check_source.py
+++ b/scripts/check_source.py
@@ -24,7 +24,7 @@ def read_docker_agent_source() -> str:
preflight(root)
manifest = tomllib.loads((root / "Cargo.toml").read_text())
-assert manifest["package"]["version"] == "0.27.0"
+assert manifest["package"]["version"] == "0.27.1"
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
h3dep = manifest["dependencies"]["h3"]
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"]
@@ -32,14 +32,14 @@ assert "h3-quinn" not in manifest["dependencies"]
lock_path = root / "Cargo.lock"
if lock_path.exists():
lock = lock_path.read_text()
- assert 'name = "rust-proxy"\nversion = "0.27.0"' in lock
+ assert 'name = "rust-proxy"\nversion = "0.27.1"' in lock
assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries"
for release_doc, marker in {
- "README.md": "# RustProxy 0.27.0",
- "CHANGELOG.md": "## 0.27.0",
- "ROADMAP.md": "## 0.27.0",
- "AGENTS.md": "Current source version: 0.27.0.",
- "docs/TEST_REPORT.md": "# Verification report - RustProxy 0.27.0",
+ "README.md": "# RustProxy 0.27.1",
+ "CHANGELOG.md": "## 0.27.1",
+ "ROADMAP.md": "## 0.27.1",
+ "AGENTS.md": "Current source version: 0.27.1.",
+ "docs/TEST_REPORT.md": "# Verification report - RustProxy 0.27.1",
}.items():
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}"
assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
@@ -113,8 +113,12 @@ for marker in [
'MODE=update',
'bash update.sh',
'--prefix "$PREFIX"',
+ 'python3-venv',
+ 'python3-pip',
+ 'certbot',
'python3-certbot-dns-rfc2136',
'python3-certbot-dns-cloudflare',
+ 'python3-certbot-dns-ovh',
]:
assert marker in bootstrap, marker
assert 'curl ' not in bootstrap, 'Debian bootstrap must use wget instead of curl'
@@ -136,12 +140,13 @@ assert "USER rustproxy:rustproxy" in dockerfile
assert 'VOLUME ["/var/lib/rustproxy"]' in dockerfile
assert "COPY . ." in dockerfile
for marker in [
- "python3 -m venv /opt/rustproxy-certbot",
- "certbot==5.8.0",
- "certbot-dns-cloudflare==5.8.0",
- "certbot-dns-ovh==5.8.0",
- "certbot-dns-rfc2136==5.8.0",
- "PATH=/opt/rustproxy-certbot/bin:",
+ "python3-venv",
+ "python3-pip",
+ "certbot",
+ "python3-certbot-dns-cloudflare",
+ "python3-certbot-dns-ovh",
+ "python3-certbot-dns-rfc2136",
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
]:
assert marker in dockerfile, marker
assert "data" in (root / ".dockerignore").read_text().splitlines()
@@ -157,6 +162,21 @@ for selector in ["config-history-month", "config-history-day", "config-history-c
assert ".vhost-chart-line" in css and ".host-chip-editor" in css
assert 'class="vhost-dialog logging-format-dialog"' in html
assert 'data-panel="streams"' in html and 'id="panel-streams"' in html
+for marker in ['id="vhost-sort"', 'id="vhost-sort-direction"', 'id="docker-vhost-sort"', 'id="docker-vhost-sort-direction"', 'id="redirect-sort"', 'id="redirect-sort-direction"', 'id="notfound-sort"', 'id="notfound-sort-direction"', 'id="stream-sort"', 'id="stream-sort-direction"']:
+ assert marker in html, marker
+preferences_source = (root / "src/admin/preferences.rs").read_text(encoding="utf-8")
+for marker in ['vhost_list_sort', 'docker_vhost_sort', 'redirect_list_sort', 'notfound_list_sort', 'stream_list_sort', 'VHOST_LIST_SORT_COLUMNS', 'DOCKER_VHOST_SORT_COLUMNS', 'REDIRECT_LIST_SORT_COLUMNS', 'NOTFOUND_LIST_SORT_COLUMNS', 'STREAM_LIST_SORT_COLUMNS']:
+ assert marker in preferences_source, marker
+for source_name, markers in {
+ "src/admin/web/vhosts/render.js": ["vhost_list_sort", "vhost_list_descending"],
+ "src/admin/web/docker.js": ["docker_vhost_sort", "docker_vhost_descending", "sortServiceRows"],
+ "src/admin/web/redirects.js": ["redirect_list_sort", "redirect_list_descending"],
+ "src/admin/web/host404.js": ["notfound_list_sort", "notfound_list_descending"],
+ "src/admin/web/streams.js": ["stream_list_sort", "stream_list_descending"],
+}.items():
+ source_text = (root / source_name).read_text(encoding="utf-8")
+ for marker in markers:
+ assert marker in source_text, (source_name, marker)
assert 'data-panel="docker-vhosts"' in html and 'id="panel-docker-vhosts"' in html
assert 'id="settings-tab-docker-agents"' in html and 'id="settings-page-docker-agents"' in html
assert '/assets/docker.js' in html and 'cache-live-toggle' in html
@@ -267,7 +287,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
assert '"StreamMetrics"' in openapi and '"streams"' in openapi
-for marker in ['"version": "0.27.0"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
+for marker in ['"version": "0.27.1"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
assert marker in openapi, marker
for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
assert marker in openapi, marker
@@ -537,6 +557,15 @@ for marker in ['function selectTab(name, focus = false, persist = true)', 'log_d
for marker in ['resetIssueFormJob = value.job?.id || null', 'value.status === "ready"', 'resetIssueForm(); resetIssueFormJob = null', 'value.status === "failed"']:
assert marker in acme_js, marker
assert 'render(value.job); resetIssueForm();' not in acme_js, "ACME form must not reset before issuance actually succeeds"
+for marker in ['id="acme-runtime-card"', 'id="acme-runtime-message"', 'id="acme-runtime-state"', 'id="acme-runtime-path"']:
+ assert marker in html, marker
+for marker in ['function renderRuntime(value)', 'runtimeAvailable', 'certificateRuntimeUnavailable', 'acme.runtime_checking']:
+ assert marker in acme_js, marker
+assert 'rust-proxy --data-dir ./data admin setup-token' in html
+assert 'rust-proxy --data-dir /var/lib/rustproxy admin setup-token' in html
+login_html_source = (root / "src/admin/web/login.html").read_text(encoding="utf-8")
+assert 'rust-proxy --data-dir ./data admin setup-token' in login_html_source
+assert 'rust-proxy --data-dir /var/lib/rustproxy admin setup-token' in login_html_source
login_js = (root / "src/admin/web/login.js").read_text()
assert 'const panelPaths = new Set' in login_js and 'panelPaths.has(location.pathname)' in login_js
assert '"/404-hosts"' in login_js
@@ -558,6 +587,20 @@ h2_settings_controls = integrations_js.split('function http2SettingsControls', 1
assert 'listener-h2' not in h2_settings_controls, 'HTTP/2 enablement belongs to the listener editor, not the tuning page'
h3_settings_controls = integrations_js.split('function http3SettingsControls', 1)[1].split('function addRow', 1)[0]
assert 'h3Enable' not in h3_settings_controls, 'HTTP/3 enablement belongs to the listener editor, not the tuning page'
+for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'id="about-python-runtime"', 'id="runtime-python"', 'id="acme-runtime-components"']:
+ assert marker in html, marker
+for language in ['langs/en_US.json', 'langs/pl_pl.json']:
+ language_text = (root / language).read_text(encoding='utf-8')
+ assert './dev.sh tls deploy' not in language_text
+ assert '/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy' in language_text
+startup_source = rust_source(root, 'src/main/admin_runtime.rs')
+for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'join("acme-python")', 'certificate_runtime']:
+ assert marker in startup_source, marker
+for document in ['docs/RENEWAL.md', 'docs/INSTALL.md', 'docs/API.md']:
+ document_text = (root / document).read_text(encoding='utf-8')
+ assert './dev.sh tls deploy' not in document_text
+ assert '/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy' in document_text
+assert '"certificate_runtime": state.acme.runtime_view()' in rust_source(root, 'src/admin/status.rs')
assert 'data-i18n="processDiagnostics">Process<' in html
for marker in ['function setOptionalMetric', 'setOptionalMetric("process-rss-anon"', 'setOptionalMetric("system-swap"', 'closest(".mini-live-chart").hidden']:
assert marker in app_js, marker
@@ -565,6 +608,11 @@ for marker in ['/api/certificate-api', 'certificate-api-form', 'certificate-api-
assert marker in settings_js or marker in html, marker
db_source = rust_source(root, "src/db.rs")
acme_source = rust_source(root, "src/acme.rs")
+acme_runtime_source = (root / "src/acme/runtime_env.rs").read_text(encoding="utf-8")
+assert 'runtime_setup_once' in acme_source
+assert 'include!("acme/runtime_env.rs")' in (root / "src/acme.rs").read_text(encoding="utf-8")
+for marker in ['CERTBOT_PACKAGES', 'certbot==5.8.0', 'certbot-dns-cloudflare==5.8.0', 'certbot-dns-ovh==5.8.0', 'certbot-dns-rfc2136==5.8.0', 'join("acme-python")', 'virtualenv>=20,<21', 'system_has_pip', 'distro_certbot_available', 'acme.runtime_python_missing', 'acme.runtime_pip_missing']:
+ assert marker in acme_runtime_source, marker
main_source = rust_source(root, "src/main.rs")
logging_source = rust_source(root, "src/logging.rs")
secret_store_source = rust_source(root, "src/secret_store.rs")
@@ -636,6 +684,7 @@ assert 'load_renewal_observations' not in acme_source and 'renewals.json' not in
for marker in ['pub kind: &\'static str', 'pub log_directory: String', 'pub(crate) fn certbot_log_directory', 'kind: "issue"', '"renewal-dry-run"']:
assert marker in acme_source, marker
assert '"log_directories":log_directories' in admin and 'crate::acme::certbot_log_directory' in admin
+assert '"runtime":state.acme.runtime_view()' in admin
assert 'let cert_dir = Some(directory.clone());' in main_source, 'certificate directory must remain available after ValidationPolicy construction'
assert 'accept-remote-admin-risk' in main_source and 'hide = true' in main_source
for marker in [
@@ -767,7 +816,7 @@ assert any(variable.get('name') == 'traffic_vhost' for variable in grafana['temp
assert any(panel.get('title') == 'Traffic analytics and Web Server' for panel in grafana['panels'])
assert any(panel.get('title') == 'Traffic capture state' for panel in grafana['panels'])
assert any(panel.get('title') == 'Top configured routes' for panel in grafana['panels'])
-# 0.27.0 compression/protocol/dashboard UX regressions.
+# 0.27.1 compression/protocol/dashboard UX regressions.
compression_source = rust_source(root, "src/compression.rs")
metrics_source = rust_source(root, "src/metrics.rs")
admin_source = rust_source(root, "src/admin/mod.rs")
@@ -1324,7 +1373,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log"
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241
assert 'location.pathname === "/logging"' in settings_js_0241
-assert '"RuntimeInformation"' in openapi and '"version": "0.27.0"' in openapi
+assert '"RuntimeInformation"' in openapi and '"version": "0.27.1"' in openapi
# 0.24.1 compiler and hostname-cardinality regressions.
tls_0241 = rust_source(root, "src/tls.rs")
@@ -1369,5 +1418,5 @@ for schema_name in ["RedirectHost", "NotFoundHost"]:
node = openapi_0241["components"]["schemas"][schema_name]["properties"]["frontend"]["properties"]["hosts"]
assert "maxItems" not in node, schema_name
-print("PASS: 0.27.0 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v8 migrations.")
+print("PASS: 0.27.1 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v8 migrations.")
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
diff --git a/scripts/deploy_core.py b/scripts/deploy_core.py
index afc6f8c..cc3b834 100755
--- a/scripts/deploy_core.py
+++ b/scripts/deploy_core.py
@@ -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.0'})
+ request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.27.1'})
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.')
@@ -316,7 +316,7 @@ WorkingDirectory={data}
UMask=0077
Environment=RUST_LOG=rust_proxy=info,warn
Environment=MALLOC_ARENA_MAX=2
-Environment=PATH=/opt/rustproxy-certbot/bin:/usr/local/bin:/usr/bin:/bin
+Environment=PATH=/usr/local/bin:/usr/bin:/bin
EnvironmentFile=-/etc/rustproxy/environment
ExecStart={args}
Restart=on-failure
diff --git a/scripts/docs_check.py b/scripts/docs_check.py
index bd39f2f..e77e5b8 100755
--- a/scripts/docs_check.py
+++ b/scripts/docs_check.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-"""Rust-independent documentation/Reader consistency checks for the 0.27.0 release."""
+"""Rust-independent documentation/Reader consistency checks for the 0.27.1 release."""
from pathlib import Path
import re
from rust_source import rust_source
@@ -14,6 +14,7 @@ required = [
DOCS / "README.md",
DOCS / "WEB_SERVER.md",
DOCS / "TODO.md",
+ DOCS / "V0.27.1_IMPLEMENTATION.md",
DOCS / "V0.27.0_IMPLEMENTATION.md",
DOCS / "V0.26.7_IMPLEMENTATION.md",
DOCS / "V0.25.5_AUDIT.md",
@@ -57,16 +58,16 @@ assert '"/docs/content.json"' in admin
readme = (ROOT / "README.md").read_text(encoding="utf-8")
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
-assert readme.startswith("# RustProxy 0.27.0")
+assert readme.startswith("# RustProxy 0.27.1")
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
-assert roadmap.startswith("# RustProxy roadmap") and "## 0.27.0" in roadmap and "## 0.26.7" in roadmap and "## 0.26.6" in roadmap and "## 0.26.5" in roadmap and "## 0.26.4" in roadmap and "## 0.26.0" in roadmap and "## 0.25.5" in roadmap and "## 0.25.4" in roadmap and "## 0.25.3" in roadmap and "## 0.25.2" in roadmap and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap
+assert roadmap.startswith("# RustProxy roadmap") and "## 0.27.1" in roadmap and "## 0.27.0" in roadmap and "## 0.26.7" in roadmap and "## 0.26.6" in roadmap and "## 0.26.5" in roadmap and "## 0.26.4" in roadmap and "## 0.26.0" in roadmap and "## 0.25.5" in roadmap and "## 0.25.4" in roadmap and "## 0.25.3" in roadmap and "## 0.25.2" in roadmap and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap
assert "## 0.24." not in roadmap and "## Web Server follow-up" in roadmap and "docs/ROADMAP_HISTORY.md" in roadmap
assert changelog.startswith("# Changelog\n")
-for release in ["0.27.0", "0.26.7", "0.26.6", "0.26.5", "0.26.4", "0.26.1", "0.26.0", "0.25.6", "0.25.5", "0.25.4", "0.25.3"]:
+for release in ["0.27.1", "0.27.0", "0.26.7", "0.26.6", "0.26.5", "0.26.4", "0.26.1", "0.26.0", "0.25.6", "0.25.5", "0.25.4", "0.25.3"]:
assert f"## {release}" in changelog, release
-assert changelog.index("## 0.27.0") < changelog.index("## 0.26.7") < changelog.index("## 0.26.6") < changelog.index("## 0.26.5") < changelog.index("## 0.26.4") < changelog.index("## 0.26.1") < changelog.index("## 0.26.0") < changelog.index("## 0.25.6") < changelog.index("## 0.25.5")
+assert changelog.index("## 0.27.1") < changelog.index("## 0.27.0") < changelog.index("## 0.26.7") < changelog.index("## 0.26.6") < changelog.index("## 0.26.5") < changelog.index("## 0.26.4") < changelog.index("## 0.26.1") < changelog.index("## 0.26.0") < changelog.index("## 0.25.6") < changelog.index("## 0.25.5")
assert "CHANGELOG_TECHNICAL_ARCHIVE.md" in changelog
for developer_only in ["Clean the remaining compiler warnings", "Update package/source/UI/OpenAPI/release identity", "source regressions and update release identity"]:
assert developer_only not in changelog, f"developer-only CHANGELOG detail: {developer_only}"
diff --git a/scripts/preflight.py b/scripts/preflight.py
index 9e81149..77fd4d0 100755
--- a/scripts/preflight.py
+++ b/scripts/preflight.py
@@ -75,10 +75,10 @@ def check(root: Path, repair_known: bool = False) -> list[str]:
raise ValueError(f"Conflicting Rust modules: {flat.relative_to(root)} and {module.relative_to(root)}")
for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/proxy/websocket.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/admin/web/drafts.js", "src/pages.rs", "scripts/mock_server.py", "scripts/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.5_AUDIT.md", "docs/V0.25.4_AUDIT.md", "docs/V0.25.3_AUDIT.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "scripts/docs_check.py", "scripts/web_server_contracts.py", "scripts/rust_source.py", "scripts/rust_module_contracts.py", "scripts/source_module_contracts.py", "scripts/acme_provider.py", "src/basic_auth.rs", "scripts/deploy.py", "scripts/deploy_core.py", "scripts/deploy_runtime.py", "scripts/migrate_npm.py", "scripts/migrate_npm_core.py", "scripts/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json"]:
if not (root / path).is_file():
- raise ValueError(f"Incomplete RustProxy 0.27.0 source tree: missing {path}")
+ raise ValueError(f"Incomplete RustProxy 0.27.1 source tree: missing {path}")
text = (root / "src/lib.rs").read_text(encoding="utf-8")
if '#[path = "auth/mod.rs"]' not in text:
- raise ValueError("src/lib.rs is not the 0.27.0 entry point; extract the complete package, not individual files.")
+ raise ValueError("src/lib.rs is not the 0.27.1 entry point; extract the complete package, not individual files.")
return messages
diff --git a/scripts/release_smoke.py b/scripts/release_smoke.py
index 91fa8ff..e092f08 100755
--- a/scripts/release_smoke.py
+++ b/scripts/release_smoke.py
@@ -123,7 +123,7 @@ def upgrade_smoke(tmp: Path) -> None:
assert backup.is_dir()
assert (backup / "src/obsolete.rs").is_file()
assert not (target / "src/obsolete.rs").exists()
- assert 'version = "0.27.0"' in (target / "Cargo.toml").read_text(encoding="utf-8")
+ assert 'version = "0.27.1"' in (target / "Cargo.toml").read_text(encoding="utf-8")
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
assert (target / "langs/site.json").is_file()
@@ -134,7 +134,7 @@ def upgrade_smoke(tmp: Path) -> None:
assert (target / "docker-agent/certificate.go").is_file()
assert (target / "docker-agent/server.go").is_file()
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
- assert manifest["to_version"] == "0.27.0"
+ assert manifest["to_version"] == "0.27.1"
assert manifest["removed_obsolete_runtime_state"] is True
assert not legacy.exists()
assert not (backup / "legacy-data").exists()
@@ -403,8 +403,37 @@ def ui_contract_smoke() -> None:
assert marker in vhosts, marker
assert 'rustproxy-backend-table-v1' not in vhosts and 'poolTablePreferenceKey' not in vhosts
assert 'window.rustProxyPreferences.update({' in vhosts and 'backend_table_sort' in vhosts and 'backend_table_descending' in vhosts
+ for marker in ['id="vhost-sort"', 'id="vhost-sort-direction"', 'id="docker-vhost-sort"', 'id="docker-vhost-sort-direction"', 'id="redirect-sort"', 'id="redirect-sort-direction"', 'id="notfound-sort"', 'id="notfound-sort-direction"', 'id="stream-sort"', 'id="stream-sort-direction"']:
+ assert marker in html, marker
+ preferences_source = (ROOT / 'src/admin/preferences.rs').read_text(encoding='utf-8')
+ for marker in ['vhost_list_sort', 'docker_vhost_sort', 'redirect_list_sort', 'notfound_list_sort', 'stream_list_sort']:
+ assert marker in preferences_source, marker
+ assert 'vhost_list_sort' in vhosts and 'vhost_list_descending' in vhosts
+ list_sort_sources = {
+ 'docker': (ROOT / 'src/admin/web/docker.js').read_text(encoding='utf-8'),
+ 'redirects': (ROOT / 'src/admin/web/redirects.js').read_text(encoding='utf-8'),
+ 'notfound': (ROOT / 'src/admin/web/host404.js').read_text(encoding='utf-8'),
+ 'streams': (ROOT / 'src/admin/web/streams.js').read_text(encoding='utf-8'),
+ }
+ assert 'docker_vhost_sort' in list_sort_sources['docker'] and 'sortServiceRows' in list_sort_sources['docker']
+ assert 'redirect_list_sort' in list_sort_sources['redirects']
+ assert 'notfound_list_sort' in list_sort_sources['notfound']
+ assert 'stream_list_sort' in list_sort_sources['streams']
for marker in ['id="pool-search"', 'id="pool-sort"', 'data-pool-column-toggle="vhost"']:
assert marker in html, marker
+ for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'id="about-python-runtime"', 'id="runtime-python"', 'id="acme-runtime-components"']:
+ assert marker in html, marker
+ for language in ['langs/en_US.json', 'langs/pl_pl.json']:
+ text = (ROOT / language).read_text(encoding='utf-8')
+ assert './dev.sh tls deploy' not in text
+ assert '/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy' in text
+ startup_source = (ROOT / 'src/main/admin_runtime.rs').read_text(encoding='utf-8')
+ for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'join("acme-python")', 'certificate_runtime']:
+ assert marker in startup_source, marker
+ for document in ['docs/RENEWAL.md', 'docs/INSTALL.md', 'docs/API.md']:
+ document_text = (ROOT / document).read_text(encoding='utf-8')
+ assert './dev.sh tls deploy' not in document_text
+ assert '/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy' in document_text
style = (ROOT / "src/admin/web/style.css").read_text(encoding="utf-8")
for marker in ['--scrollbar-thumb', '*::-webkit-scrollbar-thumb', '.backend-table-tools', '.vhost-quick-toggle', '.dashboard-widget-toggle', '--toolbar-control-height: min(var(--button-min-height), var(--ui-field-min-height))', 'height: var(--toolbar-control-height)']:
assert marker in style, marker
@@ -428,6 +457,10 @@ def ui_contract_smoke() -> None:
assert 'id="certificate-dialog"' not in html and 'id="certificate-close"' not in html
for marker in ['function selectTab(name, focus = false, persist = true)', 'log_directories', 'certbotLogs', 'value?.kind?.startsWith("renewal")']:
assert marker in acme_ui, marker
+ for marker in ['id="acme-runtime-card"', 'id="acme-runtime-message"', 'id="acme-runtime-state"', 'id="acme-runtime-path"', 'rust-proxy --data-dir ./data admin setup-token', 'rust-proxy --data-dir /var/lib/rustproxy admin setup-token']:
+ assert marker in html, marker
+ for marker in ['function renderRuntime(value)', 'runtimeAvailable', 'certificateRuntimeUnavailable']:
+ assert marker in acme_ui, marker
for marker in ['.certificate-operation-card', '.renewal-meta', '#panel-tls .certificate-tabs']:
assert marker in css, marker
login = (ROOT / "src/admin/web/login.js").read_text(encoding="utf-8")
@@ -571,7 +604,7 @@ def source_contract_smoke() -> None:
assert marker in admin, marker
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
assert '"/api/status"' not in anonymous_block
- assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.27.0"' in openapi_0241
+ assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.27.1"' in openapi_0241
assert 'id="panel-logging"' not in html
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
assert marker in html, marker
@@ -946,6 +979,11 @@ def source_contract_smoke() -> None:
assert '/Ubuntu%2014.04%20LTS/' in acl and 'repeated_slashes_are_normalized_for_routing' in acl and 'percent_encoded_spaces_are_valid_request_paths' in policies and 'repeated_slashes_route_normally_but_reach_upstream_unchanged' in policies and 'local_location_response_uses_response_rules_without_opening_backend' in policies and 'local_location_response_still_respects_route_acl' in policies
db_source = rust_source(ROOT, "src/db.rs")
acme_source = rust_source(ROOT, "src/acme.rs")
+ acme_runtime = (ROOT / "src/acme/runtime_env.rs").read_text(encoding="utf-8")
+ assert 'runtime_setup_once' in acme_source
+ assert 'include!("acme/runtime_env.rs")' in (ROOT / 'src/acme.rs').read_text(encoding='utf-8')
+ for marker in ['CERTBOT_PACKAGES', 'join("acme-python")', 'virtualenv>=20,<21', 'distro_certbot_available', 'acme.runtime_python_missing', 'acme.runtime_pip_missing']:
+ assert marker in acme_runtime, marker
assert "SCHEMA_VERSION: i64 = 8" in db_source and "certificate_renewals" in db_source and "0004_admin_preferences.sql" in db_source and "0005_certificate_api.sql" in db_source and "0006_certificate_api_credentials.sql" in db_source and "0007_secret_store.sql" in db_source and "0008_config_history.sql" in db_source
runtime_source = rust_source(ROOT, "src/runtime.rs")
assert 'replace_as_restore' in db_source and 'config.restore' in db_source and 'source_revision' in db_source
@@ -955,6 +993,8 @@ def source_contract_smoke() -> None:
for marker in ["pub kind: &'static str", "pub log_directory: String", "pub(crate) fn certbot_log_directory", 'kind: "issue"', '"renewal-dry-run"']:
assert marker in acme_source, marker
assert '"log_directories":log_directories' in admin and 'crate::acme::certbot_log_directory' in admin
+ assert '"runtime":state.acme.runtime_view()' in admin
+ assert '"certificate_runtime": state.acme.runtime_view()' in rust_source(ROOT, 'src/admin/status.rs')
assert "store.load_certificate_renewals().await?" in main and "state.acme.restore_renewals(renewal_observations)" in main
assert 'accept-remote-admin-risk' in main and 'hide = true' in main
settings_rust = rust_source(ROOT, 'src/settings.rs')
@@ -1047,7 +1087,7 @@ def source_contract_smoke() -> None:
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
assert '"/api/config/history/restore"' in openapi
- for marker in ['"version": "0.27.0"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
+ for marker in ['"version": "0.27.1"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
assert marker in openapi, marker
assert '"software_update"' in openapi and '"update_available"' in openapi
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
@@ -1120,12 +1160,13 @@ def container_contract_smoke() -> None:
"cargo generate-lockfile",
"cargo build --locked --release",
"FROM debian:trixie-slim AS runtime",
- "python3 -m venv /opt/rustproxy-certbot",
- "certbot==5.8.0",
- "certbot-dns-cloudflare==5.8.0",
- "certbot-dns-ovh==5.8.0",
- "certbot-dns-rfc2136==5.8.0",
- "PATH=/opt/rustproxy-certbot/bin:",
+ "python3-venv",
+ "python3-pip",
+ "certbot",
+ "python3-certbot-dns-cloudflare",
+ "python3-certbot-dns-ovh",
+ "python3-certbot-dns-rfc2136",
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"USER rustproxy:rustproxy",
'VOLUME ["/var/lib/rustproxy"]',
]:
@@ -1211,7 +1252,7 @@ def main() -> None:
source_contract_smoke()
container_contract_smoke()
grafana_contract_smoke()
- print("PASS: 0.27.0 SQLite/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
+ print("PASS: 0.27.1 SQLite/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
if __name__ == "__main__":
diff --git a/scripts/swagger_assets.py b/scripts/swagger_assets.py
index e8703d7..d0d2ea1 100755
--- a/scripts/swagger_assets.py
+++ b/scripts/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.0"})
+ request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.27.1"})
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/upgrade.py b/scripts/upgrade.py
index b74eecb..08a0204 100755
--- a/scripts/upgrade.py
+++ b/scripts/upgrade.py
@@ -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.0-" + uuid.uuid4().hex[:12])
+ backup = target / ".upgrade-backup" / ("before-0.27.1-" + 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.0", "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.1", "managed_paths":MANAGED, "removed_obsolete_runtime_state": remove_legacy}, indent=2)+"\n", encoding="utf-8")
if remove_legacy:
legacy.unlink()
except BaseException:
@@ -115,7 +115,7 @@ def main() -> None:
backup = upgrade(Path(__file__).resolve().parents[1], 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.0. Previous managed source files: {backup}")
+ print(f"Installed RustProxy 0.27.1. 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/web_server_contracts.py b/scripts/web_server_contracts.py
index fd9a2ca..7caf111 100755
--- a/scripts/web_server_contracts.py
+++ b/scripts/web_server_contracts.py
@@ -96,7 +96,7 @@ need(web,
'HeaderValue::from_static("no-store")',
"builtin_error_response(StatusCode::FORBIDDEN, &method)",
"empty_index_can_go_directly_to_directory_listing",
- "RustProxy 0.27.0",
+ "RustProxy 0.27.1",
"traversal_and_symlink_escape_are_not_served",
"try_files_rewrites_cache_rules_headers_and_downloads_work",
"precompressed_sidecars_keep_mime_validators_and_range_semantics",
diff --git a/src/acme.rs b/src/acme.rs
index 8ac6858..7e80bd7 100644
--- a/src/acme.rs
+++ b/src/acme.rs
@@ -3,6 +3,7 @@
//! travel through argv, logs or API responses.
include!("acme/model.rs");
+include!("acme/runtime_env.rs");
include!("acme/providers.rs");
include!("acme/jobs.rs");
#[cfg(test)]
diff --git a/src/acme/model.rs b/src/acme/model.rs
index 58d18e0..3912e12 100644
--- a/src/acme/model.rs
+++ b/src/acme/model.rs
@@ -6,13 +6,8 @@ use base64::Engine as _;
use http::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json::Value;
-use std::{
- path::PathBuf,
- process::Stdio,
- sync::{Arc, Mutex},
- time::Duration,
-};
-use tokio::{io::AsyncWriteExt, process::Command, sync::Semaphore};
+use std::sync::{Arc, Mutex};
+use tokio::sync::Semaphore;
use zeroize::{Zeroize, Zeroizing};
#[derive(Debug, Clone, Serialize)]
@@ -229,6 +224,8 @@ fn provider_credential_bytes(
pub struct Manager {
slot: Arc,
+ runtime_slot: Arc,
+ runtime: Mutex,
job: Mutex
-
+
-
-
-
-
+
+
+
+
@@ -3439,7 +3567,7 @@ max_execution_time = 30
r/p
-
RustProxy0.27.0
+
RustProxy0.27.1
@@ -3450,12 +3578,24 @@ max_execution_time = 30
License
Open Source
+
+
ACME mode
+
-
+
+
+
Certificate Python runtime
+
-
+
RustProxy is an open-source project. Its source code is publicly
available for inspection, development and community contribution.
+
Source
+ target="_blank">Official source
repositorygit.linuxiarz.pl/gru/RustProxy
+ Official package
+ repositoryrustproxy.linuxiarz.pl
diff --git a/src/admin/web/login-keys.json b/src/admin/web/login-keys.json
index 2b569b9..e61a130 100644
--- a/src/admin/web/login-keys.json
+++ b/src/admin/web/login-keys.json
@@ -20,8 +20,9 @@
"password",
"passwordMismatch",
"setupHelp",
+ "setupCommandExample",
"setupTitle",
"setupToken",
"signIn",
"username"
-]
\ No newline at end of file
+]
diff --git a/src/admin/web/login.html b/src/admin/web/login.html
index c2b9854..5ebed57 100644
--- a/src/admin/web/login.html
+++ b/src/admin/web/login.html
@@ -23,7 +23,9 @@
Create the first administrator using a one-time local
- capability.