0.27.2_hotfix1

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-07 15:39:33 +02:00
1 parent 177297c529
commit bcc9f20345
2 files changed
+145 -56

No files matched your search

+53 -52
View File
@@ -75,8 +75,18 @@ fn find_on_path(name: &str) -> Option<PathBuf> {
.find(|path| path.is_file())
}
fn system_python() -> Option<PathBuf> {
find_on_path("python3").or_else(|| find_on_path("python"))
fn python_candidates() -> impl Iterator<Item = PathBuf> {
[
"python3",
"python3.14",
"python3.13",
"python3.12",
"python3.11",
"python3.10",
"python",
]
.into_iter()
.filter_map(find_on_path)
}
#[cfg(windows)]
@@ -135,20 +145,27 @@ async fn managed_environment_valid(
if !command_success(imports, Duration::from_secs(20), shutdown).await {
return false;
}
// 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
}
async fn system_has_pip(
python: &Path,
shutdown: &tokio_util::sync::CancellationToken,
) -> bool {
async fn system_python(shutdown: &tokio_util::sync::CancellationToken) -> Option<PathBuf> {
for python in python_candidates() {
let mut command = Command::new(&python);
command.args([
"-I",
"-c",
"import sys;raise SystemExit(0 if sys.version_info >= (3,10) else 1)",
]);
if command_success(command, Duration::from_secs(10), shutdown).await {
return Some(python);
}
}
None
}
async fn system_has_pip(python: &Path, shutdown: &tokio_util::sync::CancellationToken) -> bool {
let mut command = Command::new(python);
command.args(["-m", "pip", "--version"]);
command_success(command, Duration::from_secs(20), shutdown).await
@@ -168,10 +185,8 @@ async fn create_virtual_environment(
if !system_pip {
return false;
}
// Some distributions split the stdlib venv module into a separate package.
// Bootstrap virtualenv with the system pip into the RustProxy data directory
// instead of modifying site-packages owned by the distribution.
let _ = tokio::fs::remove_dir_all(staging).await;
// Fall back to an application-local virtualenv bootstrap.
let bootstrap = staging.with_extension("virtualenv-bootstrap");
let _ = tokio::fs::remove_dir_all(&bootstrap).await;
let mut install = Command::new(python);
@@ -183,9 +198,9 @@ async fn create_virtual_environment(
"--no-input",
"--no-cache-dir",
"--retries",
"1",
"3",
"--timeout",
"15",
"30",
"--target",
]);
install.arg(&bootstrap).arg("virtualenv>=20,<21");
@@ -204,7 +219,7 @@ async fn create_virtual_environment(
created
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum BuildFailure {
Bootstrap,
Install,
@@ -216,27 +231,26 @@ async fn build_managed_environment(
system_pip: bool,
shutdown: &tokio_util::sync::CancellationToken,
) -> Result<(), BuildFailure> {
let staging = target.with_extension("installing");
let _ = tokio::fs::remove_dir_all(&staging).await;
if !create_virtual_environment(python, &staging, system_pip, shutdown).await {
let _ = tokio::fs::remove_dir_all(&staging).await;
let legacy_stage = target.with_extension("installing");
let _ = tokio::fs::remove_dir_all(&legacy_stage).await;
let _ = tokio::fs::remove_dir_all(target).await;
let _ = tokio::fs::remove_file(target).await;
if !create_virtual_environment(python, target, system_pip, shutdown).await {
let _ = tokio::fs::remove_dir_all(target).await;
return Err(BuildFailure::Bootstrap);
}
let staging_python = venv_python(&staging);
let mut pip_check = Command::new(&staging_python);
let target_python = venv_python(target);
let mut pip_check = Command::new(&target_python);
pip_check.args(["-m", "pip", "--version"]);
if !command_success(pip_check, Duration::from_secs(30), shutdown).await {
let mut ensurepip = Command::new(&staging_python);
let mut ensurepip = Command::new(&target_python);
ensurepip.args(["-m", "ensurepip", "--upgrade"]);
if !command_success(ensurepip, Duration::from_secs(120), shutdown).await {
let _ = tokio::fs::remove_dir_all(&staging).await;
let _ = tokio::fs::remove_dir_all(target).await;
return Err(BuildFailure::Bootstrap);
}
}
let mut pip = Command::new(&staging_python);
let mut pip = Command::new(&target_python);
pip.args([
"-m",
"pip",
@@ -245,24 +259,15 @@ async fn build_managed_environment(
"--no-input",
"--no-cache-dir",
"--retries",
"1",
"3",
"--timeout",
"15",
"30",
]);
pip.args(CERTBOT_PACKAGES.iter().copied());
if !command_success(pip, Duration::from_secs(300), shutdown).await
|| !managed_environment_valid(&staging, shutdown).await
if !command_success(pip, Duration::from_secs(600), shutdown).await
|| !managed_environment_valid(target, shutdown).await
{
let _ = tokio::fs::remove_dir_all(&staging).await;
return Err(BuildFailure::Install);
}
if target.exists() && tokio::fs::remove_dir_all(target).await.is_err() {
let _ = tokio::fs::remove_dir_all(&staging).await;
return Err(BuildFailure::Install);
}
if tokio::fs::rename(&staging, target).await.is_err() {
let _ = tokio::fs::remove_dir_all(&staging).await;
let _ = tokio::fs::remove_dir_all(target).await;
return Err(BuildFailure::Install);
}
Ok(())
@@ -306,7 +311,6 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
managed_certbot_python: Some(venv_python(&managed)),
});
}
let distro_certbot = find_on_path("certbot");
let distro_certbot_available = if let Some(certbot) = distro_certbot.as_deref() {
let mut version = Command::new(certbot);
@@ -315,7 +319,7 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
} else {
false
};
let Some(python) = system_python() else {
let Some(python) = system_python(&state.shutdown).await else {
state.acme.set_runtime(RuntimeStatus {
status: "unavailable",
mode: "none",
@@ -337,9 +341,9 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
pip: system_pip,
certbot: distro_certbot_available,
});
let build = build_managed_environment(&python, &managed, system_pip, &state.shutdown).await;
if build.is_ok() {
tracing::info!(path = %managed.display(), "managed ACME runtime ready");
state.acme.set_runtime(RuntimeStatus {
status: "ready",
mode: "managed",
@@ -355,8 +359,8 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
managed_certbot_python: Some(venv_python(&managed)),
});
}
if distro_certbot_available {
tracing::warn!(python = %python.display(), failure = ?build, "managed ACME runtime setup failed; using system Certbot fallback");
state.acme.set_runtime(RuntimeStatus {
status: "ready",
mode: "system",
@@ -372,7 +376,6 @@ async fn ensure_runtime(state: &App) -> Result<RuntimeLaunch, String> {
managed_certbot_python: None,
});
}
let message = if build == Err(BuildFailure::Bootstrap) && !system_pip {
"acme.runtime_pip_missing"
} else {
@@ -397,14 +400,12 @@ pub async fn runtime_setup_once(state: Weak<App>) {
let _ = ensure_runtime(&state).await;
}
/// Kill the entire private process group if execution is cancelled/abandoned.
/// Certbot inherits the group, so it cannot outlive a stopped RustProxy worker.
struct ProcessGroup(Option<u32>);
impl Drop for ProcessGroup {
fn drop(&mut self) {
#[cfg(unix)]
if let Some(id) = self.0 {
// SAFETY: this is the process group created solely for this child.
// SAFETY: this process group belongs only to the managed child.
unsafe {
libc::kill(-(id as i32), libc::SIGKILL);
}