* fix(release): normalize development package versions * ci: build only the rustfs release binary --------- Co-authored-by: Zhengchao An <[email protected]>
636 lines
25 KiB
YAML
636 lines
25 KiB
YAML
# Copyright 2024 RustFS Team
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# Package Workflow - Build DEB/RPM packages
|
|
#
|
|
# This workflow builds DEB and RPM packages from pre-built Linux binaries
|
|
# and uploads them to Cloudflare R2 and the GitHub release.
|
|
#
|
|
# Trigger:
|
|
# - workflow_run: automatically package after "Build and Release" completes
|
|
# for a release tag (the mac/windows/linux binaries are already uploaded
|
|
# to the GitHub release before packaging starts)
|
|
# - workflow_dispatch: manual fallback with a release tag and/or exact build run ID
|
|
#
|
|
# Flow:
|
|
# 1. Resolve and validate the selected Build workflow run and source identity
|
|
# 2. Download Linux binaries (x86_64-gnu, aarch64-gnu) from build artifacts
|
|
# 3. Build DEB packages for amd64 and arm64
|
|
# 4. Build RPM packages for x86_64 and aarch64
|
|
# 5. Upload all packages to Cloudflare R2 and the GitHub release
|
|
|
|
name: Package DEB/RPM
|
|
|
|
permissions:
|
|
# contents: write is required to upload packages to the GitHub release
|
|
contents: write
|
|
actions: read
|
|
|
|
on:
|
|
# Follows the same pattern as docker.yml: run after the release build
|
|
# workflow completes, so packaging is triggered only by release tags
|
|
# (e.g. 1.0.0-rc.2, 1.0.0-rc.3), never by development builds.
|
|
workflow_run:
|
|
workflows: [ "Build and Release" ]
|
|
types: [ completed ]
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: "Release tag to package (e.g. 1.0.0-beta.12). Leave empty for latest main build."
|
|
required: false
|
|
type: string
|
|
build_run_id:
|
|
description: "Build workflow run ID (when combined with tag, both must identify the same release commit)"
|
|
required: false
|
|
type: string
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.event.inputs.tag || github.run_id }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
|
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
|
|
|
jobs:
|
|
# Resolve which build run to use and extract version info
|
|
resolve:
|
|
name: Resolve Build
|
|
# Auto-trigger only from successful tag builds of "Build and Release".
|
|
# Tag pushes arrive as event == push with head_branch != main (a
|
|
# non-main push head_branch is the release tag name). Manual dispatch
|
|
# stays available as a fallback for backfills and re-runs.
|
|
if: >-
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(github.event.workflow_run.conclusion == 'success' &&
|
|
github.event.workflow_run.event == 'push' &&
|
|
github.event.workflow_run.head_branch != 'main')
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
outputs:
|
|
version: ${{ steps.resolve.outputs.version }}
|
|
build_type: ${{ steps.resolve.outputs.build_type }}
|
|
build_run_id: ${{ steps.resolve.outputs.build_run_id }}
|
|
build_run_number: ${{ steps.resolve.outputs.build_run_number }}
|
|
head_sha: ${{ steps.resolve.outputs.head_sha }}
|
|
dev_sequence: ${{ steps.resolve.outputs.dev_sequence }}
|
|
tag: ${{ steps.resolve.outputs.tag }}
|
|
steps:
|
|
- name: Resolve build run
|
|
id: resolve
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
INPUT_TAG: ${{ github.event.inputs.tag }}
|
|
INPUT_RUN_ID: ${{ github.event.inputs.build_run_id }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
fail() {
|
|
echo "❌ $1" >&2
|
|
exit 1
|
|
}
|
|
|
|
TAG=""
|
|
BUILD_RUN_ID=""
|
|
case "$EVENT_NAME" in
|
|
workflow_run)
|
|
TAG="$HEAD_BRANCH"
|
|
BUILD_RUN_ID="$WORKFLOW_RUN_ID"
|
|
;;
|
|
workflow_dispatch)
|
|
TAG="$INPUT_TAG"
|
|
BUILD_RUN_ID="$INPUT_RUN_ID"
|
|
;;
|
|
*) fail "unsupported event: $EVENT_NAME" ;;
|
|
esac
|
|
|
|
# Validate and classify tags before using them in API paths or logs.
|
|
semver_core='(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)'
|
|
prerelease_id='(alpha|beta|rc)\.(0|[1-9][0-9]*)'
|
|
if [[ -n "$TAG" ]]; then
|
|
if [[ "$TAG" =~ ^${semver_core}-${prerelease_id}-preview\.(0|[1-9][0-9]*)$ ]]; then
|
|
BUILD_TYPE=preview
|
|
elif [[ "$TAG" =~ ^${semver_core}-${prerelease_id}$ ]]; then
|
|
BUILD_TYPE=prerelease
|
|
elif [[ "$TAG" =~ ^${semver_core}$ ]]; then
|
|
BUILD_TYPE=release
|
|
else
|
|
fail "tag is not a supported strict package version"
|
|
fi
|
|
else
|
|
BUILD_TYPE=development
|
|
fi
|
|
|
|
if [[ -n "$BUILD_RUN_ID" ]]; then
|
|
[[ "$BUILD_RUN_ID" =~ ^[1-9][0-9]*$ ]] || fail "build run ID must be a positive decimal integer"
|
|
echo "Using selected build run: $BUILD_RUN_ID"
|
|
elif [[ -n "$TAG" ]]; then
|
|
echo "Looking for build run for tag: $TAG"
|
|
BUILD_RUN_ID=$(gh api --method GET \
|
|
"repos/${REPOSITORY}/actions/workflows/build.yml/runs" \
|
|
-f branch="$TAG" -f status=success -F per_page=1 \
|
|
--jq '.workflow_runs[0].id // empty' 2>/dev/null || true)
|
|
|
|
if [[ -z "$BUILD_RUN_ID" ]]; then
|
|
BUILD_RUN_ID=$(gh api --method GET \
|
|
"repos/${REPOSITORY}/actions/workflows/build.yml/runs" \
|
|
-f event=push -f status=success -F per_page=100 2>/dev/null |
|
|
jq -r --arg tag "$TAG" \
|
|
'[.workflow_runs[] | select(.head_branch == $tag)][0].id // empty' || true)
|
|
fi
|
|
[[ "$BUILD_RUN_ID" =~ ^[1-9][0-9]*$ ]] || fail "no successful build run found for tag"
|
|
echo "Found build run: $BUILD_RUN_ID"
|
|
else
|
|
echo "No tag specified, looking for latest main build"
|
|
BUILD_RUN_ID=$(gh api --method GET \
|
|
"repos/${REPOSITORY}/actions/workflows/build.yml/runs" \
|
|
-f branch=main -f status=success -F per_page=1 \
|
|
--jq '.workflow_runs[0].id // empty' 2>/dev/null || true)
|
|
[[ "$BUILD_RUN_ID" =~ ^[1-9][0-9]*$ ]] || fail "no successful main build found"
|
|
echo "Latest main build: $BUILD_RUN_ID"
|
|
fi
|
|
|
|
# Fetch once and use the same immutable run metadata for identity,
|
|
# ordering, workflow provenance, and release-channel validation.
|
|
RUN_JSON=$(gh api "repos/${REPOSITORY}/actions/runs/${BUILD_RUN_ID}") ||
|
|
fail "cannot read selected build run"
|
|
RUN_ID=$(jq -r '.id // empty' <<<"$RUN_JSON")
|
|
RUN_NUMBER=$(jq -r '.run_number // empty' <<<"$RUN_JSON")
|
|
RUN_STATUS=$(jq -r '.status // empty' <<<"$RUN_JSON")
|
|
RUN_CONCLUSION=$(jq -r '.conclusion // empty' <<<"$RUN_JSON")
|
|
RUN_PATH=$(jq -r '.path // empty' <<<"$RUN_JSON")
|
|
HEAD_SHA=$(jq -r '.head_sha // empty' <<<"$RUN_JSON")
|
|
RUN_HEAD_BRANCH=$(jq -r '.head_branch // empty' <<<"$RUN_JSON")
|
|
|
|
[[ "$RUN_ID" == "$BUILD_RUN_ID" ]] || fail "run metadata ID mismatch"
|
|
[[ "$RUN_NUMBER" =~ ^[1-9][0-9]*$ ]] || fail "build run number must be a positive decimal integer"
|
|
[[ "$RUN_STATUS" == completed && "$RUN_CONCLUSION" == success ]] || fail "selected build run is not successful"
|
|
[[ "$RUN_PATH" == .github/workflows/build.yml ]] || fail "selected run is not Build and Release"
|
|
[[ "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] || fail "selected build run has an invalid head SHA"
|
|
[[ "$RUN_HEAD_BRANCH" != *$'\n'* && -n "$RUN_HEAD_BRANCH" ]] || fail "selected build run has an invalid head branch"
|
|
|
|
if [[ -n "$TAG" ]]; then
|
|
[[ "$RUN_HEAD_BRANCH" == "$TAG" ]] || fail "tag and build run head branch do not match"
|
|
|
|
TAG_REF_JSON=$(gh api "repos/${REPOSITORY}/git/ref/tags/${TAG}") ||
|
|
fail "cannot resolve release tag ref"
|
|
TAG_OBJECT_TYPE=$(jq -r '.object.type // empty' <<<"$TAG_REF_JSON")
|
|
TAG_OBJECT_SHA=$(jq -r '.object.sha // empty' <<<"$TAG_REF_JSON")
|
|
depth=0
|
|
while [[ "$TAG_OBJECT_TYPE" == tag && $depth -lt 5 ]]; do
|
|
TAG_OBJECT_JSON=$(gh api "repos/${REPOSITORY}/git/tags/${TAG_OBJECT_SHA}") ||
|
|
fail "cannot peel annotated release tag"
|
|
TAG_OBJECT_TYPE=$(jq -r '.object.type // empty' <<<"$TAG_OBJECT_JSON")
|
|
TAG_OBJECT_SHA=$(jq -r '.object.sha // empty' <<<"$TAG_OBJECT_JSON")
|
|
depth=$((depth + 1))
|
|
done
|
|
[[ "$TAG_OBJECT_TYPE" == commit && "$TAG_OBJECT_SHA" =~ ^[0-9a-f]{40}$ ]] ||
|
|
fail "release tag does not resolve to a commit"
|
|
[[ "$TAG_OBJECT_SHA" == "$HEAD_SHA" ]] || fail "release tag commit and build run head SHA do not match"
|
|
VERSION="$TAG"
|
|
DEV_SEQUENCE=""
|
|
else
|
|
VERSION="dev-${HEAD_SHA}"
|
|
DEV_SEQUENCE="$RUN_NUMBER"
|
|
fi
|
|
|
|
{
|
|
echo "version=$VERSION"
|
|
echo "build_type=$BUILD_TYPE"
|
|
echo "build_run_id=$BUILD_RUN_ID"
|
|
echo "build_run_number=$RUN_NUMBER"
|
|
echo "head_sha=$HEAD_SHA"
|
|
echo "dev_sequence=$DEV_SEQUENCE"
|
|
echo "tag=${TAG}"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
echo "📊 Resolved:"
|
|
echo " Version: $VERSION"
|
|
echo " Build type: $BUILD_TYPE"
|
|
echo " Build run ID: $BUILD_RUN_ID"
|
|
echo " Build run number: $RUN_NUMBER"
|
|
|
|
# Build DEB and RPM packages for each architecture
|
|
package:
|
|
name: Package (${{ matrix.arch }})
|
|
needs: resolve
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
strategy:
|
|
fail-fast: false
|
|
max-parallel: 1
|
|
matrix:
|
|
include:
|
|
- arch: x86_64
|
|
deb_arch: amd64
|
|
rpm_arch: x86_64
|
|
artifact_name: "rustfs-linux-x86_64-gnu"
|
|
- arch: aarch64
|
|
deb_arch: arm64
|
|
rpm_arch: aarch64
|
|
artifact_name: "rustfs-linux-aarch64-gnu"
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Normalize package metadata
|
|
id: versions
|
|
shell: bash
|
|
env:
|
|
BUILD_TYPE: ${{ needs.resolve.outputs.build_type }}
|
|
SOURCE_VERSION: ${{ needs.resolve.outputs.version }}
|
|
DEV_SEQUENCE: ${{ needs.resolve.outputs.dev_sequence }}
|
|
DEB_ARCH: ${{ matrix.deb_arch }}
|
|
RPM_ARCH: ${{ matrix.rpm_arch }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
normalized=$(./scripts/release/package_versions.sh \
|
|
"$BUILD_TYPE" "$SOURCE_VERSION" "$DEV_SEQUENCE" "$DEB_ARCH" "$RPM_ARCH")
|
|
printf '%s\n' "$normalized" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Download binary artifact from build run
|
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
|
with:
|
|
pattern: ${{ matrix.artifact_name }}*
|
|
path: ./binary-artifact
|
|
run-id: ${{ needs.resolve.outputs.build_run_id }}
|
|
github-token: ${{ github.token }}
|
|
merge-multiple: true
|
|
|
|
- name: Extract binary
|
|
id: binary
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
ZIP_FILE=$(find ./binary-artifact -name "*.zip" -type f | head -1)
|
|
if [[ -z "$ZIP_FILE" ]]; then
|
|
echo "❌ No binary artifact found"
|
|
find ./binary-artifact -mindepth 1 -maxdepth 1 -print 2>/dev/null || true
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found artifact: $ZIP_FILE"
|
|
|
|
mkdir -p ./bin
|
|
unzip -o "$ZIP_FILE" -d ./bin
|
|
|
|
if [[ ! -f ./bin/rustfs ]]; then
|
|
echo "❌ rustfs binary not found in archive"
|
|
exit 1
|
|
fi
|
|
|
|
chmod +x ./bin/rustfs
|
|
stat --printf='%n %s bytes\n' ./bin/rustfs
|
|
echo "✅ Binary extracted"
|
|
|
|
- name: Build DEB package
|
|
id: deb
|
|
shell: bash
|
|
env:
|
|
DEB_VERSION: ${{ steps.versions.outputs.deb_version }}
|
|
DEB_ARCH: ${{ matrix.deb_arch }}
|
|
DEB_FILE: ${{ steps.versions.outputs.deb_file }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
PKG_DIR="${DEB_FILE%.deb}"
|
|
|
|
echo "Building DEB: ${DEB_FILE}"
|
|
|
|
mkdir -p "${PKG_DIR}/DEBIAN"
|
|
mkdir -p "${PKG_DIR}/usr/bin"
|
|
mkdir -p "${PKG_DIR}/etc/default"
|
|
mkdir -p "${PKG_DIR}/lib/systemd/system"
|
|
mkdir -p "${PKG_DIR}/usr/share/doc/rustfs"
|
|
|
|
cp ./bin/rustfs "${PKG_DIR}/usr/bin/"
|
|
chmod 755 "${PKG_DIR}/usr/bin/rustfs"
|
|
|
|
cp deploy/build/rustfs.service "${PKG_DIR}/lib/systemd/system/"
|
|
|
|
cat > "${PKG_DIR}/etc/default/rustfs" << 'ENVEOF'
|
|
# RustFS Environment Configuration
|
|
# See https://rustfs.com/docs/ for more information
|
|
# RUSTFS_VOLUMES=""
|
|
# RUSTFS_ROOT_USER=""
|
|
# RUSTFS_ROOT_PASSWORD=""
|
|
ENVEOF
|
|
|
|
cat > "${PKG_DIR}/DEBIAN/control" << EOF
|
|
Package: rustfs
|
|
Version: ${DEB_VERSION}
|
|
Section: utils
|
|
Priority: optional
|
|
Architecture: ${DEB_ARCH}
|
|
Depends: libc6 (>= 2.31)
|
|
Maintainer: RustFS Team <[email protected]>
|
|
Description: High-performance distributed object storage
|
|
RustFS is a high-performance distributed object storage software
|
|
built using Rust. It is compatible with MinIO and S3 API.
|
|
Homepage: https://rustfs.com
|
|
EOF
|
|
|
|
# Declare /etc/default/rustfs as a conffile so dpkg preserves user
|
|
# modifications on upgrade instead of silently overwriting them.
|
|
cat > "${PKG_DIR}/DEBIAN/conffiles" << 'CONFFILES'
|
|
/etc/default/rustfs
|
|
CONFFILES
|
|
|
|
cat > "${PKG_DIR}/DEBIAN/postinst" << 'POSTINST'
|
|
#!/bin/bash
|
|
set -e
|
|
if ! getent passwd rustfs > /dev/null 2>&1; then
|
|
useradd -r -s /bin/false -d /opt/rustfs rustfs
|
|
fi
|
|
mkdir -p /opt/rustfs /data/rustfs /var/log/rustfs
|
|
chown rustfs:rustfs /opt/rustfs /data/rustfs /var/log/rustfs
|
|
if [ -d /run/systemd/system ]; then
|
|
systemctl daemon-reload
|
|
fi
|
|
echo "RustFS installed. Configure /etc/default/rustfs then: systemctl start rustfs"
|
|
POSTINST
|
|
chmod 755 "${PKG_DIR}/DEBIAN/postinst"
|
|
|
|
cat > "${PKG_DIR}/DEBIAN/prerm" << 'PRERM'
|
|
#!/bin/bash
|
|
set -e
|
|
if [ -d /run/systemd/system ] && systemctl is-active --quiet rustfs; then
|
|
systemctl stop rustfs
|
|
fi
|
|
PRERM
|
|
chmod 755 "${PKG_DIR}/DEBIAN/prerm"
|
|
|
|
cat > "${PKG_DIR}/DEBIAN/postrm" << 'POSTRM'
|
|
#!/bin/bash
|
|
set -e
|
|
if [ -d /run/systemd/system ]; then
|
|
systemctl daemon-reload
|
|
fi
|
|
POSTRM
|
|
chmod 755 "${PKG_DIR}/DEBIAN/postrm"
|
|
|
|
cp LICENSE "${PKG_DIR}/usr/share/doc/rustfs/"
|
|
cp README.md "${PKG_DIR}/usr/share/doc/rustfs/"
|
|
|
|
fakeroot dpkg-deb --build "${PKG_DIR}" "$DEB_FILE"
|
|
|
|
[[ $(dpkg-deb -f "$DEB_FILE" Package) == rustfs ]]
|
|
[[ $(dpkg-deb -f "$DEB_FILE" Version) == "$DEB_VERSION" ]]
|
|
[[ $(dpkg-deb -f "$DEB_FILE" Architecture) == "$DEB_ARCH" ]]
|
|
dpkg-deb --fsys-tarfile "$DEB_FILE" | tar -tf - | grep -Fx './usr/bin/rustfs' >/dev/null
|
|
stat --printf='%n %s bytes\n' "$DEB_FILE"
|
|
echo "deb_file=$DEB_FILE" >> "$GITHUB_OUTPUT"
|
|
echo "✅ DEB built: $DEB_FILE"
|
|
|
|
- name: Build RPM package
|
|
id: rpm
|
|
shell: bash
|
|
env:
|
|
RPM_VERSION: ${{ steps.versions.outputs.rpm_version }}
|
|
RPM_RELEASE: ${{ steps.versions.outputs.rpm_release }}
|
|
RPM_ARCH: ${{ matrix.rpm_arch }}
|
|
RPM_FILE: ${{ steps.versions.outputs.rpm_file }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
echo "Building RPM for ${RPM_ARCH}"
|
|
|
|
sudo apt-get update && sudo apt-get install -y ruby ruby-dev build-essential rpm
|
|
sudo gem install fpm
|
|
./scripts/test_package_versions.sh --require-package-managers
|
|
|
|
# Create config file for fpm (DEB build creates it in its package dir structure,
|
|
# but fpm needs the file to exist before packaging)
|
|
mkdir -p ./tmp-pkg/etc/default
|
|
cat > ./tmp-pkg/etc/default/rustfs << 'ENVEOF'
|
|
# RustFS Environment Configuration
|
|
# See https://rustfs.com/docs/ for more information
|
|
# RUSTFS_VOLUMES=""
|
|
# RUSTFS_ROOT_USER=""
|
|
# RUSTFS_ROOT_PASSWORD=""
|
|
ENVEOF
|
|
|
|
fpm -s dir -t rpm \
|
|
--name rustfs \
|
|
--version "$RPM_VERSION" \
|
|
--iteration "$RPM_RELEASE" \
|
|
--architecture "$RPM_ARCH" \
|
|
--package "$RPM_FILE" \
|
|
--depends "glibc >= 2.31" \
|
|
--maintainer "RustFS Team <[email protected]>" \
|
|
--description "High-performance distributed object storage" \
|
|
--url "https://rustfs.com" \
|
|
--license "Apache-2.0" \
|
|
--after-install <(cat <<'POSTINST'
|
|
#!/bin/bash
|
|
set -e
|
|
if ! getent passwd rustfs > /dev/null 2>&1; then
|
|
useradd -r -s /bin/false -d /opt/rustfs rustfs
|
|
fi
|
|
mkdir -p /opt/rustfs /data/rustfs /var/log/rustfs
|
|
chown rustfs:rustfs /opt/rustfs /data/rustfs /var/log/rustfs
|
|
if [ -d /run/systemd/system ]; then
|
|
systemctl daemon-reload
|
|
fi
|
|
POSTINST
|
|
) \
|
|
--before-remove <(cat <<'PRERM'
|
|
#!/bin/bash
|
|
set -e
|
|
if [ -d /run/systemd/system ] && systemctl is-active --quiet rustfs; then
|
|
systemctl stop rustfs
|
|
fi
|
|
PRERM
|
|
) \
|
|
--after-remove <(cat <<'POSTRM'
|
|
#!/bin/bash
|
|
set -e
|
|
if [ -d /run/systemd/system ]; then
|
|
systemctl daemon-reload
|
|
fi
|
|
POSTRM
|
|
) \
|
|
--config-files /etc/default/rustfs \
|
|
./bin/rustfs=/usr/bin/rustfs \
|
|
./tmp-pkg/etc/default/rustfs=/etc/default/rustfs \
|
|
deploy/build/rustfs.service=/lib/systemd/system/rustfs.service \
|
|
LICENSE=/usr/share/doc/rustfs/LICENSE \
|
|
README.md=/usr/share/doc/rustfs/README.md
|
|
|
|
if [[ ! -f "$RPM_FILE" ]]; then
|
|
echo "❌ RPM build failed"
|
|
exit 1
|
|
fi
|
|
|
|
RPM_METADATA=$(rpm -qp --qf '%{NAME}\n%{VERSION}\n%{RELEASE}\n%{ARCH}\n' "$RPM_FILE")
|
|
EXPECTED_METADATA=$(printf 'rustfs\n%s\n%s\n%s' "$RPM_VERSION" "$RPM_RELEASE" "$RPM_ARCH")
|
|
[[ "$RPM_METADATA" == "$EXPECTED_METADATA" ]]
|
|
rpm -qpl "$RPM_FILE" | grep -Fx '/usr/bin/rustfs' >/dev/null
|
|
stat --printf='%n %s bytes\n' "$RPM_FILE"
|
|
echo "rpm_file=$RPM_FILE" >> "$GITHUB_OUTPUT"
|
|
echo "✅ RPM built: $RPM_FILE"
|
|
|
|
- name: Upload packages to artifacts
|
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
|
with:
|
|
name: packages-${{ matrix.arch }}
|
|
path: |
|
|
*.deb
|
|
*.rpm
|
|
retention-days: 30
|
|
|
|
- name: Upload packages to Cloudflare R2
|
|
if: env.R2_ACCESS_KEY_ID != ''
|
|
env:
|
|
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
|
|
R2_BUCKET: ${{ secrets.R2_BUCKET }}
|
|
AWS_EC2_METADATA_DISABLED: true
|
|
BUILD_TYPE: ${{ needs.resolve.outputs.build_type }}
|
|
DEB_FILE: ${{ steps.deb.outputs.deb_file }}
|
|
RPM_FILE: ${{ steps.rpm.outputs.rpm_file }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [[ -z "$R2_ACCESS_KEY_ID" || -z "$R2_SECRET_ACCESS_KEY" || -z "$R2_ENDPOINT" || -z "$R2_BUCKET" ]]; then
|
|
echo "⚠️ R2 credentials missing, skipping upload"
|
|
exit 0
|
|
fi
|
|
|
|
if ! command -v aws >/dev/null 2>&1; then
|
|
sudo apt-get update && sudo apt-get install -y awscli
|
|
fi
|
|
|
|
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY_ID"
|
|
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY"
|
|
export AWS_DEFAULT_REGION="auto"
|
|
|
|
if [[ "$BUILD_TYPE" == "development" ]]; then
|
|
R2_PREFIX="artifacts/rustfs/packages/dev"
|
|
else
|
|
R2_PREFIX="artifacts/rustfs/packages/release"
|
|
fi
|
|
R2_PATH="s3://${R2_BUCKET}/${R2_PREFIX}/"
|
|
|
|
echo "📤 Uploading to $R2_PATH"
|
|
|
|
for f in "$DEB_FILE" "$RPM_FILE"; do
|
|
if [[ -n "$f" && -f "$f" ]]; then
|
|
echo "Uploading: $f"
|
|
aws s3 cp "$f" "$R2_PATH" --endpoint-url "$R2_ENDPOINT" --only-show-errors
|
|
fi
|
|
done
|
|
|
|
echo "✅ Upload complete"
|
|
|
|
# Also upload as latest for release/prerelease
|
|
if [[ "$BUILD_TYPE" == "release" || "$BUILD_TYPE" == "prerelease" ]]; then
|
|
LATEST_PATH="s3://${R2_BUCKET}/artifacts/rustfs/packages/latest/"
|
|
for f in "$DEB_FILE" "$RPM_FILE"; do
|
|
if [[ -n "$f" && -f "$f" ]]; then
|
|
echo "Uploading latest: $(basename "$f")"
|
|
aws s3 cp "$f" "$LATEST_PATH" --endpoint-url "$R2_ENDPOINT" --only-show-errors
|
|
fi
|
|
done
|
|
echo "✅ Latest packages updated"
|
|
fi
|
|
|
|
- name: Upload packages to GitHub Release
|
|
if: needs.resolve.outputs.tag != ''
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TAG: ${{ needs.resolve.outputs.tag }}
|
|
DEB_FILE: ${{ steps.deb.outputs.deb_file }}
|
|
RPM_FILE: ${{ steps.rpm.outputs.rpm_file }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
# Upload the packages, then refresh the release checksums so the new
|
|
# assets are covered, matching the binary release flow.
|
|
for f in "$DEB_FILE" "$RPM_FILE"; do
|
|
if [[ -n "$f" && -f "$f" ]]; then
|
|
echo "📤 Uploading $(basename "$f") to GitHub release ${TAG}..."
|
|
gh release upload "$TAG" "$f" --clobber
|
|
fi
|
|
done
|
|
|
|
CHECKSUM_DIR="$(mktemp -d)"
|
|
gh release download "$TAG" -p 'SHA256SUMS' -p 'SHA512SUMS' \
|
|
-D "$CHECKSUM_DIR" --clobber
|
|
|
|
for spec in "SHA256SUMS:sha256sum" "SHA512SUMS:sha512sum"; do
|
|
asset="${spec%%:*}"
|
|
checksum_cmd="${spec##*:}"
|
|
checksum_file="${CHECKSUM_DIR}/${asset}"
|
|
|
|
for f in "$DEB_FILE" "$RPM_FILE"; do
|
|
if [[ -n "$f" && -f "$f" ]]; then
|
|
base="$(basename "$f")"
|
|
# GitHub stores release asset names with '~' normalized to '.'
|
|
# (e.g. rustfs_1.0.0~rc.2_amd64.deb is stored as
|
|
# rustfs_1.0.0.rc.2_amd64.deb), so checksum entries must
|
|
# reference the name as stored on the release.
|
|
github_base="${base//\~/.}"
|
|
# Remove any stale entry (both naming variants), then append
|
|
grep -Fv -- "$base" "$checksum_file" > "${checksum_file}.tmp" || true
|
|
grep -Fv -- "$github_base" "${checksum_file}.tmp" > "${checksum_file}.tmp2" || true
|
|
mv "${checksum_file}.tmp2" "$checksum_file"
|
|
digest=$("$checksum_cmd" -- "$f" | awk '{print $1}')
|
|
printf '%s %s\n' "$digest" "$github_base" >> "$checksum_file"
|
|
fi
|
|
done
|
|
|
|
echo "📤 Updating ${asset} for release ${TAG}..."
|
|
gh release upload "$TAG" "$checksum_file" --clobber
|
|
done
|
|
|
|
echo "✅ GitHub release assets updated"
|
|
|
|
# Summary
|
|
summary:
|
|
name: Summary
|
|
needs: [ resolve, package ]
|
|
if: always()
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Print summary
|
|
shell: bash
|
|
env:
|
|
SUMMARY_VERSION: ${{ needs.resolve.outputs.version }}
|
|
SUMMARY_BUILD_TYPE: ${{ needs.resolve.outputs.build_type }}
|
|
SUMMARY_BUILD_RUN_ID: ${{ needs.resolve.outputs.build_run_id }}
|
|
SUMMARY_PACKAGE_STATUS: ${{ needs.package.result }}
|
|
run: |
|
|
{
|
|
echo "## 📦 Package Summary"
|
|
echo ""
|
|
echo "| Item | Value |"
|
|
echo "|------|-------|"
|
|
echo "| Version | \`${SUMMARY_VERSION}\` |"
|
|
echo "| Build Type | ${SUMMARY_BUILD_TYPE} |"
|
|
echo "| Build Run | #${SUMMARY_BUILD_RUN_ID} |"
|
|
echo "| Package Status | ${SUMMARY_PACKAGE_STATUS} |"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|