diff --git a/AGENTS.md b/AGENTS.md index f5453ec..e8aecc5 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.1. +compatibility. Current source version: 0.27.2. ## Delivery diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bfa6d6..6112be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ > test-only changes and packaging implementation notes were moved to > [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md). +## 0.27.2 + +- Fix managed ACME issuance/renewal after the 0.27.1 private-Python change: Certbot is now launched through the final managed interpreter (`acme-python/bin/python -m certbot`) instead of the pip console script whose absolute shebang still referenced the temporary `acme-python.installing` path after the atomic directory rename. +- Validate managed Certbot through the same module-based launch path, so already-created 0.27.1 environments recover immediately after upgrade without deleting or rebuilding `/var/lib/rustproxy/acme-python`. +- Keep the distribution Certbot fallback unchanged for systems where the managed environment cannot be used. +- Keep SQLite schema 8 and the saved configuration format unchanged. + ## 0.27.1 - Show ready-to-run first-administrator setup-token commands instead of requiring a `$DATA_DIR` placeholder substitution. diff --git a/Cargo.lock b/Cargo.lock index 3d3f5fc..7dfe58c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1688,7 +1688,7 @@ dependencies = [ [[package]] name = "rust-proxy" -version = "0.27.1" +version = "0.27.2" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index a600bbe..e7d56d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-proxy" -version = "0.27.1" +version = "0.27.2" edition = "2021" rust-version = "1.88" description = "RustProxy - independent reverse proxy with local administration" diff --git a/README.md b/README.md index ca92630..647812f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# RustProxy 0.27.1 +# RustProxy 0.27.2 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.1 release source.** +**0.27.2 release source.** ## Layer-4 streams @@ -11,16 +11,14 @@ 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.1 +## What changed in 0.27.2 -- 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. +- Fix ACME issuance and renewal with the RustProxy-managed Python environment introduced in 0.27.1. +- Run managed Certbot through the final venv interpreter (`acme-python/bin/python -m certbot`) instead of the pip console script whose shebang still points at the temporary `acme-python.installing` directory after the atomic rename. +- Validate the managed runtime through the same module launch path, so an existing 0.27.1 `acme-python` directory starts working immediately after upgrade; no manual venv deletion/rebuild is required. +- Keep the distribution Certbot fallback, SQLite schema 8 and saved configuration format unchanged. -0.27.1 retains the 0.27.0 proxy/compression and Docker-publication behavior. Historical details are in [CHANGELOG.md](CHANGELOG.md). +0.27.2 retains the 0.27.1 first-admin, managed-runtime UI and route-list sorting changes plus the 0.27.0 proxy/compression and Docker-publication behavior. Historical details are in [CHANGELOG.md](CHANGELOG.md). ### Historical 0.26.7 release notes @@ -88,7 +86,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.1 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.2 verification status is maintained in [docs/TEST_REPORT.md](docs/TEST_REPORT.md). ## Documentation @@ -168,7 +166,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.1 source archive preserves the resolved `Cargo.lock` supplied with the source tree and +The 0.27.2 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 +412,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.1.zip +python3 make_zip.py /tmp/RustProxy-0.27.2.zip ``` ## New management and test-origin guide diff --git a/ROADMAP.md b/ROADMAP.md index 6fdbfb2..7e66896 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,13 @@ # RustProxy roadmap +## 0.27.2 - Managed ACME renewal repair + +Current direction: 0.27.1 -> 0.27.2 + +- Make the managed Certbot runtime independent of pip console-script shebang relocation. +- Reuse existing 0.27.1 `acme-python` environments without operator cleanup. +- Preserve the system Certbot fallback and schema/config compatibility. + ## 0.27.1 - Portable certificate runtime and list sorting Current direction: 0.27.0 -> 0.27.1 diff --git a/SHA256SUMS b/SHA256SUMS index 1bb8af8..93be8f7 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,18 +1,18 @@ 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore -6479dfef0344de33714f8503944876a3641c8381e9a7ccaaa4faed06b65e67c6 AGENTS.md -59ff94da1b39a85b9738c8265ed3b871f483451871182d73c8a678509ee222c1 CHANGELOG.md -0b0ed48165c10747c1a2f524fbdb27488f3edc7ff0e6fdccc845625d885ef61c Cargo.lock -f6d19f4e9b1453cc3b80d47f6cb27870b205f9aa4592f82a872c53da44e1308d Cargo.toml +4595fb01446f9e9e08a459085dea96c2b27a9f0aff0fab1f40c8a62e95761902 AGENTS.md +49f5f9b0490a8b411f16f8a1a2477e96f832d6d21155727bb80c4895f9877ea1 CHANGELOG.md +3002fea4dac2c78c11003b6ef669e53415702337062ab737958ac48471e24422 Cargo.lock +5823157f89f6d8576a038866ff5bc2be8733f226e345f4ff9be68d1b59d1d34c Cargo.toml 75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile -37da924319eef96622afb996a528989780a3733cc44eda7944e4f914a81eb70e README.md -fbeb203b4ca4f02c9d35c02561cd9c098bdd2bf58688ea7039e987e24ee8e8e1 ROADMAP.md +3ef21bb67c050fb7189a032e55e4794c4e07a1b20bcdc3f2f6222f5a37fa8fb5 README.md +e62a31c08cc123b39031e95d10d149d36dbccf73a78a18a7578bb88521c89983 ROADMAP.md da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml 928ba6a504bc431ae100c6e587861e5088b28822d2cb19da5d437b39d26c864a build.rs 385ac7970fa94f043d0bcf04a91184e6e2c807587b85e55517ed064bb4bddba6 deploy/README.md 31d4e0cd454060b46f359fdb84a14b16a5fe0d3baabb387dd584d76e555558bb deploy/rustproxy.service -18121e6fb922fb0e8d8b582486cb029256b305bd0f14bb989f9a16484b619674 dev.sh +c90c8a484ff15bb516a810c01e19260ba941b652d5d67afc4405f444ab3b06ab 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 -dcd840bb3fd542379d3554ddc78e597c64bff22a4bd832b466f0db867840c17e docker-agent/model.go +4e9eb90294d936e0e4f63618bbadd54b8063ec73d4beda9aa4d70d768b16ab97 docker-agent/model.go e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go 634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service 5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go 875faa3969df9409d79f050a84b4183496f557edda3d34aaec19fe556cf8c471 docs/ACME_PROVIDERS.md ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md c7b75ed137094cade2b2047c1c8d304e936e0fe1121ff91a858f77c2e969fa39 docs/API.md -386d1d9b120e05b8b776af87237294d1674ac6f37c6ca4fbd74368014d09c0d7 docs/API_ACCESS.md +d7b224425930cf06cf34d0547e445e0363f6ac4aced43e182d1d88faa2d28cdb docs/API_ACCESS.md 0f7f159ad5b4b5277f80c19f3c47f4e7823ea439ef02e5913e1391f1b34d129f docs/ARCHITECTURE.md 86469d17f9786c3e6f20c66791b47e10e11695d2f1a88c66c9ded157a585c85f docs/CACHE.md 6266163e17d2921468abdae92c1620266af8351321530e2c9b7dca6f3dd89c88 docs/CACHE_DIAGNOSTICS.md c6003dcbc0b95e2cfa9523b7017186cc8b4826248b588af77111ac5eb15174f9 docs/CHANGELOG_TECHNICAL_ARCHIVE.md -0031861b52c00fc1cffc73df1410c9dee5838d445699326deddc2488ad1193ad docs/CONTAINER.md +0a92a2d4ee88d02e9b460c680305680227c3ef82cf6b57d6f92b1ef5a6433154 docs/CONTAINER.md 9538a52d508d8d7053c1387c42eef60e6d5dc1879c300b6fe951b3374c16884c docs/CUSTOM_LOCATIONS.md 16d8f59908c66e595d6003495cbf5c068c68b10aa9495eb8612c38b2de0e4a7c docs/DOCKER_DISCOVERY.md 9ea8db4d034ecb573976dd323da29f044538525c230df9f43a47be1ac867b24c docs/ERROR_LOGGING.md -adb4c6c43023f09d291eee1027944453eb7667b4c702339346a75aa23c986b5a docs/FILE_LOGS.md +5c46b311f48e143a3e43e61a69c8bc3cfad7748c7dfef87bd76df333b64023bd docs/FILE_LOGS.md df6ecd4a50796640243676ad4b3a87789f6e5d20cbebea6255e92fbfff1c7131 docs/HTTP3.md -69b7ede915818a52bb68f94983f0fcefe77ff6c58d776c92758e2f7007678eab docs/INSTALL.md +10f378674dced022f063bfc1b15dc9a5cfc0afd03639deb4db5de091c139288c docs/INSTALL.md 3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md 4bb56add544f15d54695cf797cd16a51c09024360cdeef803a88988f4a54b3a5 docs/LIVE_LOGS.md 7c35c4cdbe7bbb81d94f3bf6aeb22b8a866af3e7f0d62bbedc299e4d6f264e0d docs/MANAGEMENT.md @@ -50,7 +50,7 @@ e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND 0c2241828e01b12f749562e2ac206a4519908fff63f46eba207b1cffa932dc5b docs/NPM_MIGRATION.md 712cb1cbf84515384b081fd2929e86e40af7c10f65bc69919b78b7b3e2fd12b5 docs/PHP_FPM.md 572c70fbd2f61fecd80a8ff9fa5de4a94c4f158de5e90da8e920f99b051241fa docs/PROMETHEUS.md -72a1cfc792f4cc35d3463c312a88bf0053689df0bb52d89889bb7ed15e35462b docs/README.md +a5faed54574e23cc5940205883a9731aa1572c2499783ecba138a621fcfb4a42 docs/README.md 6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md 0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md be661dbf81af840d4d8aeeb5347c24c7faecdecc73a8cf0f6f09053f1abe04c1 docs/RENEWAL.md @@ -60,7 +60,7 @@ be661dbf81af840d4d8aeeb5347c24c7faecdecc73a8cf0f6f09053f1abe04c1 docs/RENEWAL.m 620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md 632976125cbd9e96e91d1771331c73d1ac5adaa796476a20f8546971787c2529 docs/SETTINGS.md 3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md -6f1d1ced81d32c2efae10b0743e6fbd45648734439ce1adbc4a62baa07341dd8 docs/TEST_REPORT.md +6a6f48614fcf64b675ef0b7efb49f65878da3f42a6ee110f0d608c70e737be2a docs/TEST_REPORT.md cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md 8ee6187a5460ca45643b7fd789e27bffd264449e3a92ff70e9359edee011bbbb docs/TRANSFORM.md a1e9c6d38d21067d1ce7115ca3395ae8ff1f6a4c40fb0ebf00a6940ef79df439 docs/TRANSPORT.md @@ -97,6 +97,7 @@ dd9b5dcdbd53a04fd32458caaf03b56aa335f0153b1a0e332fdf230fec12680e docs/V0.26.6_I f9667fe81b31762e64d36126a7e94b609316dfa856de8f8e3b96661dd2a918c6 docs/V0.26.7_IMPLEMENTATION.md efd5935852d4cfd09758bf67c70e437490cfce2baade46c8733201e506dbcc42 docs/V0.27.0_IMPLEMENTATION.md 9ba6417417a0eb293879b26b019449f143e773d1482434bee88f5eaf1fe34b90 docs/V0.27.1_IMPLEMENTATION.md +6aafee24d7badabe00e23554c8c1ec868f586e09f2d915e9f48422639a7348fb docs/V0.27.2_IMPLEMENTATION.md bc2ac0117528ed4616c68cf7fe05cb1d9bbd36ecaa8573828cb3b3136923f265 docs/VHOSTS.md 09b60fb7b40db4d0139fa2a9e69a2aa9f28d4e61b8643521cfc7e936d7df8774 docs/VHOST_ACCESS.md 4026a4adeee70f98a96cc7aa8d8be16cda42ffb2e5890d38f1ba638134ab0675 docs/WEB_SERVER.md @@ -111,14 +112,14 @@ b26833cb4251f9390a44192ecc827ff7f052fd19016a5ccf7498c94503ab4ce3 examples/mock- 5a8f3a8a054abc89c6e78896b9fe2f903212199a24dc3d2d2bcbd0696a31bcde examples/transport.json d242de9614fbb52933b4accd32f4715ca94107abe4a3b1d7e088362b07a6ba60 examples/vhosts-balanced.json af2bdb1343a8141069e94ccd2c7f92574ad848437e2f9fdb5c7576a9a18ef4a5 examples/vhosts-private.json -9cab73d7de1f54bc9e9e8b889a6eeaf2c293ab454c93ce904e9f7f5c6b7c588c grafana/README.md -2e23d77477bd3eea895c2afe8c5e836559f435419c066e995dec9910c5bbb942 grafana/rustproxy-overview.json +cc99b14927c696adc17c9d0689041798f0a99cbc372b35a81652d6e38af69311 grafana/README.md +b6401012c55a9cdf7ca7ac5f90b4142c623230903877309e427142de1a81f9d0 grafana/rustproxy-overview.json fe1fc76bcb639cac0609a8969cd92725cfd77d87f1d8519756ef16825e04914a install-debian13.sh 858b39fa6886b71d20690bf369e76580922364b3cc7f0cf6ba9b040df9cd76e3 install.sh 29cada0936762f26495064d1524a668262ed729b21f20aebddc8b6e5cfea363b langs/README.md -55a50c4071a05e4c24fef7fe9da10c0d9a3d5aaf19bafdb39fbacfbaa0b953e8 langs/en_US.json -41707ded727ff98fc3672f69f7cb16e7950eca8efa248fd328108ca1b262c22e langs/pl_pl.json -95f980b8420d14addf02c4c0139986ba317239426a3c91a1a821df410147c2c4 make_zip.py +917efd1646d06ac34c5729ce5a569482d7512cf285dfc603bca24b3608d36e7c langs/en_US.json +bab5f124ee1e1e823fe310217560c2abaff8a07c9c23a4daf7ab0f60d766eb4b langs/pl_pl.json +300b8cbb49e44d6ce11a4445df01b1a7467b81acfafa9c2d2ba62b5a611501e4 make_zip.py e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql 5c62da30fa9db594a4d656c77ae676d3c388072b6a85f91170ec7d911d7cde43 migrations/0004_admin_preferences.sql @@ -133,15 +134,15 @@ ee50f38c037765fab742e888d4b9fc4d874c31c785c6064d4eca3d2c4d24d944 nagios/check_r 1465396cf1358067e9fe48b3d61f28fd4ac65c577b67c502950ce6b42ecbe3b3 nagios/check_rustproxy_latency 6e036f736a2b40feb78eec17bf34871771524190f283591d1c89e48e6d2271e6 nagios/check_rustproxy_queues 7458b2d74495e6fb1c1edeea4d5a793705476431dadb60627187111f91441e13 rust-toolchain.toml -edb6ee1445b01cd6f3a74a3a607f39fb107d411b49df028d2e0f4679e093de31 scripts/acme_provider.py +5d4ae015f1c8199c2ed02b34576b74ec6f2b3b7a90c8d7178e5070a850bb6e72 scripts/acme_provider.py e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/acme_rfc2136.py a5b4451e45ce975685772aa8409b64a57a20a7548466584c3ea7fb975e64729c scripts/admin_audit.py 20e83f8b6347b819d1d1ea3de5c691c36f0816954a4210b0d3f39557fefe1931 scripts/backup.py -4ab1fc13e7447e752c476bc8bfd15ad4745900bf2a40fc5469174aacbc3aaef6 scripts/check_source.py +6e656850ca0bafae0aeb2fdf3b8f7e9342b027830df8e4ca19190a8b1cfeb7a9 scripts/check_source.py 187ee92c8029cdd59e89b4447cf4d170e856017a2091ceb85ae7c22dd1a79c72 scripts/deploy.py -11321aa4c5744dd2a3322123c00182e9565891e3b2742cda5bf3fc437b3d0c60 scripts/deploy_core.py +6960ff75a235289d270a9d8e7205a4432b51d3bca83e2c599bd20bcc90caac81 scripts/deploy_core.py f014472588e889488976970dbaa95a8745b6907516aa81a7cca21d0f81eaaad3 scripts/deploy_runtime.py -f818039f8a2e35922596122cde671a5e1af264b03d48938a3ca8261ec3c8e431 scripts/docs_check.py +fc1a74246478cb477b3892812634cb28f8b8ad45604a098327f7a14a60f25c96 scripts/docs_check.py d391681dc3f6786552ca44929979286a5c76a2b86785635871b59d2eb4d73389 scripts/langs_check.py 2e35b7c78887fd56f83ca9017caa168630963a9f47da71dfc53b1a8f9ad436c2 scripts/memory_contracts.py 2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migrate_npm.py @@ -151,8 +152,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 -f7474501b9ab7102d9f9b51c6d62c83dac3e6111e982f12e13480bcfd0b1c909 scripts/preflight.py -3019322686f3eccb141a9587b5bff78aabf46fb2ef766d3ddf1de274fea4dc8e scripts/release_smoke.py +65ee67092955aae5216c304453f88066ad9f51dc27e682f36866540eebbf621b scripts/preflight.py +1df736690e3de94f1ddc363ecef9e0e86acee4e98a78c30b82936324f4747484 scripts/release_smoke.py 45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 scripts/routeros-oci-build/.env.routeros.example 3b48cff8506e5a6ae7d3876f47721f5de7d63e2e02264a78cf5b6ddd52d9fbc6 scripts/routeros-oci-build/Dockerfile.routeros 7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e scripts/routeros-oci-build/Dockerfile.routeros.dockerignore @@ -164,11 +165,11 @@ ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed scripts/rust_s 1c83054336b3508d7ad72cd259f039394682642439877fe69639c0b76be09154 scripts/security_contracts.py 295b6f2625b1875950ea3eecccd142f49be327afdd00c92641286e6de530ec51 scripts/security_dependency_contracts.py 6fefb5c459aacdef3dcabe9c5e6e7718a2145a0190b165c29d7a46216ac53bad scripts/source_module_contracts.py -6c1b64db378fd6ab351882ecf91d7be3db7114b6650a88d402408b34794260cc scripts/swagger_assets.py +df2bffcd729a998c5c7ec5452235cc07e834d8f86017d65ccd0b1754d91d11b0 scripts/swagger_assets.py 538b5f6cfa738355a729940ee3d92e7ab3dc25ca69ae330ddaa08f831775b8ba scripts/test_compression.py -19bfa4dca422cebe8b41d9374f2e5ad480a34228995eddcf12acfc21e0ebed6f scripts/upgrade.py +45a6a4cca69419da94b2389d73d3f1eb42ccc4aafa5825da14242eb236bdd963 scripts/upgrade.py 0aaa80453e25aae7ea953fadb91b3ae3eaff627977c81c9ab5a2779f9a93582f scripts/web_assets.py -71bc04c6d71ce14362c7668e6798e0f13f2fda052a081ae52d04d7845e208adb scripts/web_server_contracts.py +12d2e324b2685f2d20d7bde4b68d2cb1565a3b5aff49e30850238e05d57bff9f scripts/web_server_contracts.py c245f8f832bd2c36a3fbd5e66fe98e7367c70a8db840c79e6b640f1d9d74f2ac scripts/web_service_ui_test.mjs ad62f73bb42906d617cc1b479f02d4648c7324cb89fc11d2c6aa6bf13774eec9 src/acl.rs ea272433e7f92e983509a8ab1aa54242440abef21df526c2f99944ef279e09b9 src/acl/tests.rs @@ -177,20 +178,20 @@ ea272433e7f92e983509a8ab1aa54242440abef21df526c2f99944ef279e09b9 src/acl/tests. 3f158f46e80b403b658b4e9b1e3710e38c70e47b2d476b8223d9c394ffeeba95 src/acme/model.rs c5334de6c732d76b2bea7bfc17b3ac305a250bbaad31a5420d6a7d7abb200449 src/acme/providers.rs b9e57db4bd80ef37564dfc533a96897d8a494671d53acea9fb4bf28d23ba370d src/acme/providers/tests.rs -a5dad04694780f0c6da3c277a110930f3c2ed925fc122d4be770678055931017 src/acme/runtime_env.rs +f8c4bb96814f669be8be40d013a6217c32f66943de0961ad827a184be6879645 src/acme/runtime_env.rs 7acbaf34e7b953b753ab074893e47b5024f0cd756dc4c7a1074067dd936c2e8e src/acme/tests.rs 55ef7ceb44c25c7a8ddcbfd5e0d4b647526f9bdcde901ee08db5314e4d055b39 src/admin/access.rs d18efa3f797f62b3916265e30ba546725a41e7ccfd9b123965ff9f522e3baa88 src/admin/certificates.rs 1de4c7c116eeab2452ccccbaba3f2b7b63c6eb1c1e146ab870d19c5a5ca16f80 src/admin/documentation.rs -9cac4393318cd34afcfdd02527291aaeb8eb373d38f9b219f71cb963c6d8e883 src/admin/documentation/documents.rs +e8892cca11b3d686d1e5f6ef091a01d7b28cf4289993876a0a4d91bedb751ec4 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 -dd40e2e8f1cac463532b113f91b4474a97a118fcd96aa27ee9f26bd78821064b src/admin/openapi.json +01bde7e69062ad85449a03c05188c8af4299952921993e9828f9c03439cff104 src/admin/openapi.json a53309daf118381031122ca70764f7a2f92adc44af3fc91df5f946e6856f76ae src/admin/operations.rs -a01160fd4f6743a9e8c1aa2e03ddf1514ff68a192ac9523c25be09252d2ac7a4 src/admin/preferences.rs +5287fff6b3ad436ac695e9b66feb1b0b5705b744a9f00cec7ea6a3e951e5eebf src/admin/preferences.rs b4fa393b780b2676b5d72e0f90cd7290292f424b8062d4b5983e722d6ca72c32 src/admin/public.rs 684d5d28e0d2b10fb2baf2a56857b226d6334cc3346e1ca86de173ff914bee73 src/admin/router.rs 333d5f9f4c27941c535ac689e5826264ffa2bb86b00832b597a9bb9f811f01e8 src/admin/session.rs @@ -226,7 +227,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/ 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg af6e7a2e890df2fb7385a374bcb3fba52fb5a53dbe18adc1fb7865660fcba842 src/admin/web/file-logs.js b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js -8f306bfa5635dd126a965125e11179f5475b2feec3471f36b460eb67c571bb19 src/admin/web/index.html +9e236b9a2e9505a0eda738e4cec92efb57b1db60288499e0bf797cb67df7eecf src/admin/web/index.html 24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js 7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json af0fbd94d9b88ce1cce28e39dfd2c35de031adcba412dd4a9a17d9b416c256dd src/admin/web/login.html @@ -235,7 +236,7 @@ e169d77359785ba604ce1a728420571fdc9b491fc1c0088642399466fbed8c1b src/admin/web/ 90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.js 20670b3167a56e0c79b061ea1b9af74a0d1e4dedb4b57cb8c211463610df28eb src/admin/web/settings.js bb67e77af5514478485cfb69f339d63d7eb0cbadd093fa054814a416260ccc1d src/admin/web/streams.js -2a2543859506ac69b41e706c3e2eeb4b500b054c7302a902f71e598cc67d9007 src/admin/web/style.css +625702e6e58ef6d78615909d02a0a236eee1a8b22cce451ca811ed30bb35967e 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 @@ -345,11 +346,11 @@ a16613114273c54b6d2a16a94fa8748550e91468585469642622bd2c86087baa src/logging/fo 17425ebbca6bbbb0dcaeb65ebc44b1e51488a4092b7b6add556e764c6d486d6a src/logging/tests.rs 3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs c66a9803e43bdd87101825e8c71ab33f890ac4c3428467ba5d43c17fdfa1439e src/main/admin_runtime.rs -0cc35b47d2264816009160e2416a4052d0118fee4c41c35d428dc088ee68a352 src/main/bootstrap.rs +04084e87167a8c47a24d5a4e89e0bde0f30398ea95c38fe34c057a1149ac33e8 src/main/bootstrap.rs 5a98d0e249338e73aa57ae2e352dc16db2f79c2d042c1544ce1c2022da76a31c src/main/proxy_runtime.rs 4dd7c1084a6214c0f2513ebf86fe8ef144470695557772bfe25979edd64491ab src/main/shutdown.rs 0a6578a0f4023b9f8d73043bf942dc5e096ef9afb8c0d2f131c14f65d08ac01d src/main/shutdown/tests.rs -a72eb4ae7ffecd5e1e566fbece270a7956bb3393d3fc18be108de959e65a99c0 src/main/tasks.rs +7e97938fd9209f2335af5d6304abcb4193e80bb33e4ccf480938e8c3ab4644d6 src/main/tasks.rs 49d7e104585c8791ac9a203dddd95802784b68c0ba65d6f90f3d57154b4c7871 src/metrics.rs 201ab702722d6d21585fe8bb49939150d61bfac7ed8dc198b98c2cc6098ffb5f src/metrics/global.rs 49b11df65b4fb0b5b727f9f867436584680b45e3f4fbd53f74bc0cab8b9a88ec src/metrics/vhost.rs @@ -456,7 +457,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 -fbc035f68621ac6f3c31e363712c4e06f9bc4382d94511bf93f0fdba39b627b2 src/web_server/fastcgi/protocol.rs +be75d0bf057a9b17d8c4a1cab67a1cbf349f542c8e57e4a822ebb9192b1c9582 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 @@ -466,14 +467,14 @@ fbc035f68621ac6f3c31e363712c4e06f9bc4382d94511bf93f0fdba39b627b2 src/web_server 80f946570c14c8915fd21bfdb1a84dfb6a14c633ab6060acb828fab4c7c21daa src/web_server/mod.rs 67ab8bef567452f123c4b9b46a942b39c93f1f765c128edca7ee808ff1c5e304 src/web_server/request.rs c5cb561b0f276e8082f904b9446fa5885f6b3c0608f25ea2ef425061e9f7f6b4 src/web_server/response.rs -bbd66ca7e3cab7b5a8b42402567b99fe060f19d513bb2de9f4423aaa14513d18 src/web_server/response/file.rs +51b0878fcf6e8ef2458f1b8ded65f843951c122f0ff0afe2854f467c5e192273 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 -bb09d550a0dacb6169ffabe2877f510e3a1b7ebd5a6bf527c0f19395c77b753e src/web_server/tests/part_02.rs +ef49a4230a1cb46c51866c4c0a49eae8c711377245df50c4f36a86693c040c1e 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/dev.sh b/dev.sh index 4544770..dfece4c 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.1 +RustProxy 0.27.2 ./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.1...\n' >&2 +printf 'Building RustProxy 0.27.2...\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 56d7d1f..73f6c68 100644 --- a/docker-agent/model.go +++ b/docker-agent/model.go @@ -1,6 +1,6 @@ package main -const version = "0.27.1" +const version = "0.27.2" type config struct { Listen string diff --git a/docs/API_ACCESS.md b/docs/API_ACCESS.md index 1ea3e61..186e159 100644 --- a/docs/API_ACCESS.md +++ b/docs/API_ACCESS.md @@ -1,4 +1,4 @@ -# Administration API access - 0.27.1 +# Administration API access - 0.27.2 ## Sign-in boundary diff --git a/docs/CONTAINER.md b/docs/CONTAINER.md index 791b26f..44e1906 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.1 . +docker build -t rustproxy:0.27.2 . ``` 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.1 + rustproxy:0.27.2 docker exec rustproxy rust-proxy admin setup-token ``` diff --git a/docs/FILE_LOGS.md b/docs/FILE_LOGS.md index 3932596..978cf77 100644 --- a/docs/FILE_LOGS.md +++ b/docs/FILE_LOGS.md @@ -1,6 +1,6 @@ # Stored log explorer -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. +RustProxy 0.27.2 provides an authenticated, read-only **Logs** workspace at `/logs`. The top bar has one **Logs** menu with separate **Stored logs** and **Live Logs** actions; the two data paths remain independent. ## Separation from Live Logs diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 90bd87c..8641b00 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,4 +1,4 @@ -# Installing and updating RustProxy 0.27.1 +# Installing and updating RustProxy 0.27.2 The scripts prepare a Linux/systemd installation. This source archive is **not a verified production build**: Rust/Cargo, real CA operations, live systemd startup diff --git a/docs/README.md b/docs/README.md index efb9702..6dcc2f2 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.2 implementation note](V0.27.2_IMPLEMENTATION.md) - managed Certbot relocation/renewal repair. - [0.27.1 implementation note](V0.27.1_IMPLEMENTATION.md) - first-admin command, managed/fallback certificate runtime and account-scoped route-list sorting. - [0.27.0 implementation note](V0.27.0_IMPLEMENTATION.md) - compression defaults/scoped policy, persistent Docker publications and visible cache occupancy meter fill. - [0.26.7 implementation note](V0.26.7_IMPLEMENTATION.md) - HOT/COLD occupancy meters, backend rotation continuity and container Certbot packaging. @@ -69,7 +70,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.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. +Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md); [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) is retained as the prior managed-runtime/list-sorting milestone; [V0.27.0_IMPLEMENTATION.md](V0.27.0_IMPLEMENTATION.md) is retained as the prior compression/Docker-publication milestone; [V0.26.7_IMPLEMENTATION.md](V0.26.7_IMPLEMENTATION.md) is retained as the prior cache/backend/container milestone; [V0.26.6_IMPLEMENTATION.md](V0.26.6_IMPLEMENTATION.md) is retained as the prior access-policy/administration milestone; [V0.26.5_IMPLEMENTATION.md](V0.26.5_IMPLEMENTATION.md) is retained as the prior interface-scaling/listener-navigation milestone, [V0.26.4_IMPLEMENTATION.md](V0.26.4_IMPLEMENTATION.md) as the prior administration/backend-lifecycle milestone, [V0.26.3_IMPLEMENTATION.md](V0.26.3_IMPLEMENTATION.md) as the prior appearance/UI milestone, [V0.26.2_IMPLEMENTATION.md](V0.26.2_IMPLEMENTATION.md) as the prior Logs/UI milestone and [V0.26.1_IMPLEMENTATION.md](V0.26.1_IMPLEMENTATION.md) as the prior protocol-attribution milestone. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain historical evidence. 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/TEST_REPORT.md b/docs/TEST_REPORT.md index 0881f19..d5beb41 100644 --- a/docs/TEST_REPORT.md +++ b/docs/TEST_REPORT.md @@ -1,18 +1,18 @@ -# Verification report - RustProxy 0.27.1 +# Verification report - RustProxy 0.27.2 ## Scope -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. +RustProxy 0.27.2 is a focused repair for managed ACME/Certbot execution while keeping SQLite schema 8 and the saved configuration format unchanged. -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`. +0.27.1 created the private environment under `/acme-python.installing`, installed Certbot there, validated it, and then renamed the directory to `/acme-python`. pip console scripts contain an absolute interpreter path, so `bin/certbot` could retain a shebang pointing at the removed staging directory and fail with `OSError` during issuance/renewal. -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. +0.27.2 validates and launches managed Certbot through the final venv interpreter with `python -I -m certbot`. Existing 0.27.1 environments can therefore be reused without deleting `/var/lib/rustproxy/acme-python`. The system/distribution Certbot fallback remains unchanged. -The 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. +The 0.27.1 first-administrator, runtime-status and route/list sorting behavior remains unchanged. ## Regression coverage -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. +Static/source contracts cover the managed runtime path and pinned package set, distro fallback markers and the new module-based Certbot launch path. A dedicated relocation regression reproduces the stale-shebang failure after renaming a venv and verifies that module execution through the final interpreter remains valid. Existing release-smoke, language/OpenAPI, source-module, security, web-server, browser-service and Docker-agent gates remain part of the Rust-independent verification set. ## Verification status in this workspace @@ -20,6 +20,7 @@ Rust/Cargo is not installed in this workspace. Rust-independent checks are run h | Check | Status | |---|---| +| managed ACME relocated-venv regression (`release_smoke.py`) | PASS | | changed administration JavaScript syntax (`node --check`) | PASS | | language/OpenAPI JSON parse | PASS | | `python3 scripts/langs_check.py` | PASS | @@ -41,4 +42,4 @@ Rust/Cargo is not installed in this workspace. Rust-independent checks are run h | `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.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. +The 0.27.2 source archive must not be described as compiler-verified or production-ready until the Rust-enabled release-host gates pass. Static/source contracts do not substitute for Rust parsing, type checking, Clippy, unit tests or real protocol runtime tests. diff --git a/docs/V0.27.2_IMPLEMENTATION.md b/docs/V0.27.2_IMPLEMENTATION.md new file mode 100644 index 0000000..89ca6d8 --- /dev/null +++ b/docs/V0.27.2_IMPLEMENTATION.md @@ -0,0 +1,27 @@ +# RustProxy 0.27.2 implementation note + +## Scope + +0.27.2 is a focused ACME runtime repair. It keeps SQLite schema 8 and the saved configuration format unchanged. + +## Managed Certbot relocation bug + +0.27.1 creates the private environment under `/acme-python.installing`, installs Certbot and its DNS plugins there, validates it, and then atomically renames the directory to `/acme-python`. + +Python virtual-environment console scripts created by pip contain an absolute interpreter path in their shebang. After the directory rename, `bin/certbot` can still point at the removed `acme-python.installing/bin/python`. Executing that console script can therefore fail with a local `OSError`; the private worker intentionally reduced such errors to `acme.local_io_or_json_error`, which hid the relocation detail from the UI. + +## Repair + +Managed-environment validation now runs Certbot as: + +```text +/acme-python/bin/python -I -m certbot --version +``` + +ACME issuance and renewal use the same final interpreter with `-m certbot`. The provider worker receives the managed interpreter path from RustProxy and no longer depends on the relocated pip console-script shebang. + +The existing system/distribution Certbot fallback continues to execute its discovered `certbot` program normally. + +## Upgrade behavior + +No ACME profile, account, certificate or credential migration is required. An `acme-python` environment created by 0.27.1 can be reused as-is because 0.27.2 bypasses the stale console-script shebang and imports Certbot from the environment's installed Python modules. diff --git a/grafana/README.md b/grafana/README.md index 84e1bfa..aca7cf9 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.1 metric names and a scrape interval around 15s. +dashboard expects RustProxy 0.27.2 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 7c09019..a5066b6 100644 --- a/grafana/rustproxy-overview.json +++ b/grafana/rustproxy-overview.json @@ -4529,9 +4529,9 @@ }, "timepicker": {}, "timezone": "browser", - "title": "RustProxy 0.27.1 - Operations", + "title": "RustProxy 0.27.2 - Operations", "uid": "rustproxy-0170", "version": 5, "weekStart": "", - "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." + "description": "RustProxy 0.27.2 operations dashboard including backend client lifecycle/retry diagnostics, bounded Traffic analytics, country and User-Agent-family visibility, and built-in Web Server traffic." } diff --git a/langs/en_US.json b/langs/en_US.json index c61e08d..6977280 100644 --- a/langs/en_US.json +++ b/langs/en_US.json @@ -183,7 +183,7 @@ "saveCertificate": "Apply certificate", "reloadCertificates": "Reload certificate files", "certificateHelp": "Upload a PEM pair, reference files already in data/certs, or deploy an externally issued certificate with the installed RustProxy binary. Reload re-reads configured files without changing the configuration revision.", - "certificateHint": "External Certbot hook (standard service install): rust-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", + "certificateHint": "External Certbot hook (standard service install): /opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert --cert /etc/letsencrypt/live/example/fullchain.pem --key /etc/letsencrypt/live/example/privkey.pem", "emptyCertificates": "No explicit SNI bindings. The generated default certificate is used while the TLS listener is enabled.", "certificateSafety": "Certificates secure TLS connections, not routing. Assign a certificate to a vhost. Changes affect new handshakes.", "wsActive": "WebSocket tunnels", diff --git a/langs/pl_pl.json b/langs/pl_pl.json index 3a110f3..fe66026 100644 --- a/langs/pl_pl.json +++ b/langs/pl_pl.json @@ -183,7 +183,7 @@ "saveCertificate": "Zastosuj certyfikat", "reloadCertificates": "Wczytaj pliki certyfikatów", "certificateHelp": "Wgraj parę PEM, wskaż pliki w data/certs albo wdrażaj certyfikat z zewnętrznego wystawcy zainstalowaną binarką RustProxy. Ponowne wczytanie odczytuje skonfigurowane pliki bez zmiany rewizji.", - "certificateHint": "Hook Certbota (standardowa instalacja usługi): rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert --cert /etc/letsencrypt/live/example/fullchain.pem --key /etc/letsencrypt/live/example/privkey.pem", + "certificateHint": "Hook Certbota (standardowa instalacja usługi): /opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert --cert /etc/letsencrypt/live/example/fullchain.pem --key /etc/letsencrypt/live/example/privkey.pem", "emptyCertificates": "Brak jawnych powiązań SNI. Aktywny listener TLS używa wygenerowanego certyfikatu domyślnego.", "certificateSafety": "Certyfikaty samopodpisane służą do testów. Certyfikat nie tworzy trasy. Zmiana dotyczy nowych połączeń TLS.", "wsActive": "Tunele WebSocket", diff --git a/make_zip.py b/make_zip.py index dcf1071..4dd125d 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.1.zip') + parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.27.2.zip') output = parser.parse_args().output.resolve() paths = files() checksum = ROOT / 'SHA256SUMS' diff --git a/scripts/acme_provider.py b/scripts/acme_provider.py index 0ca5a5f..4fbac07 100755 --- a/scripts/acme_provider.py +++ b/scripts/acme_provider.py @@ -165,8 +165,20 @@ def credentials(value: dict) -> bytes: return "".join(f"dns_rfc2136_{k} = {v}\n" for k, v in lines.items()).encode("ascii") -def certbot_command(value: dict, work: Path, credential_file: Path, executable: str) -> list[str]: - args = [executable, "certonly", "--non-interactive", "--agree-tos", "--no-eff-email", +def certbot_prefix(executable: str | None = None) -> list[str]: + if executable: + return [executable] + managed_python = os.environ.get("RUSTPROXY_CERTBOT_PYTHON") + if managed_python: + return [managed_python, "-I", "-m", "certbot"] + discovered = shutil.which("certbot") + if discovered: + return [discovered] + raise AcmeError("acme.certbot_not_installed") + + +def certbot_command(value: dict, work: Path, credential_file: Path, command_prefix: list[str]) -> list[str]: + args = [*command_prefix, "certonly", "--non-interactive", "--agree-tos", "--no-eff-email", "--email", value["email"], "--server", DIRECTORIES[value["environment"]], "--cert-name", "rustproxy", "--keep-until-expiring", "--key-type", "ecdsa", "--config-dir", str(work / "config"), "--work-dir", str(work / "work"), @@ -203,9 +215,7 @@ def read_generated(work: Path, name: str) -> bytes: def issue(raw: dict, certificate_directory: Path, executable: str | None = None) -> dict: value = validate(raw) - executable = executable or shutil.which("certbot") - if not executable: - raise AcmeError("acme.certbot_not_installed") + command_prefix = certbot_prefix(executable) certificate_directory = certificate_directory.absolute() private_directory(certificate_directory) root = certificate_directory / "acme" @@ -242,7 +252,7 @@ def issue(raw: dict, certificate_directory: Path, executable: str | None = None) temporary = work / ("profile-" + uuid.uuid4().hex) write_new(temporary, json.dumps(profile).encode()) os.replace(temporary, work / "profile.json") - args = certbot_command(value, work, credential_file, executable) + args = certbot_command(value, work, credential_file, command_prefix) try: result = subprocess.run(args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=1200 + value["propagation_seconds"], check=False) @@ -354,9 +364,7 @@ def renew(raw: dict, certificate_directory: Path, executable: str | None = None) Dry-run validates challenge access but never stages/deploys its test output. """ value = renewal_request(raw) - executable = executable or shutil.which("certbot") - if not executable: - raise AcmeError("acme.certbot_not_installed") + command_prefix = certbot_prefix(executable) certificate_directory = certificate_directory.absolute() private_directory(certificate_directory) work = profile_directory(certificate_directory, value["provider"], value["environment"], value["certificate_id"]) @@ -400,7 +408,7 @@ def renew(raw: dict, certificate_directory: Path, executable: str | None = None) installed_key = read_private(certificate_directory / value["key_file"], MAX_PEM) # Check dependencies/installed PEM before invoking a CA operation. old_dates = certificate_dates(installed_cert) - args = [executable, "renew", "--cert-name", "rustproxy", "--non-interactive", + args = [*command_prefix, "renew", "--cert-name", "rustproxy", "--non-interactive", "--no-random-sleep-on-renew", "--no-directory-hooks", "--config-dir", str(work / "config"), "--work-dir", str(work / "work"), "--logs-dir", str(work / "logs")] if value["dry_run"]: diff --git a/scripts/check_source.py b/scripts/check_source.py index 4128611..40b5b7a 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.1" +assert manifest["package"]["version"] == "0.27.2" 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.1"' in lock + assert 'name = "rust-proxy"\nversion = "0.27.2"' 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.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", + "README.md": "# RustProxy 0.27.2", + "CHANGELOG.md": "## 0.27.2", + "ROADMAP.md": "## 0.27.2", + "AGENTS.md": "Current source version: 0.27.2.", + "docs/TEST_REPORT.md": "# Verification report - RustProxy 0.27.2", }.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" @@ -287,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.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']: +for marker in ['"version": "0.27.2"', '"/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 @@ -613,12 +613,16 @@ 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 +for marker in ['"-I", "-m", "certbot", "--version"', 'RUSTPROXY_CERTBOT_PYTHON', 'managed_certbot_python']: + 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") backup_source = rust_source(root, "src/backup.rs") config_source = rust_source(root, "src/config.rs") acme_worker_source = (root / "scripts/acme_provider.py").read_text() +for marker in ['def certbot_prefix', 'RUSTPROXY_CERTBOT_PYTHON', '"-I", "-m", "certbot"', '[*command_prefix, "renew"', 'certbot_command(value, work, credential_file, command_prefix)']: + assert marker in acme_worker_source, marker for feature_marker in [ "AES_256_GCM", "master.key", "O_NOFOLLOW", "migrate_acme_credentials", "secret_store", "SecretMetadata", @@ -816,7 +820,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.1 compression/protocol/dashboard UX regressions. +# 0.27.2 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") @@ -1373,7 +1377,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.1"' in openapi +assert '"RuntimeInformation"' in openapi and '"version": "0.27.2"' in openapi # 0.24.1 compiler and hostname-cardinality regressions. tls_0241 = rust_source(root, "src/tls.rs") @@ -1418,5 +1422,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.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("PASS: 0.27.2 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 cc3b834..824a77b 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.1'}) + request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.27.2'}) with urllib.request.urlopen(request, timeout=30) as response: if urllib.parse.urlparse(response.geturl()).scheme != 'https': raise ValueError('GeoIP download redirected away from HTTPS.') diff --git a/scripts/docs_check.py b/scripts/docs_check.py index e77e5b8..8b66d4b 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.1 release.""" +"""Rust-independent documentation/Reader consistency checks for the 0.27.2 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.2_IMPLEMENTATION.md", DOCS / "V0.27.1_IMPLEMENTATION.md", DOCS / "V0.27.0_IMPLEMENTATION.md", DOCS / "V0.26.7_IMPLEMENTATION.md", @@ -58,16 +59,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.1") +assert readme.startswith("# RustProxy 0.27.2") 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.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 roadmap.startswith("# RustProxy roadmap") and "## 0.27.2" in 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.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"]: +for release in ["0.27.2", "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.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.index("## 0.27.2") < 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 77fd4d0..218511e 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.1 source tree: missing {path}") + raise ValueError(f"Incomplete RustProxy 0.27.2 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.1 entry point; extract the complete package, not individual files.") + raise ValueError("src/lib.rs is not the 0.27.2 entry point; extract the complete package, not individual files.") return messages diff --git a/scripts/release_smoke.py b/scripts/release_smoke.py index e092f08..4fbc40e 100755 --- a/scripts/release_smoke.py +++ b/scripts/release_smoke.py @@ -7,6 +7,7 @@ then checks the 0.24.1 shared-statistics/runtime/settings contracts and retained from __future__ import annotations import hashlib +import importlib.util import json import os import re @@ -123,7 +124,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.1"' in (target / "Cargo.toml").read_text(encoding="utf-8") + assert 'version = "0.27.2"' 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 +135,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.1" + assert manifest["to_version"] == "0.27.2" assert manifest["removed_obsolete_runtime_state"] is True assert not legacy.exists() assert not (backup / "legacy-data").exists() @@ -604,7 +605,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.1"' in openapi_0241 + assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.27.2"' 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 @@ -1087,7 +1088,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.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"']: + for marker in ['"version": "0.27.2"', '"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"']: @@ -1238,6 +1239,52 @@ def reproducible_archive_smoke(tmp: Path) -> None: assert len(timestamps) == 1 +def managed_acme_relocation_smoke(tmp: Path) -> None: + """Reproduce the 0.27.1 relocated console-script shebang failure.""" + stage = tmp / "acme-python.installing" + final = tmp / "acme-python" + bindir = stage / "bin" + bindir.mkdir(parents=True) + python = bindir / "python" + python.write_text( + "#!/bin/sh\n" + "test \"$1\" = -I && test \"$2\" = -m && test \"$3\" = certbot && test \"$4\" = --version || exit 2\n" + "echo 'certbot 5.8.0'\n", + encoding="utf-8", + ) + python.chmod(0o755) + console = bindir / "certbot" + console.write_text(f"#!{python}\necho 'certbot 5.8.0'\n", encoding="utf-8") + console.chmod(0o755) + stage.rename(final) + + relocated_console = final / "bin/certbot" + try: + subprocess.run([str(relocated_console), "--version"], check=True, capture_output=True, text=True) + except OSError: + pass + else: + raise AssertionError("relocated pip console script unexpectedly kept a valid staging shebang") + + worker_path = ROOT / "scripts/acme_provider.py" + spec = importlib.util.spec_from_file_location("rustproxy_acme_provider", worker_path) + assert spec and spec.loader + module = importlib.util.module_from_spec(spec) + old = os.environ.get("RUSTPROXY_CERTBOT_PYTHON") + os.environ["RUSTPROXY_CERTBOT_PYTHON"] = str(final / "bin/python") + try: + spec.loader.exec_module(module) + prefix = module.certbot_prefix() + assert prefix == [str(final / "bin/python"), "-I", "-m", "certbot"] + result = subprocess.run(prefix + ["--version"], check=True, capture_output=True, text=True) + assert result.stdout.strip() == "certbot 5.8.0" + finally: + if old is None: + os.environ.pop("RUSTPROXY_CERTBOT_PYTHON", None) + else: + os.environ["RUSTPROXY_CERTBOT_PYTHON"] = old + + def main() -> None: subprocess.run(["python3", str(ROOT / "scripts/rust_module_contracts.py")], check=True) subprocess.run(["python3", str(ROOT / "scripts/source_module_contracts.py")], check=True) @@ -1245,6 +1292,7 @@ def main() -> None: tmp = Path(directory) sql_and_backup_smoke(tmp / "sql") upgrade_smoke(tmp / "upgrade") + managed_acme_relocation_smoke(tmp / "acme-relocation") reproducible_archive_smoke(tmp / "archives") deployment_gate_smoke(tmp) npm_migration_smoke(tmp) @@ -1252,7 +1300,7 @@ def main() -> None: source_contract_smoke() container_contract_smoke() grafana_contract_smoke() - 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.") + print("PASS: 0.27.2 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 d0d2ea1..41c466a 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.1"}) + request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.27.2"}) 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 08a0204..aa82da6 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.1-" + uuid.uuid4().hex[:12]) + backup = target / ".upgrade-backup" / ("before-0.27.2-" + 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.1", "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.2", "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.1. Previous managed source files: {backup}") + print(f"Installed RustProxy 0.27.2. 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 7caf111..5bcf555 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.1", + "RustProxy 0.27.2", "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/runtime_env.rs b/src/acme/runtime_env.rs index 2597266..78375bb 100644 --- a/src/acme/runtime_env.rs +++ b/src/acme/runtime_env.rs @@ -60,6 +60,7 @@ impl Manager { struct RuntimeLaunch { python: PathBuf, prepend_path: Option, + managed_certbot_python: Option, } fn find_on_path(name: &str) -> Option { @@ -96,15 +97,6 @@ fn venv_python(directory: &Path) -> PathBuf { venv_bin(directory).join("python") } -#[cfg(windows)] -fn venv_certbot(directory: &Path) -> PathBuf { - venv_bin(directory).join("certbot.exe") -} -#[cfg(not(windows))] -fn venv_certbot(directory: &Path) -> PathBuf { - venv_bin(directory).join("certbot") -} - async fn command_success( mut command: Command, timeout: Duration, @@ -131,8 +123,7 @@ async fn managed_environment_valid( shutdown: &tokio_util::sync::CancellationToken, ) -> bool { let python = venv_python(directory); - let certbot = venv_certbot(directory); - if !python.is_file() || !certbot.is_file() { + if !python.is_file() { return false; } let mut imports = Command::new(&python); @@ -144,8 +135,13 @@ async fn managed_environment_valid( if !command_success(imports, Duration::from_secs(20), shutdown).await { return false; } - let mut version = Command::new(&certbot); - version.arg("--version"); + // A venv is created under an atomic staging directory and then renamed + // into place. pip-generated console scripts contain an absolute shebang, + // so the staged `bin/certbot` is not relocatable after that rename. Invoke + // Certbot as a module through the final venv interpreter instead. This also + // makes existing 0.27.1 environments immediately usable after upgrade. + let mut version = Command::new(&python); + version.args(["-I", "-m", "certbot", "--version"]); command_success(version, Duration::from_secs(20), shutdown).await } @@ -307,6 +303,7 @@ async fn ensure_runtime(state: &App) -> Result { return Ok(RuntimeLaunch { python: venv_python(&managed), prepend_path: Some(venv_bin(&managed)), + managed_certbot_python: Some(venv_python(&managed)), }); } @@ -355,6 +352,7 @@ async fn ensure_runtime(state: &App) -> Result { return Ok(RuntimeLaunch { python: venv_python(&managed), prepend_path: Some(venv_bin(&managed)), + managed_certbot_python: Some(venv_python(&managed)), }); } @@ -371,6 +369,7 @@ async fn ensure_runtime(state: &App) -> Result { return Ok(RuntimeLaunch { python, prepend_path: None, + managed_certbot_python: None, }); } @@ -434,9 +433,13 @@ async fn worker(state: &App, directory: PathBuf, raw: Zeroizing) -> Resu .stdout(Stdio::piped()) .stderr(Stdio::null()) .kill_on_drop(true); + command.env_remove("RUSTPROXY_CERTBOT_PYTHON"); if let Some(prepend) = launch.prepend_path.as_deref() { command.env("PATH", merged_path(prepend)?); } + if let Some(python) = launch.managed_certbot_python.as_deref() { + command.env("RUSTPROXY_CERTBOT_PYTHON", python); + } #[cfg(unix)] { use std::os::unix::process::CommandExt; diff --git a/src/admin/documentation/documents.rs b/src/admin/documentation/documents.rs index 71ef0c9..c877bbd 100644 --- a/src/admin/documentation/documents.rs +++ b/src/admin/documentation/documents.rs @@ -409,7 +409,8 @@ pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[ "/docs/V0.25.1_AUDIT.md" )), ), - reader_doc!("docs/V0.27.1_IMPLEMENTATION.md", "technical"), + reader_doc!("docs/V0.27.2_IMPLEMENTATION.md", "technical"), + reader_doc!("docs/V0.27.1_IMPLEMENTATION.md", "historical"), reader_doc!("docs/V0.27.0_IMPLEMENTATION.md", "technical"), reader_doc!("docs/V0.26.7_IMPLEMENTATION.md", "technical"), reader_doc!("docs/V0.26.6_IMPLEMENTATION.md", "technical"), diff --git a/src/admin/openapi.json b/src/admin/openapi.json index 437c533..c5761f6 100644 --- a/src/admin/openapi.json +++ b/src/admin/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "RustProxy Administration API", - "version": "0.27.1", + "version": "0.27.2", "description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust." }, "servers": [ diff --git a/src/admin/web/index.html b/src/admin/web/index.html index 5e7854e..dc3b349 100644 --- a/src/admin/web/index.html +++ b/src/admin/web/index.html @@ -2823,7 +2823,7 @@ -