From bc79e36c713a9105f4e5671394d7bcab15752c04 Mon Sep 17 00:00:00 2001 From: eareimu Date: Wed, 17 Jun 2026 12:55:17 +0800 Subject: [PATCH 1/4] fix(release): repair product download metadata --- .github/workflows/release.yml | 4 +- xtask/src/publish/s3.rs | 11 ++++ xtask/src/publish/s3/brew.rs | 43 ++++++++++------ xtask/src/publish/s3/plan.rs | 97 ++++++++++++++++++++++++++++++++++- xtask/src/publish/s3/scoop.rs | 43 ++++++++++------ 5 files changed, 161 insertions(+), 37 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 67a7884..0cf7925 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -277,7 +277,7 @@ jobs: DHTTP_ROOT_CA: ${{ github.workspace }}/gmutils/keychain/root.crt RELEASE_BUCKET: download SCOOP_PREFIX: scoop/gmutils - SCOOP_PUBLIC_BASE_URL: https://download.genmeta.net/scoop/gmutils + SCOOP_PUBLIC_BASE_URL: https://download.dhttp.net/scoop/gmutils defaults: run: shell: bash @@ -387,7 +387,7 @@ jobs: DHTTP_ROOT_CA: ${{ github.workspace }}/gmutils/keychain/root.crt RELEASE_BUCKET: download BREW_PREFIX: brew/gmutils - BREW_PUBLIC_BASE_URL: https://download.genmeta.net/brew/gmutils + BREW_PUBLIC_BASE_URL: https://download.dhttp.net/brew/gmutils HOMEBREW_TAP_REPOSITORY: genmeta/homebrew-genmeta HOMEBREW_TAP_BASE_BRANCH: main defaults: diff --git a/xtask/src/publish/s3.rs b/xtask/src/publish/s3.rs index f3d27b4..e7a9bbb 100644 --- a/xtask/src/publish/s3.rs +++ b/xtask/src/publish/s3.rs @@ -515,4 +515,15 @@ mod tests { Some(&RequestChecksumCalculation::WhenRequired) ); } + + #[test] + fn release_workflow_uses_public_r2_download_domain() { + let workflow = include_str!("../../../.github/workflows/release.yml"); + + assert!(workflow.contains("BREW_PUBLIC_BASE_URL: https://download.dhttp.net/brew/gmutils")); + assert!( + workflow.contains("SCOOP_PUBLIC_BASE_URL: https://download.dhttp.net/scoop/gmutils") + ); + assert!(!workflow.contains("download.genmeta.net")); + } } diff --git a/xtask/src/publish/s3/brew.rs b/xtask/src/publish/s3/brew.rs index fa9bb13..bfd9b99 100644 --- a/xtask/src/publish/s3/brew.rs +++ b/xtask/src/publish/s3/brew.rs @@ -1,6 +1,6 @@ use aws_sdk_s3::Client; use snafu::{ResultExt, Snafu, Whatever}; -use tracing::info; +use tracing::{info, warn}; use super::{ BrewPublishTarget, S3Options, @@ -80,7 +80,7 @@ pub async fn run( target: BrewPublishTarget, ) -> Result<(), Whatever> { let loaded = super::load_manifest(ArtifactKind::Brew).await?; - let mut uploads = plan_payload_uploads( + let (mut uploads, manifest) = plan_payload_uploads( client, &options.bucket, &loaded.target_dir, @@ -88,7 +88,7 @@ pub async fn run( &target.prefix, ) .await?; - let formula = render_formula(&loaded.manifest, target.public_base_url.as_str()) + let formula = render_formula(&manifest, target.public_base_url.as_str()) .whatever_context("failed to render brew formula")?; let formula_path = loaded .target_dir @@ -141,9 +141,10 @@ async fn plan_payload_uploads( target_dir: &std::path::Path, manifest: &PackageManifest, prefix: &super::key::RemotePrefix, -) -> Result, Whatever> { +) -> Result<(Vec, PackageManifest), Whatever> { let mut uploads = Vec::new(); - for artifact in &manifest.artifacts { + let mut manifest = manifest.clone(); + for artifact in &mut manifest.artifacts { let archive_name = archive_name(artifact) .whatever_context("brew package artifact is missing archive name")?; let path = super::artifact_path(target_dir, artifact); @@ -155,24 +156,32 @@ async fn plan_payload_uploads( ); let key = prefix.join(archive_name); let remote = super::remote_artifact_state(client, bucket, &key).await?; - let Some(condition) = super::plan::plan_immutable_upload(&key, &actual_sha256, remote) - .whatever_context("remote brew artifact collision")? - else { + let plan = super::plan::plan_versioned_immutable_payload(&key, &actual_sha256, remote); + artifact.sha256 = plan.metadata_sha256().to_string(); + if let Some(condition) = plan.upload_condition() { + uploads.push(PlannedUpload { + path, + key, + entry: false, + condition: Some(condition), + }); + } else if plan.remote_sha256_matches_local() { info!( key, path = %path.display(), "remote immutable brew artifact already has matching sha256" ); - continue; - }; - uploads.push(PlannedUpload { - path, - key, - entry: false, - condition: Some(condition), - }); + } else { + warn!( + key, + path = %path.display(), + local_sha256 = %actual_sha256, + remote_sha256 = %plan.metadata_sha256(), + "remote immutable brew artifact already exists with different sha256; reusing remote payload for metadata" + ); + } } - Ok(uploads) + Ok((uploads, manifest)) } fn archive_name(artifact: &PackageArtifact) -> Result<&str, RenderBrewError> { diff --git a/xtask/src/publish/s3/plan.rs b/xtask/src/publish/s3/plan.rs index b89885c..f524363 100644 --- a/xtask/src/publish/s3/plan.rs +++ b/xtask/src/publish/s3/plan.rs @@ -23,6 +23,31 @@ pub struct PlannedUpload { pub condition: Option, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VersionedImmutablePayloadPlan { + metadata_sha256: String, + upload_condition: Option, + remote_sha256_matches_local: Option, +} + +impl VersionedImmutablePayloadPlan { + pub fn metadata_sha256(&self) -> &str { + &self.metadata_sha256 + } + + pub fn upload_condition(&self) -> Option { + self.upload_condition + } + + pub fn reuses_remote_payload(&self) -> bool { + self.remote_sha256_matches_local.is_some() + } + + pub fn remote_sha256_matches_local(&self) -> bool { + self.remote_sha256_matches_local.unwrap_or(false) + } +} + #[derive(Debug, Snafu)] #[snafu(module)] pub enum ImmutableCollisionError { @@ -58,9 +83,34 @@ pub fn plan_immutable_upload( } } +pub fn plan_versioned_immutable_payload( + _artifact_path: &str, + actual_sha256: &str, + remote: RemoteArtifactState, +) -> VersionedImmutablePayloadPlan { + match remote { + RemoteArtifactState::Missing => VersionedImmutablePayloadPlan { + metadata_sha256: actual_sha256.to_string(), + upload_condition: Some(UploadCondition::IfMissing), + remote_sha256_matches_local: None, + }, + RemoteArtifactState::Present { sha256 } => { + let matches_local = sha256 == actual_sha256; + VersionedImmutablePayloadPlan { + metadata_sha256: sha256, + upload_condition: None, + remote_sha256_matches_local: Some(matches_local), + } + } + } +} + #[cfg(test)] mod tests { - use super::{RemoteArtifactState, UploadCondition, plan_immutable_upload}; + use super::{ + RemoteArtifactState, UploadCondition, plan_immutable_upload, + plan_versioned_immutable_payload, + }; #[test] fn immutable_missing_remote_uploads_only_when_absent() { @@ -100,4 +150,49 @@ mod tests { "remote immutable artifact brew/file.tar.gz already exists with different sha256 def" ); } + + #[test] + fn versioned_immutable_missing_remote_uploads_local_payload() { + let plan = plan_versioned_immutable_payload( + "brew/file.tar.gz", + "local-sha", + RemoteArtifactState::Missing, + ); + + assert_eq!(plan.metadata_sha256(), "local-sha"); + assert_eq!(plan.upload_condition(), Some(UploadCondition::IfMissing)); + assert!(!plan.reuses_remote_payload()); + } + + #[test] + fn versioned_immutable_matching_remote_reuses_remote_payload() { + let plan = plan_versioned_immutable_payload( + "brew/file.tar.gz", + "same-sha", + RemoteArtifactState::Present { + sha256: "same-sha".to_string(), + }, + ); + + assert_eq!(plan.metadata_sha256(), "same-sha"); + assert_eq!(plan.upload_condition(), None); + assert!(plan.reuses_remote_payload()); + assert!(plan.remote_sha256_matches_local()); + } + + #[test] + fn versioned_immutable_different_remote_reuses_remote_sha_for_metadata() { + let plan = plan_versioned_immutable_payload( + "brew/file.tar.gz", + "new-local-sha", + RemoteArtifactState::Present { + sha256: "published-sha".to_string(), + }, + ); + + assert_eq!(plan.metadata_sha256(), "published-sha"); + assert_eq!(plan.upload_condition(), None); + assert!(plan.reuses_remote_payload()); + assert!(!plan.remote_sha256_matches_local()); + } } diff --git a/xtask/src/publish/s3/scoop.rs b/xtask/src/publish/s3/scoop.rs index 7785a7c..0f8ae7e 100644 --- a/xtask/src/publish/s3/scoop.rs +++ b/xtask/src/publish/s3/scoop.rs @@ -1,7 +1,7 @@ use aws_sdk_s3::Client; use serde::Serialize; use snafu::{ResultExt, Snafu, Whatever}; -use tracing::info; +use tracing::{info, warn}; use super::{ S3Options, ScoopPublishTarget, @@ -104,7 +104,7 @@ pub async fn run( target: ScoopPublishTarget, ) -> Result<(), Whatever> { let loaded = super::load_manifest(ArtifactKind::Scoop).await?; - let mut uploads = plan_payload_uploads( + let (mut uploads, manifest) = plan_payload_uploads( client, &options.bucket, &loaded.target_dir, @@ -112,7 +112,7 @@ pub async fn run( &target.prefix, ) .await?; - let json = render_scoop_json(&loaded.manifest, target.public_base_url.as_str()) + let json = render_scoop_json(&manifest, target.public_base_url.as_str()) .whatever_context("failed to render scoop json")?; let manifest_path = loaded .target_dir @@ -165,9 +165,10 @@ async fn plan_payload_uploads( target_dir: &std::path::Path, manifest: &PackageManifest, prefix: &super::key::RemotePrefix, -) -> Result, Whatever> { +) -> Result<(Vec, PackageManifest), Whatever> { let mut uploads = Vec::new(); - for artifact in &manifest.artifacts { + let mut manifest = manifest.clone(); + for artifact in &mut manifest.artifacts { let archive_name = archive_name(artifact) .whatever_context("scoop package artifact is missing archive name")?; let path = super::artifact_path(target_dir, artifact); @@ -179,24 +180,32 @@ async fn plan_payload_uploads( ); let key = prefix.join(archive_name); let remote = super::remote_artifact_state(client, bucket, &key).await?; - let Some(condition) = super::plan::plan_immutable_upload(&key, &actual_sha256, remote) - .whatever_context("remote scoop artifact collision")? - else { + let plan = super::plan::plan_versioned_immutable_payload(&key, &actual_sha256, remote); + artifact.sha256 = plan.metadata_sha256().to_string(); + if let Some(condition) = plan.upload_condition() { + uploads.push(PlannedUpload { + path, + key, + entry: false, + condition: Some(condition), + }); + } else if plan.remote_sha256_matches_local() { info!( key, path = %path.display(), "remote immutable scoop artifact already has matching sha256" ); - continue; - }; - uploads.push(PlannedUpload { - path, - key, - entry: false, - condition: Some(condition), - }); + } else { + warn!( + key, + path = %path.display(), + local_sha256 = %actual_sha256, + remote_sha256 = %plan.metadata_sha256(), + "remote immutable scoop artifact already exists with different sha256; reusing remote payload for metadata" + ); + } } - Ok(uploads) + Ok((uploads, manifest)) } fn archive_name(artifact: &PackageArtifact) -> Result<&str, RenderScoopError> { From 01e27816918afb71bc707fb461fee25e9f975628 Mon Sep 17 00:00:00 2001 From: eareimu Date: Wed, 17 Jun 2026 13:26:33 +0800 Subject: [PATCH 2/4] fix(release): publish gmutils apt metadata to unified repo --- .github/workflows/release.yml | 74 ++++++++++- xtask/src/publish/s3.rs | 59 +++++++- xtask/src/publish/s3/deb.rs | 244 ++++++++++++++++++++++++++++------ xtask/src/publish/s3/plan.rs | 5 +- 4 files changed, 332 insertions(+), 50 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0cf7925..ed3be65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ on: - 'v*' permissions: - contents: read + contents: write concurrency: group: release-${{ github.workflow }}-${{ github.ref }} @@ -33,8 +33,8 @@ jobs: env: DHTTP_ROOT_CA: ${{ github.workspace }}/gmutils/keychain/root.crt RELEASE_BUCKET: download - APT_SUITE: stable - APT_PREFIX: apt/gmutils + APT_SUITE: genmeta + APT_PREFIX: ppa/genmeta LINUX_TARGETS: x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu armv7-unknown-linux-gnueabihf i686-unknown-linux-gnu defaults: run: @@ -150,6 +150,23 @@ jobs: --bucket "$RELEASE_BUCKET" \ deb --prefix "$APT_PREFIX" --suite "$APT_SUITE" --fingerprint "$APT_SIGNING_FINGERPRINT" + - name: Upload deb packages to GitHub Release + if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v') + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + shopt -s nullglob + assets=(target/*/release/deb/*.deb) + if [ "${#assets[@]}" -eq 0 ]; then + echo "no deb release assets found" >&2 + exit 1 + fi + if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --notes-from-tag || gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null + fi + gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber --repo "$GITHUB_REPOSITORY" + - name: Upload deb manifests if: always() uses: actions/upload-artifact@v7 @@ -260,6 +277,23 @@ jobs: --bucket "$RELEASE_BUCKET" \ rpm --prefix "$RPM_PREFIX" + - name: Upload rpm packages to GitHub Release + if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v') + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + shopt -s nullglob + assets=(target/*/release/rpm/*.rpm) + if [ "${#assets[@]}" -eq 0 ]; then + echo "no rpm release assets found" >&2 + exit 1 + fi + if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --notes-from-tag || gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null + fi + gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber --repo "$GITHUB_REPOSITORY" + - name: Upload rpm manifests if: always() uses: actions/upload-artifact@v7 @@ -370,6 +404,23 @@ jobs: --bucket "$RELEASE_BUCKET" \ scoop --prefix "$SCOOP_PREFIX" --public-base-url "$SCOOP_PUBLIC_BASE_URL" + - name: Upload scoop packages to GitHub Release + if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v') + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + shopt -s nullglob + assets=(target/*/release/scoop/*.zip target/common/scoop/*.json) + if [ "${#assets[@]}" -eq 0 ]; then + echo "no scoop release assets found" >&2 + exit 1 + fi + if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --notes-from-tag || gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null + fi + gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber --repo "$GITHUB_REPOSITORY" + - name: Upload scoop manifests if: always() uses: actions/upload-artifact@v7 @@ -514,6 +565,23 @@ jobs: --title "brew: update $FORMULA_NAME from gmutils release" \ --body "Generated by $GITHUB_REPOSITORY release $GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT." + - name: Upload Homebrew packages to GitHub Release + if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v') + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + shopt -s nullglob + assets=(target/*/release/brew/*.tar.gz target/common/brew/*.rb) + if [ "${#assets[@]}" -eq 0 ]; then + echo "no Homebrew release assets found" >&2 + exit 1 + fi + if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --notes-from-tag || gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null + fi + gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber --repo "$GITHUB_REPOSITORY" + - name: Upload Homebrew manifests if: always() uses: actions/upload-artifact@v7 diff --git a/xtask/src/publish/s3.rs b/xtask/src/publish/s3.rs index e7a9bbb..e6c387c 100644 --- a/xtask/src/publish/s3.rs +++ b/xtask/src/publish/s3.rs @@ -8,7 +8,9 @@ use aws_sdk_s3::{ Client, config::{Region, RequestChecksumCalculation}, error::SdkError, - operation::{get_object::GetObjectError, put_object::PutObjectError}, + operation::{ + get_object::GetObjectError, head_object::HeadObjectError, put_object::PutObjectError, + }, primitives::ByteStream, }; use clap::{Args, CommandFactory, Parser, Subcommand, error::ErrorKind}; @@ -277,13 +279,19 @@ pub(crate) async fn upload_file( .await .whatever_context("failed to read upload body")?; let mut request = client.put_object().bucket(bucket).key(key).body(body); - if condition == Some(plan::UploadCondition::IfMissing) { - request = request.if_none_match("*"); + match condition { + Some(plan::UploadCondition::IfMissing) => { + request = request.if_none_match("*"); + } + Some(plan::UploadCondition::IfMatch(etag)) => { + request = request.if_match(etag); + } + None => {} } match request.send().await { Ok(_) => {} Err(error) if is_precondition_failed_error(&error) => { - snafu::whatever!("remote immutable artifact {key} already exists during upload"); + snafu::whatever!("remote object {key} changed during conditional upload"); } Err(error) => { snafu::whatever!("failed to upload {key}: {error}"); @@ -351,6 +359,26 @@ pub(crate) async fn get_object_bytes( Ok(Some(bytes)) } +pub(crate) async fn remote_upload_condition( + client: &Client, + bucket: &str, + key: &str, +) -> Result { + let output = match client.head_object().bucket(bucket).key(key).send().await { + Ok(output) => output, + Err(error) if is_missing_head_object_error(&error) => { + return Ok(plan::UploadCondition::IfMissing); + } + Err(error) => { + snafu::whatever!("failed to inspect remote object {key}: {error}"); + } + }; + let etag = output + .e_tag() + .whatever_context(format!("remote object {key} is missing ETag"))?; + Ok(plan::UploadCondition::IfMatch(etag.to_string())) +} + pub(crate) async fn download_object( client: &Client, bucket: &str, @@ -420,10 +448,21 @@ fn is_missing_object_error(error: &SdkError) -> bool { + if let Some(service) = error.as_service_error() { + let metadata = service.meta(); + return classify_missing_object(metadata.code(), metadata.message(), None); + } + false +} + fn is_precondition_failed_error(error: &SdkError) -> bool { if let Some(service) = error.as_service_error() { let metadata = service.meta(); - return matches!(metadata.code(), Some("PreconditionFailed")); + return matches!( + metadata.code(), + Some("PreconditionFailed") | Some("ConditionalRequestConflict") + ); } false } @@ -526,4 +565,14 @@ mod tests { ); assert!(!workflow.contains("download.genmeta.net")); } + + #[test] + fn release_workflow_publishes_deb_to_unified_apt_repo() { + let workflow = include_str!("../../../.github/workflows/release.yml"); + + assert!(workflow.contains("APT_PREFIX: ppa/genmeta")); + assert!(workflow.contains("APT_SUITE: genmeta")); + assert!(!workflow.contains("APT_PREFIX: apt/gmutils")); + assert!(!workflow.contains("APT_SUITE: stable")); + } } diff --git a/xtask/src/publish/s3/deb.rs b/xtask/src/publish/s3/deb.rs index 3a20dfa..7f1c28b 100644 --- a/xtask/src/publish/s3/deb.rs +++ b/xtask/src/publish/s3/deb.rs @@ -19,7 +19,10 @@ use tempfile::TempDir; use tracing::info; use walkdir::WalkDir; -use super::{DebPublishTarget, S3Options, plan::PlannedUpload}; +use super::{ + DebPublishTarget, S3Options, + plan::{PlannedUpload, UploadCondition}, +}; use crate::{ container::{ check_docker, exec_in_container, force_remove_container, host_uid_gid, @@ -30,7 +33,7 @@ use crate::{ }; const APT_ARCHES: &[&str] = &["amd64", "arm64", "armhf", "i386"]; -const APT_COMPONENT: &str = "main"; +const APT_COMPONENTS: &[&str] = &["main", "contrib", "non-free"]; const APT_STAGE_BASE_IMAGE: &str = "debian:bookworm"; const APT_STAGE_IMAGE: &str = "xtask-apt-publish:bookworm-v1"; const APT_REPOSITORY_TARGET: &str = "/apt-repository"; @@ -94,6 +97,9 @@ pub fn apt_upload_order(key: &str) -> u8 { if key.contains("/pool/") || key.starts_with("pool/") { return 0; } + if key.contains("/binary-") { + return 1; + } if key.ends_with("InRelease") { return 4; } @@ -116,6 +122,9 @@ pub async fn run( ) -> Result<(), Whatever> { let loaded = super::load_manifest(ArtifactKind::Deb).await?; let local_payloads = local_payloads(&loaded.target_dir, &loaded.manifest.artifacts)?; + let entry_conditions = + remote_entry_upload_conditions(client, &options.bucket, &target.prefix, &target.suite) + .await?; let remote_entries = remote_package_entries(client, &options.bucket, &target.prefix, &target.suite).await?; let local_payloads = publishable_local_payloads(local_payloads, &remote_entries) @@ -139,6 +148,7 @@ pub async fn run( generate_repository_metadata(repository.path(), &publish_options).await?; let uploads = repository_uploads(repository.path(), &target.prefix)?; let mut uploads = super::plan_repository_uploads(client, &options.bucket, uploads).await?; + apply_entry_upload_conditions(&mut uploads, &entry_conditions)?; uploads.sort_by(|left, right| { apt_upload_order(&left.key) .cmp(&apt_upload_order(&right.key)) @@ -275,16 +285,16 @@ async fn remote_package_entries( suite: &str, ) -> Result, Whatever> { let mut entries = Vec::new(); - for arch in APT_ARCHES { - let key = prefix.join(&format!( - "dists/{suite}/{APT_COMPONENT}/binary-{arch}/Packages" - )); - let Some(bytes) = super::get_object_bytes(client, bucket, &key).await? else { - continue; - }; - let content = String::from_utf8(bytes) - .whatever_context(format!("remote Packages object {key} was not utf-8"))?; - entries.extend(parse_remote_packages(&content)?); + for component in APT_COMPONENTS { + for arch in APT_ARCHES { + let key = prefix.join(&format!("dists/{suite}/{component}/binary-{arch}/Packages")); + let Some(bytes) = super::get_object_bytes(client, bucket, &key).await? else { + continue; + }; + let content = String::from_utf8(bytes) + .whatever_context(format!("remote Packages object {key} was not utf-8"))?; + entries.extend(parse_remote_packages(&content)?); + } } Ok(entries) } @@ -379,18 +389,20 @@ async fn generate_repository_metadata( } async fn generate_binary_metadata(suite: &str, tools: &AptStageContainer) -> Result<(), Whatever> { - for arch in APT_ARCHES { - let paths = binary_metadata_paths(suite, APT_COMPONENT, arch); - let directory = paths - .packages - .parent() - .whatever_context("binary package path must have a parent")?; - tools - .run_in_repository(&format!("mkdir -p {}", shell_quote_path(directory))) - .await?; - scan_packages(arch, &paths.packages, tools).await?; - gzip_file(&paths.packages, &paths.packages_gz, tools).await?; - write_binary_release(&paths.release, suite, APT_COMPONENT, arch, tools).await?; + for component in APT_COMPONENTS { + for arch in APT_ARCHES { + let paths = binary_metadata_paths(suite, component, arch); + let directory = paths + .packages + .parent() + .whatever_context("binary package path must have a parent")?; + tools + .run_in_repository(&format!("mkdir -p {}", shell_quote_path(directory))) + .await?; + scan_packages(component, arch, &paths.packages, tools).await?; + gzip_file(&paths.packages, &paths.packages_gz, tools).await?; + write_binary_release(&paths.release, suite, component, arch, tools).await?; + } } Ok(()) } @@ -408,13 +420,14 @@ fn binary_metadata_paths(suite: &str, component: &str, arch: &str) -> BinaryMeta } async fn scan_packages( + component: &str, arch: &str, packages: &Path, tools: &AptStageContainer, ) -> Result<(), Whatever> { let script = format!( "{} > {}", - scan_packages_script(arch, packages), + scan_packages_script(component, arch, packages), shell_quote_path(packages) ); tools @@ -423,10 +436,12 @@ async fn scan_packages( .whatever_context(format!("failed to generate {}", packages.display())) } -fn scan_packages_script(arch: &str, _packages: &Path) -> String { +fn scan_packages_script(component: &str, arch: &str, _packages: &Path) -> String { + let pool = format!("pool/{component}"); format!( - "dpkg-scanpackages --multiversion --arch {} pool /dev/null", - shell_quote(arch) + "mkdir -p {pool} && dpkg-scanpackages --multiversion --arch {arch} {pool} /dev/null", + pool = shell_quote(&pool), + arch = shell_quote(arch), ) } @@ -466,19 +481,41 @@ async fn write_binary_release( } async fn generate_suite_release(suite: &str, tools: &AptStageContainer) -> Result<(), Whatever> { - let relative = PathBuf::from("dists").join(suite).join("Release"); - let suite_dir = PathBuf::from("dists").join(suite); - let script = format!( - "apt-ftparchive release {} > {}", - shell_quote_path(&suite_dir), - shell_quote_path(&relative) - ); + let script = suite_release_script(suite); tools .run_in_repository(&script) .await .whatever_context("failed to generate apt suite Release") } +fn suite_release_script(suite: &str) -> String { + let relative = PathBuf::from("dists").join(suite).join("Release"); + let suite_dir = PathBuf::from("dists").join(suite); + let architectures = APT_ARCHES.join(" "); + let components = APT_COMPONENTS.join(" "); + let options = [ + ("Origin", "genmeta"), + ("Label", "genmeta"), + ("Suite", "stable"), + ("Codename", suite), + ("Version", "2025"), + ("Architectures", &architectures), + ("Components", &components), + ("Description", "Genmeta Package Archives"), + ] + .into_iter() + .map(|(name, value)| shell_quote(&format!("APT::FTPArchive::Release::{name}={value}"))) + .map(|argument| format!("-o {argument}")) + .collect::>() + .join(" "); + + format!( + "apt-ftparchive {options} release {} > {}", + shell_quote_path(&suite_dir), + shell_quote_path(&relative) + ) +} + async fn sign_suite_release( options: &AptPublishOptions, tools: &AptStageContainer, @@ -563,6 +600,55 @@ fn repository_uploads( Ok(uploads) } +async fn remote_entry_upload_conditions( + client: &Client, + bucket: &str, + prefix: &super::key::RemotePrefix, + suite: &str, +) -> Result, Whatever> { + let mut conditions = BTreeMap::new(); + for relative in entry_metadata_relative_paths(suite) { + let key = prefix.join(&relative); + let condition = super::remote_upload_condition(client, bucket, &key).await?; + conditions.insert(key, condition); + } + Ok(conditions) +} + +fn entry_metadata_relative_paths(suite: &str) -> Vec { + let mut paths = vec![ + format!("dists/{suite}/Release"), + format!("dists/{suite}/Release.gpg"), + format!("dists/{suite}/InRelease"), + ]; + for component in APT_COMPONENTS { + for arch in APT_ARCHES { + let base = format!("dists/{suite}/{component}/binary-{arch}"); + paths.push(format!("{base}/Packages")); + paths.push(format!("{base}/Packages.gz")); + paths.push(format!("{base}/Release")); + } + } + paths +} + +fn apply_entry_upload_conditions( + uploads: &mut [PlannedUpload], + conditions: &BTreeMap, +) -> Result<(), Whatever> { + for upload in uploads.iter_mut().filter(|upload| upload.entry) { + let condition = conditions + .get(&upload.key) + .whatever_context(format!( + "apt metadata upload {} was not captured in remote baseline", + upload.key + ))? + .clone(); + upload.condition = Some(condition); + } + Ok(()) +} + fn pool_path(package: &str, filename: &str) -> PathBuf { let first = package.chars().next().unwrap_or('_'); PathBuf::from("pool") @@ -786,12 +872,17 @@ fn path_to_mount_source(path: &Path) -> Result { #[cfg(test)] mod tests { - use std::path::{Path, PathBuf}; + use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + }; use super::{ - AptContainerOptions, DebPayload, PackageEntry, RemotePackageEntry, apt_upload_order, - deb_payload_key, publishable_local_payloads, retained_remote_entries, scan_packages_script, - sign_suite_release_script, + super::plan::{PlannedUpload, UploadCondition}, + AptContainerOptions, DebPayload, PackageEntry, RemotePackageEntry, + apply_entry_upload_conditions, apt_upload_order, deb_payload_key, + entry_metadata_relative_paths, publishable_local_payloads, retained_remote_entries, + scan_packages_script, sign_suite_release_script, suite_release_script, }; #[test] @@ -818,11 +909,59 @@ mod tests { #[test] fn scan_packages_script_keeps_multiple_versions() { let script = scan_packages_script( + "main", "amd64", Path::new("dists/stable/main/binary-amd64/Packages"), ); - assert!(script.contains("dpkg-scanpackages --multiversion --arch 'amd64'")); + assert!(script.contains("dpkg-scanpackages --multiversion --arch 'amd64' 'pool/main'")); + } + + #[test] + fn apt_metadata_covers_unified_genmeta_components() { + let paths = entry_metadata_relative_paths("genmeta"); + + assert!(paths.contains(&"dists/genmeta/Release".to_string())); + assert!(paths.contains(&"dists/genmeta/InRelease".to_string())); + assert!(paths.contains(&"dists/genmeta/Release.gpg".to_string())); + assert!(paths.contains(&"dists/genmeta/main/binary-amd64/Packages".to_string())); + assert!(paths.contains(&"dists/genmeta/contrib/binary-amd64/Packages".to_string())); + assert!(paths.contains(&"dists/genmeta/non-free/binary-amd64/Packages".to_string())); + } + + #[test] + fn suite_release_script_describes_genmeta_archive() { + let script = suite_release_script("genmeta"); + + assert!(script.contains("APT::FTPArchive::Release::Origin=genmeta")); + assert!(script.contains("APT::FTPArchive::Release::Label=genmeta")); + assert!(script.contains("APT::FTPArchive::Release::Suite=stable")); + assert!(script.contains("APT::FTPArchive::Release::Codename=genmeta")); + assert!(script.contains("APT::FTPArchive::Release::Components=main contrib non-free")); + assert!(script.contains("APT::FTPArchive::Release::Architectures=amd64 arm64 armhf i386")); + assert!(script.contains("APT::FTPArchive::Release::Description=Genmeta Package Archives")); + } + + #[test] + fn entry_upload_conditions_preserve_remote_baseline() { + let mut uploads = vec![PlannedUpload { + path: PathBuf::from("/tmp/Packages"), + key: "ppa/genmeta/dists/genmeta/main/binary-amd64/Packages".to_string(), + entry: true, + condition: None, + }]; + let conditions = BTreeMap::from([( + "ppa/genmeta/dists/genmeta/main/binary-amd64/Packages".to_string(), + UploadCondition::IfMatch("\"old-etag\"".to_string()), + )]); + + apply_entry_upload_conditions(&mut uploads, &conditions) + .expect("metadata condition should be applied"); + + assert_eq!( + uploads[0].condition, + Some(UploadCondition::IfMatch("\"old-etag\"".to_string())) + ); } #[test] @@ -870,6 +1009,31 @@ mod tests { ); } + #[test] + fn apt_upload_order_places_binary_metadata_before_suite_release() { + let mut keys = [ + "ppa/genmeta/dists/genmeta/InRelease".to_string(), + "ppa/genmeta/dists/genmeta/Release.gpg".to_string(), + "ppa/genmeta/dists/genmeta/Release".to_string(), + "ppa/genmeta/dists/genmeta/main/binary-amd64/Packages".to_string(), + ]; + keys.sort_by(|left, right| { + apt_upload_order(left) + .cmp(&apt_upload_order(right)) + .then_with(|| left.cmp(right)) + }); + + assert_eq!( + keys, + [ + "ppa/genmeta/dists/genmeta/main/binary-amd64/Packages", + "ppa/genmeta/dists/genmeta/Release", + "ppa/genmeta/dists/genmeta/Release.gpg", + "ppa/genmeta/dists/genmeta/InRelease", + ] + ); + } + #[test] fn sign_suite_release_script_checks_fingerprint() { let script = sign_suite_release_script(&AptContainerOptions { diff --git a/xtask/src/publish/s3/plan.rs b/xtask/src/publish/s3/plan.rs index f524363..847e0b8 100644 --- a/xtask/src/publish/s3/plan.rs +++ b/xtask/src/publish/s3/plan.rs @@ -10,9 +10,10 @@ pub enum RemoteArtifactState { Present { sha256: String }, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum UploadCondition { IfMissing, + IfMatch(String), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -36,7 +37,7 @@ impl VersionedImmutablePayloadPlan { } pub fn upload_condition(&self) -> Option { - self.upload_condition + self.upload_condition.clone() } pub fn reuses_remote_payload(&self) -> bool { From 957559afb3199eccf4f3af8afe2f17c852b8f14d Mon Sep 17 00:00:00 2001 From: eareimu Date: Wed, 17 Jun 2026 13:44:57 +0800 Subject: [PATCH 3/4] fix(release): attach gmutils packages to github releases --- .github/workflows/release.yml | 113 ++++++++++++++++++++++++++++++---- xtask/src/main.rs | 33 ++++++++++ 2 files changed, 134 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed3be65..a7568ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,65 @@ env: DHTTP_CERT_SERVER_URL: ${{ vars.DHTTP_CERT_SERVER_URL }} jobs: + github-release: + name: GitHub Release + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Skip GitHub Release for dry-run + if: ${{ !(github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) }} + run: echo "not a v* tag; skipping GitHub Release creation" + + - name: Checkout tag + if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v') + uses: actions/checkout@v6 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Create GitHub Release + if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v') + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + tag_ref="refs/tags/$GITHUB_REF_NAME" + git fetch --force origin "$tag_ref:$tag_ref" + tag_object="$(git rev-parse "$tag_ref^{tag}" 2>/dev/null || true)" + target_commit="$(git rev-parse "$tag_ref^{commit}")" + notes_file="$RUNNER_TEMP/release-notes.md" + git for-each-ref "$tag_ref" --format='%(contents)' > "$notes_file" + if [ ! -s "$notes_file" ]; then + git log -1 --format=%B "$target_commit" > "$notes_file" + fi + { + printf '\n## Authentication and provenance\n\n' + printf -- '- Tag: `%s`\n' "$GITHUB_REF_NAME" + if [ -n "$tag_object" ]; then + printf -- '- Annotated tag object: `%s`\n' "$tag_object" + else + printf -- '- Tag object: lightweight tag\n' + fi + printf -- '- Target commit: `%s`\n' "$target_commit" + printf -- '- Workflow run: %s/%s/actions/runs/%s\n' \ + "$GITHUB_SERVER_URL" "$GITHUB_REPOSITORY" "$GITHUB_RUN_ID" + printf -- '- Workflow attempt: `%s`\n' "$GITHUB_RUN_ATTEMPT" + printf -- '- Published by: GitHub Actions `%s` workflow\n' "$GITHUB_WORKFLOW" + } >> "$notes_file" + + if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "github release $GITHUB_REF_NAME already exists" + exit 0 + fi + + gh release create "$GITHUB_REF_NAME" \ + --repo "$GITHUB_REPOSITORY" \ + --verify-tag \ + --title "$GITHUB_REF_NAME" \ + --notes-file "$notes_file" + linux-deb: + needs: github-release name: Linux deb packages and S3 publish runs-on: ubuntu-24.04 timeout-minutes: 240 @@ -162,9 +220,16 @@ jobs: echo "no deb release assets found" >&2 exit 1 fi - if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --notes-from-tag || gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null - fi + for attempt in {1..30}; do + if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + break + fi + if [ "$attempt" -eq 30 ]; then + echo "github release $GITHUB_REF_NAME was not visible after waiting" >&2 + exit 1 + fi + sleep 2 + done gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber --repo "$GITHUB_REPOSITORY" - name: Upload deb manifests @@ -177,6 +242,7 @@ jobs: gmutils/target/common/deb/** linux-rpm: + needs: github-release name: Linux rpm packages and S3 publish runs-on: ubuntu-24.04 timeout-minutes: 240 @@ -289,9 +355,16 @@ jobs: echo "no rpm release assets found" >&2 exit 1 fi - if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --notes-from-tag || gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null - fi + for attempt in {1..30}; do + if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + break + fi + if [ "$attempt" -eq 30 ]; then + echo "github release $GITHUB_REF_NAME was not visible after waiting" >&2 + exit 1 + fi + sleep 2 + done gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber --repo "$GITHUB_REPOSITORY" - name: Upload rpm manifests @@ -304,6 +377,7 @@ jobs: gmutils/target/common/rpm/** linux-scoop: + needs: github-release name: Linux scoop packages and S3 publish runs-on: ubuntu-24.04 timeout-minutes: 240 @@ -416,9 +490,16 @@ jobs: echo "no scoop release assets found" >&2 exit 1 fi - if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --notes-from-tag || gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null - fi + for attempt in {1..30}; do + if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + break + fi + if [ "$attempt" -eq 30 ]; then + echo "github release $GITHUB_REF_NAME was not visible after waiting" >&2 + exit 1 + fi + sleep 2 + done gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber --repo "$GITHUB_REPOSITORY" - name: Upload scoop manifests @@ -431,6 +512,7 @@ jobs: gmutils/target/common/scoop/** homebrew: + needs: github-release name: Homebrew package and S3 publish runs-on: macos-15 timeout-minutes: 120 @@ -577,9 +659,16 @@ jobs: echo "no Homebrew release assets found" >&2 exit 1 fi - if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --notes-from-tag || gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null - fi + for attempt in {1..30}; do + if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + break + fi + if [ "$attempt" -eq 30 ]; then + echo "github release $GITHUB_REF_NAME was not visible after waiting" >&2 + exit 1 + fi + sleep 2 + done gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber --repo "$GITHUB_REPOSITORY" - name: Upload Homebrew manifests diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 03f6bfe..d525d51 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -404,6 +404,39 @@ mod tests { ); } + #[test] + fn release_workflow_uploads_product_assets_to_github_release() { + assert!(RELEASE_WORKFLOW.contains("contents: write")); + assert!(RELEASE_WORKFLOW.contains(" github-release:")); + assert_eq!(RELEASE_WORKFLOW.matches("needs: github-release").count(), 4); + assert_eq!( + RELEASE_WORKFLOW + .matches("gh release upload \"$GITHUB_REF_NAME\"") + .count(), + 4 + ); + assert_eq!( + RELEASE_WORKFLOW + .matches("gh release create \"$GITHUB_REF_NAME\"") + .count(), + 1 + ); + assert!(RELEASE_WORKFLOW.contains("git for-each-ref \"$tag_ref\" --format='%(contents)'")); + assert!(RELEASE_WORKFLOW.contains("## Authentication and provenance")); + assert!(RELEASE_WORKFLOW.contains("--notes-file \"$notes_file\"")); + assert!(!RELEASE_WORKFLOW.contains("--notes-from-tag ||")); + assert!(RELEASE_WORKFLOW.contains("assets=(target/*/release/deb/*.deb)")); + assert!(RELEASE_WORKFLOW.contains("assets=(target/*/release/rpm/*.rpm)")); + assert!( + RELEASE_WORKFLOW + .contains("assets=(target/*/release/scoop/*.zip target/common/scoop/*.json)") + ); + assert!( + RELEASE_WORKFLOW + .contains("assets=(target/*/release/brew/*.tar.gz target/common/brew/*.rb)") + ); + } + #[test] fn public_package_manifests_declare_apache_2_license() { let workspace_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) From 944ba9e33ccb1ec23ad73dbe414660258657bd0e Mon Sep 17 00:00:00 2001 From: eareimu Date: Wed, 17 Jun 2026 14:18:42 +0800 Subject: [PATCH 4/4] fix(discover): use configured mdns service suffix --- genmeta-discover/src/lib.rs | 48 +++++++++++++++++++++++++++++++------ xtask/src/container.rs | 7 ++---- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/genmeta-discover/src/lib.rs b/genmeta-discover/src/lib.rs index 5e9e6c9..76c0ce8 100644 --- a/genmeta-discover/src/lib.rs +++ b/genmeta-discover/src/lib.rs @@ -15,7 +15,7 @@ use tracing_subscriber::prelude::*; #[derive(Parser, Debug, Clone)] #[command(name = "discover", version, about)] pub struct Options { - /// Domain name to discover (e.g. _genmeta.local) + /// Domain name to discover (e.g. _dhttp.local) #[arg(value_name = "DOMAIN", default_value = "")] domain: String, @@ -28,6 +28,17 @@ pub struct Options { #[derive(Debug, snafu::Snafu)] pub enum Error {} +fn domain_with_default_mdns_service(domain: &str) -> String { + if domain.is_empty() + || domain == DHTTP_MDNS_SERVICE + || domain.ends_with(&format!(".{DHTTP_MDNS_SERVICE}")) + { + domain.to_owned() + } else { + format!("{domain}.{DHTTP_MDNS_SERVICE}") + } +} + fn init_tracing() -> tracing_appender::non_blocking::WorkerGuard { let (stderr, guard) = tracing_appender::non_blocking(std::io::stderr()); tracing_subscriber::registry() @@ -58,12 +69,7 @@ pub async fn run(options: Options) -> Result<(), Error> { let resolvers = MdnsResolvers::bind(network, Arc::new(options.binds.clone()), DHTTP_MDNS_SERVICE).await; - // Auto-append ._genmeta.local suffix if not already present. - let with_suffix = if options.domain.is_empty() || options.domain.ends_with("._genmeta.local") { - options.domain.clone() - } else { - format!("{}._genmeta.local", options.domain) - }; + let with_suffix = domain_with_default_mdns_service(&options.domain); let matches_domain = |name: &str, domain: &str| { if domain.is_empty() { @@ -108,3 +114,31 @@ pub async fn run(options: Options) -> Result<(), Error> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::{DHTTP_MDNS_SERVICE, domain_with_default_mdns_service}; + + #[test] + fn domain_with_default_mdns_service_appends_configured_service_suffix() { + assert_eq!( + domain_with_default_mdns_service("reimu.pilot"), + format!("reimu.pilot.{DHTTP_MDNS_SERVICE}") + ); + } + + #[test] + fn domain_with_default_mdns_service_keeps_empty_and_full_names() { + assert_eq!(domain_with_default_mdns_service(""), ""); + assert_eq!( + domain_with_default_mdns_service(DHTTP_MDNS_SERVICE), + DHTTP_MDNS_SERVICE + ); + assert_eq!( + domain_with_default_mdns_service("_dhttp.local"), + format!("_dhttp.local.{DHTTP_MDNS_SERVICE}") + ); + let full_name = format!("reimu.pilot.{DHTTP_MDNS_SERVICE}"); + assert_eq!(domain_with_default_mdns_service(&full_name), full_name); + } +} diff --git a/xtask/src/container.rs b/xtask/src/container.rs index 347c3ca..10dc0e4 100644 --- a/xtask/src/container.rs +++ b/xtask/src/container.rs @@ -432,10 +432,7 @@ mod tests { "DHTTP_HTTP_DNS_SERVER".to_string(), "https://dns.genmeta.net".to_string(), ); - values.insert( - "DHTTP_MDNS_SERVICE".to_string(), - "_genmeta.local".to_string(), - ); + values.insert("DHTTP_MDNS_SERVICE".to_string(), "_dhttp.local".to_string()); values.insert( "DHTTP_CERT_SERVER_URL".to_string(), "https://license.test".to_string(), @@ -486,7 +483,7 @@ mod tests { assert!( bootstrap .exports - .contains("export DHTTP_MDNS_SERVICE='_genmeta.local'\n") + .contains("export DHTTP_MDNS_SERVICE='_dhttp.local'\n") ); assert!( bootstrap