diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d133d87f1..b3ed4763c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,8 +23,50 @@ concurrency: cancel-in-progress: true jobs: + freebsd_port_trigger: + name: "FreeBSD Port / Trigger" + runs-on: ubuntu-22.04 + outputs: + run: ${{ steps.decide.outputs.run }} + version: ${{ steps.decide.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Decide whether the port job runs + id: decide + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + set -euo pipefail + run=false + case "$GITHUB_EVENT_NAME" in + push) + if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then + run=true + elif [[ "$GITHUB_REF" == refs/tags/* && "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + # The port tracks stable releases only, so a canary or rc tag has nothing to submit. + run=true + echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + fi + ;; + pull_request) + git fetch --depth=1 origin "$BASE_SHA" + if git diff --name-only "$BASE_SHA" HEAD | \ + grep -qE '^(release_files/freebsd-port-.*\.sh|\.github/workflows/release\.yml)$'; then + run=true + fi + ;; + esac + echo "run=$run" >> "$GITHUB_OUTPUT" + echo "FreeBSD port job runs: $run" + release_freebsd_port: name: "FreeBSD Port / Build & Test" + needs: freebsd_port_trigger + if: needs.freebsd_port_trigger.outputs.run == 'true' runs-on: ubuntu-22.04 steps: - name: Checkout @@ -33,18 +75,19 @@ jobs: persist-credentials: false - name: Generate FreeBSD port diff - run: bash -x release_files/freebsd-port-diff.sh - - - name: Generate FreeBSD port issue body - run: bash -x release_files/freebsd-port-issue-body.sh + env: + TARGET_VERSION: ${{ needs.freebsd_port_trigger.outputs.version }} + run: bash -x release_files/freebsd-port-diff.sh "$TARGET_VERSION" - name: Check if diff was generated id: check_diff run: | - if ls netbird-*.diff 1> /dev/null 2>&1; then - echo "diff_exists=true" >> $GITHUB_OUTPUT + shopt -s nullglob + diffs=(netbird-*.diff) + if [[ ${#diffs[@]} -gt 0 ]]; then + echo "diff_exists=true" >> "$GITHUB_OUTPUT" else - echo "diff_exists=false" >> $GITHUB_OUTPUT + echo "diff_exists=false" >> "$GITHUB_OUTPUT" echo "No diff file generated (port may already be up to date)" fi @@ -52,10 +95,18 @@ jobs: if: steps.check_diff.outputs.diff_exists == 'true' id: version run: | - VERSION=$(ls netbird-*.diff | sed 's/netbird-\(.*\)\.diff/\1/') - echo "version=$VERSION" >> $GITHUB_OUTPUT + diffs=(netbird-*.diff) + VERSION="${diffs[0]#netbird-}" + VERSION="${VERSION%.diff}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Generated files for version: $VERSION" - cat netbird-*.diff + cat "${diffs[0]}" + + - name: Generate FreeBSD port issue body + if: steps.check_diff.outputs.diff_exists == 'true' + env: + TARGET_VERSION: ${{ steps.version.outputs.version }} + run: bash -x release_files/freebsd-port-issue-body.sh "" "$TARGET_VERSION" - name: Read Go version from go.mod id: goversion diff --git a/release_files/freebsd-port-diff.sh b/release_files/freebsd-port-diff.sh index 77ea55520..cd3c341b9 100755 --- a/release_files/freebsd-port-diff.sh +++ b/release_files/freebsd-port-diff.sh @@ -9,62 +9,19 @@ # Usage: ./freebsd-port-diff.sh [new_version] # Example: ./freebsd-port-diff.sh 0.60.7 # -# If no version is provided, it fetches the latest from GitHub. +# If no version is provided, it uses the latest stable release from GitHub. +# Prerelease versions (canary, rc, ...) are rejected: the port only tracks +# stable releases. set -e -GITHUB_REPO="netbirdio/netbird" -PORTS_MIRROR_BASE="https://raw.githubusercontent.com/freebsd/freebsd-ports/main/security/netbird" +# shellcheck source=release_files/freebsd-port-lib.sh +source "$(dirname "${BASH_SOURCE[0]}")/freebsd-port-lib.sh" + GO_PROXY="https://proxy.golang.org/github.com/netbirdio/netbird/@v" OUTPUT_DIR="${OUTPUT_DIR:-.}" AWK_FIRST_FIELD='{print $1}' -fetch_all_tags() { - curl -sL "https://github.com/${GITHUB_REPO}/tags" 2>/dev/null | \ - grep -oE '/releases/tag/v[0-9]+\.[0-9]+\.[0-9]+([^"]+)?' | \ - grep -iv 'rc' | \ - sed 's/.*\/v//' | \ - sort -u -V - return 0 -} - -fetch_current_ports_version() { - echo "Fetching current version from FreeBSD ports..." >&2 - local makefile version - makefile=$(fetch_ports_file "Makefile") || return 1 - version=$(echo "$makefile" | \ - grep -E "^DISTVERSION=" | \ - sed 's/DISTVERSION=[[:space:]]*//' | \ - tr -d '\t ') - if [[ -z "$version" ]]; then - echo "Error: Could not extract DISTVERSION from ports Makefile" >&2 - return 1 - fi - echo "$version" - return 0 -} - -fetch_latest_github_release() { - echo "Fetching latest release from GitHub..." >&2 - fetch_all_tags | tail -1 - return 0 -} - -fetch_ports_file() { - local filename="$1" - local content - if ! content=$(curl -fsL --proto '=https' --proto-redir '=https' --retry 3 "${PORTS_MIRROR_BASE}/${filename}" 2>/dev/null); then - echo "Error: Could not fetch ${filename} from ${PORTS_MIRROR_BASE}" >&2 - return 1 - fi - if [[ "$content" == \<* ]]; then - echo "Error: Received HTML instead of ${filename} from ${PORTS_MIRROR_BASE}" >&2 - return 1 - fi - printf '%s' "$content" - return 0 -} - compute_checksums() { local version="$1" local tmpdir @@ -77,11 +34,15 @@ compute_checksums() { local mod_file="${tmpdir}/v${version}.mod" local zip_file="${tmpdir}/v${version}.zip" - curl -sL "${GO_PROXY}/v${version}.mod" -o "$mod_file" 2>/dev/null - curl -sL "${GO_PROXY}/v${version}.zip" -o "$zip_file" 2>/dev/null + local -a curl_opts=(-fsL --proto '=https' --proto-redir '=https' --retry 3) + if ! curl "${curl_opts[@]}" "${GO_PROXY}/v${version}.mod" -o "$mod_file" 2>/dev/null || \ + ! curl "${curl_opts[@]}" "${GO_PROXY}/v${version}.zip" -o "$zip_file" 2>/dev/null; then + echo "Error: Could not download v${version} from the Go module proxy" >&2 + return 1 + fi if [[ ! -s "$mod_file" ]] || [[ ! -s "$zip_file" ]]; then - echo "Error: Could not download files from Go module proxy" >&2 + echo "Error: Go module proxy returned empty files for v${version}" >&2 return 1 fi @@ -133,25 +94,12 @@ generate_new_makefile() { } # Parse arguments -NEW_VERSION="${1:-}" - -# Auto-detect versions if not provided -OLD_VERSION=$(fetch_current_ports_version) -if [[ -z "$OLD_VERSION" ]]; then - echo "Error: Could not fetch current version from FreeBSD ports" >&2 - exit 1 -fi -echo "Current FreeBSD ports version: ${OLD_VERSION}" >&2 - -if [[ -z "$NEW_VERSION" ]]; then - NEW_VERSION=$(fetch_latest_github_release) - if [[ -z "$NEW_VERSION" ]]; then - echo "Error: Could not fetch latest release from GitHub" >&2 - exit 1 - fi -fi +NEW_VERSION=$(resolve_target_version "${1:-}") echo "Target version: ${NEW_VERSION}" >&2 +OLD_VERSION=$(fetch_current_ports_version) +echo "Current FreeBSD ports version: ${OLD_VERSION}" >&2 + if [[ "$OLD_VERSION" = "$NEW_VERSION" ]]; then echo "Port is already at version ${NEW_VERSION}. Nothing to do." >&2 exit 0 diff --git a/release_files/freebsd-port-issue-body.sh b/release_files/freebsd-port-issue-body.sh index 1f0c8a567..16fe0e2b6 100755 --- a/release_files/freebsd-port-issue-body.sh +++ b/release_files/freebsd-port-issue-body.sh @@ -10,81 +10,29 @@ # # If no versions are provided, the script will: # - Fetch OLD version from the FreeBSD ports GitHub mirror (current version in ports tree) -# - Fetch NEW version from latest NetBird GitHub release tag +# - Fetch NEW version from the latest stable NetBird release tag on GitHub +# +# Prerelease versions (canary, rc, ...) are rejected: the port only tracks +# stable releases. set -e -GITHUB_REPO="netbirdio/netbird" -PORTS_MAKEFILE_URL="https://raw.githubusercontent.com/freebsd/freebsd-ports/main/security/netbird/Makefile" - -fetch_current_ports_version() { - echo "Fetching current version from FreeBSD ports..." >&2 - local makefile_content - makefile_content=$(curl -fsL --proto '=https' --proto-redir '=https' --retry 3 "$PORTS_MAKEFILE_URL" 2>/dev/null) || makefile_content="" - if [[ "$makefile_content" == \<* ]]; then - echo "Error: Received HTML instead of Makefile from ${PORTS_MAKEFILE_URL}" >&2 - return 1 - fi - if [[ -z "$makefile_content" ]]; then - echo "Error: Could not fetch Makefile from FreeBSD ports" >&2 - return 1 - fi - echo "$makefile_content" | grep -E "^DISTVERSION=" | sed 's/DISTVERSION=[[:space:]]*//' | tr -d '\t ' - return 0 -} - -fetch_all_tags() { - # Fetch tags from GitHub tags page (no rate limiting, no auth needed) - curl -sL "https://github.com/${GITHUB_REPO}/tags" 2>/dev/null | \ - grep -oE '/releases/tag/v[0-9]+\.[0-9]+\.[0-9]+([^"]+)?' | \ - grep -iv 'rc' | \ - sed 's/.*\/v//' | \ - sort -u -V - return 0 -} - -fetch_latest_github_release() { - echo "Fetching latest release from GitHub..." >&2 - local latest - - # Fetch from GitHub tags page - latest=$(fetch_all_tags | tail -1) - - if [[ -z "$latest" ]]; then - # Fallback to GitHub API - latest=$(curl -sL "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" 2>/dev/null | \ - grep '"tag_name"' | sed 's/.*"tag_name": *"v\([^"]*\)".*/\1/') - fi - - if [[ -z "$latest" ]]; then - echo "Error: Could not fetch latest release from GitHub" >&2 - return 1 - fi - echo "$latest" - return 0 -} +# shellcheck source=release_files/freebsd-port-lib.sh +source "$(dirname "${BASH_SOURCE[0]}")/freebsd-port-lib.sh" OLD_VERSION="${1:-}" NEW_VERSION="${2:-}" if [[ -z "$OLD_VERSION" ]]; then OLD_VERSION=$(fetch_current_ports_version) - if [[ -z "$OLD_VERSION" ]]; then - echo "Error: Could not determine old version. Please provide it manually." >&2 - echo "Usage: $0 " >&2 - exit 1 - fi echo "Detected OLD version from FreeBSD ports: $OLD_VERSION" >&2 fi if [[ -z "$NEW_VERSION" ]]; then - NEW_VERSION=$(fetch_latest_github_release) - if [[ -z "$NEW_VERSION" ]]; then - echo "Error: Could not determine new version. Please provide it manually." >&2 - echo "Usage: $0 " >&2 - exit 1 - fi + NEW_VERSION=$(resolve_target_version "") echo "Detected NEW version from GitHub: $NEW_VERSION" >&2 +else + NEW_VERSION=$(resolve_target_version "$NEW_VERSION") fi if [[ "$OLD_VERSION" = "$NEW_VERSION" ]]; then @@ -98,8 +46,11 @@ OUTPUT_DIR="${OUTPUT_DIR:-.}" fetch_releases_between_versions() { echo "Fetching release history from GitHub..." >&2 - # Fetch all tags and filter to those between OLD and NEW versions - fetch_all_tags | \ + local tags + tags=$(fetch_stable_tags) || return 1 + + # List the stable releases after OLD up to and including NEW + echo "$tags" | \ while read -r ver; do if [[ "$(printf '%s\n' "$OLD_VERSION" "$ver" | sort -V | head -n1)" = "$OLD_VERSION" ]] && \ [[ "$(printf '%s\n' "$ver" "$NEW_VERSION" | sort -V | head -n1)" = "$ver" ]] && \ @@ -112,7 +63,7 @@ fetch_releases_between_versions() { generate_changelog_section() { local releases - releases=$(fetch_releases_between_versions) + releases=$(fetch_releases_between_versions) || return 1 echo "Changelogs:" if [[ -n "$releases" ]]; then @@ -127,7 +78,10 @@ generate_changelog_section() { OUTPUT_FILE="${OUTPUT_DIR}/netbird-${NEW_VERSION}-issue.txt" -cat << EOF > "$OUTPUT_FILE" +# Built before the heredoc: a failing substitution inside it would not stop the script. +CHANGELOG=$(generate_changelog_section) + +cat << EOT > "$OUTPUT_FILE" BUGZILLA ISSUE DETAILS ====================== @@ -139,11 +93,11 @@ Description: ------------ security/netbird: Update ${OLD_VERSION} => ${NEW_VERSION} -$(generate_changelog_section) +${CHANGELOG} Commit log: https://github.com/${GITHUB_REPO}/compare/v${OLD_VERSION}...v${NEW_VERSION} -EOF +EOT echo "=========================================" echo "Issue body saved to: ${OUTPUT_FILE}" diff --git a/release_files/freebsd-port-lib.sh b/release_files/freebsd-port-lib.sh new file mode 100644 index 000000000..20102be54 --- /dev/null +++ b/release_files/freebsd-port-lib.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# +# Shared helpers for the FreeBSD port scripts. Source this file, do not run it. +# +# The FreeBSD port tracks stable NetBird releases only, so every helper here +# works on plain MAJOR.MINOR.PATCH versions and ignores canary, release +# candidate and any other prerelease tag. + +GITHUB_REPO="netbirdio/netbird" +GITHUB_REPO_URL="https://github.com/${GITHUB_REPO}.git" +PORTS_MIRROR_BASE="https://raw.githubusercontent.com/freebsd/freebsd-ports/main/security/netbird" +STABLE_VERSION_RE='^[0-9]+\.[0-9]+\.[0-9]+$' + +# is_stable_version reports whether the version is a plain MAJOR.MINOR.PATCH release. +is_stable_version() { + local version="$1" + if [[ "$version" =~ $STABLE_VERSION_RE ]]; then + return 0 + fi + return 1 +} + +# fetch_stable_tags prints the stable release versions known to GitHub, oldest +# first. git ls-remote returns every tag in one round trip, with no API +# pagination or rate limit to work around. +fetch_stable_tags() { + local refs + if ! refs=$(git ls-remote --tags --refs "$GITHUB_REPO_URL" 2>/dev/null); then + echo "Error: Could not list tags of ${GITHUB_REPO_URL}" >&2 + return 1 + fi + + echo "$refs" | \ + sed -nE 's#.*refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)$#\1#p' | \ + sort -u -V + return 0 +} + +# resolve_target_version validates an explicitly requested version, or picks the +# latest stable release from GitHub when none was given. A leading "v" is +# accepted so a git tag name can be passed as is. +resolve_target_version() { + local raw="$1" + local requested="${raw#v}" + + if [[ -n "$raw" ]]; then + if ! is_stable_version "$requested"; then + echo "Error: '${raw}' is not a stable release. The FreeBSD port only tracks MAJOR.MINOR.PATCH versions." >&2 + return 1 + fi + echo "$requested" + return 0 + fi + + echo "Fetching latest stable release from GitHub..." >&2 + local latest + latest=$(fetch_stable_tags | tail -1) + if [[ -z "$latest" ]]; then + echo "Error: Could not determine the latest stable release from GitHub" >&2 + return 1 + fi + echo "$latest" + return 0 +} + +# fetch_ports_file prints a file from the security/netbird port on the FreeBSD +# ports GitHub mirror. +fetch_ports_file() { + local filename="$1" + local content + if ! content=$(curl -fsL --proto '=https' --proto-redir '=https' --retry 3 "${PORTS_MIRROR_BASE}/${filename}" 2>/dev/null); then + echo "Error: Could not fetch ${filename} from ${PORTS_MIRROR_BASE}" >&2 + return 1 + fi + if [[ "$content" == \<* ]]; then + echo "Error: Received HTML instead of ${filename} from ${PORTS_MIRROR_BASE}" >&2 + return 1 + fi + printf '%s' "$content" + return 0 +} + +# fetch_current_ports_version prints the DISTVERSION currently in the ports tree. +fetch_current_ports_version() { + echo "Fetching current version from FreeBSD ports..." >&2 + local makefile version + makefile=$(fetch_ports_file "Makefile") || return 1 + version=$(echo "$makefile" | \ + grep -E "^DISTVERSION=" | \ + sed 's/DISTVERSION=[[:space:]]*//' | \ + tr -d '\t ') + if [[ -z "$version" ]]; then + echo "Error: Could not extract DISTVERSION from ports Makefile" >&2 + return 1 + fi + echo "$version" + return 0 +}