Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 162 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- 'v*'

permissions:
contents: read
contents: write

concurrency:
group: release-${{ github.workflow }}-${{ github.ref }}
Expand All @@ -26,15 +26,73 @@ 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
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:
Expand Down Expand Up @@ -150,6 +208,30 @@ 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
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
if: always()
uses: actions/upload-artifact@v7
Expand All @@ -160,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
Expand Down Expand Up @@ -260,6 +343,30 @@ 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
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
if: always()
uses: actions/upload-artifact@v7
Expand All @@ -270,14 +377,15 @@ 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
env:
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
Expand Down Expand Up @@ -370,6 +478,30 @@ 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
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
if: always()
uses: actions/upload-artifact@v7
Expand All @@ -380,14 +512,15 @@ jobs:
gmutils/target/common/scoop/**

homebrew:
needs: github-release
name: Homebrew package and S3 publish
runs-on: macos-15
timeout-minutes: 120
env:
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:
Expand Down Expand Up @@ -514,6 +647,30 @@ 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
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
if: always()
uses: actions/upload-artifact@v7
Expand Down
48 changes: 41 additions & 7 deletions genmeta-discover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand All @@ -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()
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
}
7 changes: 2 additions & 5 deletions xtask/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
Loading