mirror of
https://github.com/rustfs/rustfs.git
synced 2026-10-05 12:42:20 +00:00
ee6de7d786
* ci(package): build gnu and musl DEB/RPM variants with distinct file names
The Build and Release workflow produces four Linux binaries
(x86_64-gnu, aarch64-gnu, x86_64-musl, aarch64-musl), but packaging
only consumed the two gnu artifacts. Add matrix entries for the two
musl artifacts so every release ships all four DEB/RPM variants.
The libc variant is now part of the package file names, which would
otherwise collide between gnu and musl builds of the same version:
- deb: rustfs_<version>_<libc>_<arch>.deb
- rpm: rustfs-<libc>-<version>-<release>.<arch>.rpm
The dpkg Package and rpm Name stay plain "rustfs", so gnu and musl
remain mutually exclusive upgrades of one package rather than
co-installable packages fighting over /usr/bin/rustfs.
Dependency declarations now follow the linkage: gnu binaries
dynamically link glibc and keep Depends: libc6 (>= 2.31) /
glibc >= 2.31; musl binaries are statically linked and declare no
libc dependency. The libc variant is also visible in the package
description.
scripts/release/package_versions.sh gains a LIBC argument and its
contract tests cover both variants plus the invalid-libc cases.
* ci(package): align deb/rpm file names with the zip artifact naming
Rename the package file names so every release asset of one build
shares the same stem as its binary artifact, differing only by
extension:
- before: rustfs_<deb_version>_<libc>_<deb_arch>.deb
rustfs-<libc>-<rpm_version>-<rpm_release>.<rpm_arch>.rpm
- after: rustfs-linux-<arch>-<libc>-v<version>.deb / .rpm
e.g. rustfs-linux-x86_64-gnu-v1.0.0.zip,
rustfs-linux-x86_64-gnu-v1.0.0.deb,
rustfs-linux-x86_64-gnu-v1.0.0.rpm.
Non-development builds embed the raw release tag (with 'v'), like the
zips; development builds embed dev-<full sha>. The dpkg/rpm versions
(including the '~' prerelease ordering) are unchanged - they live in
the package metadata, and a side effect is that release asset names no
longer contain '~' (which GitHub normalizes to '.').
package_versions.sh now takes the target arch (x86_64|aarch64) instead
of the deb/rpm arch pair; the deb Architecture (amd64/arm64) in the
control metadata still comes from the workflow matrix. The two test
workflows that assemble deb download URLs from a release tag
(rustfs-table-test, rustfs-upgrade-test) are updated to the new name,
which also removes their '~'-to-'.' asset name workaround.
643 lines
26 KiB
YAML
643 lines
26 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, x86_64-musl,
|
|
# aarch64-musl) from build artifacts
|
|
# 3. Build DEB packages for amd64 and arm64, gnu and musl variants
|
|
# 4. Build RPM packages for x86_64 and aarch64, gnu and musl variants
|
|
# 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 and libc variant
|
|
package:
|
|
name: Package (${{ matrix.arch }}-${{ matrix.libc }})
|
|
needs: resolve
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
strategy:
|
|
fail-fast: false
|
|
max-parallel: 1
|
|
matrix:
|
|
include:
|
|
- arch: x86_64
|
|
libc: gnu
|
|
deb_arch: amd64
|
|
rpm_arch: x86_64
|
|
artifact_name: "rustfs-linux-x86_64-gnu"
|
|
# gnu binaries dynamically link glibc; musl binaries are static
|
|
# and run without a libc dependency.
|
|
deb_depends: "libc6 (>= 2.31)"
|
|
rpm_depends: "glibc >= 2.31"
|
|
- arch: aarch64
|
|
libc: gnu
|
|
deb_arch: arm64
|
|
rpm_arch: aarch64
|
|
artifact_name: "rustfs-linux-aarch64-gnu"
|
|
deb_depends: "libc6 (>= 2.31)"
|
|
rpm_depends: "glibc >= 2.31"
|
|
- arch: x86_64
|
|
libc: musl
|
|
deb_arch: amd64
|
|
rpm_arch: x86_64
|
|
artifact_name: "rustfs-linux-x86_64-musl"
|
|
deb_depends: ""
|
|
rpm_depends: ""
|
|
- arch: aarch64
|
|
libc: musl
|
|
deb_arch: arm64
|
|
rpm_arch: aarch64
|
|
artifact_name: "rustfs-linux-aarch64-musl"
|
|
deb_depends: ""
|
|
rpm_depends: ""
|
|
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 }}
|
|
ARCH: ${{ matrix.rpm_arch }}
|
|
LIBC: ${{ matrix.libc }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
normalized=$(./scripts/release/package_versions.sh \
|
|
"$BUILD_TYPE" "$SOURCE_VERSION" "$DEV_SEQUENCE" "$ARCH" "$LIBC")
|
|
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 }}
|
|
DEB_DEPENDS: ${{ matrix.deb_depends }}
|
|
LIBC: ${{ matrix.libc }}
|
|
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://docs.rustfs.com for more information
|
|
# RUSTFS_ACCESS_KEY=<changeme>
|
|
# RUSTFS_SECRET_KEY=<changeme>
|
|
# RUSTFS_VOLUMES=""
|
|
# RUSTFS_ADDRESS=":9000"
|
|
# RUSTFS_CONSOLE_ADDRESS=":9001"
|
|
# RUSTFS_CONSOLE_ENABLE=true
|
|
# RUSTFS_OBS_LOGGER_LEVEL=error
|
|
# RUSTFS_OBS_LOG_DIRECTORY="/var/log/rustfs/"
|
|
ENVEOF
|
|
|
|
cat > "${PKG_DIR}/DEBIAN/control" << EOF
|
|
Package: rustfs
|
|
Version: ${DEB_VERSION}
|
|
Section: utils
|
|
Priority: optional
|
|
Architecture: ${DEB_ARCH}
|
|
EOF
|
|
|
|
# gnu binaries dynamically link glibc and must declare it; musl
|
|
# binaries are statically linked and need no libc dependency.
|
|
if [[ -n "$DEB_DEPENDS" ]]; then
|
|
echo "Depends: ${DEB_DEPENDS}" >> "${PKG_DIR}/DEBIAN/control"
|
|
fi
|
|
|
|
cat >> "${PKG_DIR}/DEBIAN/control" << EOF
|
|
Maintainer: RustFS Team <support@rustfs.com>
|
|
Description: High-performance distributed object storage (${LIBC} build)
|
|
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
|
|
|
|
./scripts/release/package_service_scripts.sh after-install > "${PKG_DIR}/DEBIAN/postinst"
|
|
chmod 755 "${PKG_DIR}/DEBIAN/postinst"
|
|
|
|
./scripts/release/package_service_scripts.sh before-remove > "${PKG_DIR}/DEBIAN/prerm"
|
|
chmod 755 "${PKG_DIR}/DEBIAN/prerm"
|
|
|
|
./scripts/release/package_service_scripts.sh after-remove > "${PKG_DIR}/DEBIAN/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 }}
|
|
RPM_DEPENDS: ${{ matrix.rpm_depends }}
|
|
LIBC: ${{ matrix.libc }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
echo "Building RPM for ${RPM_ARCH} (${LIBC})"
|
|
|
|
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
|
|
|
|
# gnu binaries dynamically link glibc and must declare it; musl
|
|
# binaries are statically linked and need no libc dependency.
|
|
depends_args=()
|
|
if [[ -n "$RPM_DEPENDS" ]]; then
|
|
depends_args+=(--depends "$RPM_DEPENDS")
|
|
fi
|
|
|
|
# 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://docs.rustfs.com for more information
|
|
# RUSTFS_ACCESS_KEY=<changeme>
|
|
# RUSTFS_SECRET_KEY=<changeme>
|
|
# RUSTFS_VOLUMES=""
|
|
# RUSTFS_ADDRESS=":9000"
|
|
# RUSTFS_CONSOLE_ADDRESS=":9001"
|
|
# RUSTFS_CONSOLE_ENABLE=true
|
|
# RUSTFS_OBS_LOGGER_LEVEL=error
|
|
# RUSTFS_OBS_LOG_DIRECTORY="/var/log/rustfs/"
|
|
ENVEOF
|
|
|
|
fpm -s dir -t rpm \
|
|
--name rustfs \
|
|
--version "$RPM_VERSION" \
|
|
--iteration "$RPM_RELEASE" \
|
|
--architecture "$RPM_ARCH" \
|
|
--package "$RPM_FILE" \
|
|
"${depends_args[@]}" \
|
|
--description "High-performance distributed object storage (${LIBC} build)" \
|
|
--maintainer "RustFS Team <support@rustfs.com>" \
|
|
--url "https://rustfs.com" \
|
|
--license "Apache-2.0" \
|
|
--before-install <(./scripts/release/package_service_scripts.sh rpm-before-install) \
|
|
--before-upgrade <(./scripts/release/package_service_scripts.sh rpm-before-upgrade) \
|
|
--after-install <(./scripts/release/package_service_scripts.sh after-install) \
|
|
--after-upgrade <(./scripts/release/package_service_scripts.sh after-install) \
|
|
--before-remove <(./scripts/release/package_service_scripts.sh before-remove) \
|
|
--after-remove <(./scripts/release/package_service_scripts.sh after-remove) \
|
|
--rpm-posttrans <(./scripts/release/package_service_scripts.sh rpm-posttrans) \
|
|
--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 }}-${{ matrix.libc }}
|
|
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 '.'.
|
|
# Current package file names embed the raw tag and contain no
|
|
# '~', but older releases may still ship such assets, so the
|
|
# 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"
|