diff --git a/crates/hephd/src/selfupdate.rs b/crates/hephd/src/selfupdate.rs index 8c7d470..e276955 100644 --- a/crates/hephd/src/selfupdate.rs +++ b/crates/hephd/src/selfupdate.rs @@ -70,19 +70,17 @@ pub fn parse_latest_tag(body: &str) -> Result { Ok(rel.tag_name) } -/// Fetch the latest release tag from the forge over HTTP (reusing the daemon's -/// shared `reqwest::Client`). Network/HTTP/JSON failures surface as `Err` for -/// the caller to log-and-continue. -pub async fn fetch_latest_tag(http: &reqwest::Client, url: &str) -> Result { - let body = http - .get(url) - .send() - .await +/// Fetch the latest release tag from the forge over HTTPS, blocking. Uses +/// `ureq` (already a dependency, with a rustls/ring TLS backend that needs no +/// system libs) rather than the daemon's `reqwest` client, which is built +/// without TLS — the forge poll is the only production HTTPS-over-HTTP-client +/// path (hub sync is plain HTTP). Network/HTTP/JSON failures surface as `Err`. +pub fn fetch_latest_tag(url: &str) -> Result { + let body = ureq::get(url) + .call() .context("requesting forge releases/latest")? - .error_for_status() - .context("forge releases/latest returned an error status")? - .text() - .await + .body_mut() + .read_to_string() .context("reading forge releases/latest body")?; parse_latest_tag(&body) } @@ -93,25 +91,33 @@ pub trait ReleaseSource: Send + Sync + 'static { fn latest_tag(&self) -> impl std::future::Future> + Send; } -/// The production source: the forge's `releases/latest` over HTTP. +/// The production source: the forge's `releases/latest` over HTTPS (via `ureq`). pub struct ForgeReleaseSource { - http: reqwest::Client, url: String, } impl ForgeReleaseSource { - /// Source backed by the daemon's shared client, hitting [`RELEASES_LATEST_URL`]. - pub fn new(http: reqwest::Client) -> Self { + /// Source hitting [`RELEASES_LATEST_URL`]. + pub fn new() -> Self { Self { - http, url: RELEASES_LATEST_URL.to_string(), } } } +impl Default for ForgeReleaseSource { + fn default() -> Self { + Self::new() + } +} + impl ReleaseSource for ForgeReleaseSource { async fn latest_tag(&self) -> Result { - fetch_latest_tag(&self.http, &self.url).await + // `ureq` is blocking; keep it off the async runtime. + let url = self.url.clone(); + tokio::task::spawn_blocking(move || fetch_latest_tag(&url)) + .await + .context("release-fetch task panicked")? } } diff --git a/crates/hephd/src/server.rs b/crates/hephd/src/server.rs index bf245ba..59826ac 100644 --- a/crates/hephd/src/server.rs +++ b/crates/hephd/src/server.rs @@ -135,7 +135,7 @@ impl Daemon { let Some(cfg) = self.ctx.self_update.clone() else { return; }; - let source = selfupdate::ForgeReleaseSource::new(self.ctx.http.clone()); + let source = selfupdate::ForgeReleaseSource::new(); let installer: std::sync::Arc = std::sync::Arc::new(selfupdate::CargoInstaller); let restarter: std::sync::Arc = diff --git a/docs/changelog.d/+selfupdate-poll-tls.bugfix.md b/docs/changelog.d/+selfupdate-poll-tls.bugfix.md new file mode 100644 index 0000000..7097c0b --- /dev/null +++ b/docs/changelog.d/+selfupdate-poll-tls.bugfix.md @@ -0,0 +1 @@ +Fix `hephd --self-update` never detecting releases: the release poll used the daemon's `reqwest` client, which is built without a TLS backend (`default-features = false`), so every HTTPS request to the forge failed (`release check failed: requesting forge releases/latest`). The poll now uses `ureq` — already a dependency, with a rustls/ring TLS stack that needs no system libraries (and no cmake/`aws-lc-sys`). Hub sync is unaffected (it is plain HTTP).