mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-11 23:56:53 +00:00
95627cb601
The RPM build step fails because fpm's --config-files flag requires /etc/default/rustfs to exist in the staging area, but unlike the DEB build (which creates it in its package directory structure), the fpm command has no prior step creating this file. Create the config file in a temporary directory and pass it to fpm via a source=dest mapping, matching the DEB build's behavior.
478 lines
16 KiB
YAML
478 lines
16 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.
|
|
#
|
|
# Trigger:
|
|
# - release published: automatically package when a GitHub release is published
|
|
# - workflow_dispatch: manual trigger with optional tag/run_id
|
|
#
|
|
# Flow:
|
|
# 1. Find the Build workflow run for the release tag
|
|
# 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
|
|
|
|
name: Package DEB/RPM
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
|
|
on:
|
|
release:
|
|
types: [ published ]
|
|
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 (overrides tag lookup)"
|
|
required: false
|
|
type: string
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.release.tag_name || github.event.inputs.tag || github.run_id }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# Resolve which build run to use and extract version info
|
|
resolve:
|
|
name: Resolve Build
|
|
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 }}
|
|
tag: ${{ steps.resolve.outputs.tag }}
|
|
steps:
|
|
- name: Resolve build run
|
|
id: resolve
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
INPUT_TAG: ${{ github.event.inputs.tag }}
|
|
INPUT_RUN_ID: ${{ github.event.inputs.build_run_id }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
# Determine tag
|
|
if [[ "${{ github.event_name }}" == "release" ]]; then
|
|
TAG="${{ github.event.release.tag_name }}"
|
|
elif [[ -n "$INPUT_TAG" ]]; then
|
|
TAG="$INPUT_TAG"
|
|
else
|
|
TAG=""
|
|
fi
|
|
|
|
echo "Tag: ${TAG:-<none>}"
|
|
|
|
# Determine build run ID
|
|
BUILD_RUN_ID=""
|
|
|
|
if [[ -n "$INPUT_RUN_ID" ]]; then
|
|
# Explicit run ID takes priority
|
|
BUILD_RUN_ID="$INPUT_RUN_ID"
|
|
echo "Using explicit build run ID: $BUILD_RUN_ID"
|
|
|
|
elif [[ -n "$TAG" ]]; then
|
|
# Find the build run that produced this tag
|
|
echo "Looking for build run for tag: $TAG"
|
|
BUILD_RUN_ID=$(gh api \
|
|
"repos/${{ github.repository }}/actions/workflows/build.yml/runs?branch=${TAG}&status=success&per_page=1" \
|
|
--jq '.workflow_runs[0].id' 2>/dev/null || echo "")
|
|
|
|
if [[ -z "$BUILD_RUN_ID" || "$BUILD_RUN_ID" == "null" ]]; then
|
|
# Tag might not be a branch; try event=push with head_branch matching
|
|
BUILD_RUN_ID=$(gh api \
|
|
"repos/${{ github.repository }}/actions/workflows/build.yml/runs?event=push&status=success&per_page=100" \
|
|
--jq ".workflow_runs[] | select(.head_branch == \"$TAG\") | .id" 2>/dev/null | head -1 || echo "")
|
|
fi
|
|
|
|
if [[ -z "$BUILD_RUN_ID" || "$BUILD_RUN_ID" == "null" ]]; then
|
|
echo "❌ No successful build run found for tag: $TAG"
|
|
exit 1
|
|
fi
|
|
echo "Found build run: $BUILD_RUN_ID"
|
|
|
|
else
|
|
# No tag — latest successful main build
|
|
echo "No tag specified, looking for latest main build"
|
|
BUILD_RUN_ID=$(gh api \
|
|
"repos/${{ github.repository }}/actions/workflows/build.yml/runs?branch=main&status=success&per_page=1" \
|
|
--jq '.workflow_runs[0].id' 2>/dev/null || echo "")
|
|
|
|
if [[ -z "$BUILD_RUN_ID" || "$BUILD_RUN_ID" == "null" ]]; then
|
|
echo "❌ No successful main build found"
|
|
exit 1
|
|
fi
|
|
echo "Latest main build: $BUILD_RUN_ID"
|
|
fi
|
|
|
|
# Determine version and build type
|
|
if [[ -n "$TAG" ]]; then
|
|
VERSION="$TAG"
|
|
if [[ "$TAG" == *"-preview"* ]]; then
|
|
BUILD_TYPE="preview"
|
|
elif [[ "$TAG" == *"alpha"* || "$TAG" == *"beta"* || "$TAG" == *"rc"* ]]; then
|
|
BUILD_TYPE="prerelease"
|
|
else
|
|
BUILD_TYPE="release"
|
|
fi
|
|
else
|
|
SHORT_SHA=$(gh api "repos/${{ github.repository }}/actions/runs/${BUILD_RUN_ID}" \
|
|
--jq '.head_sha' 2>/dev/null | head -c 7)
|
|
VERSION="dev-${SHORT_SHA}"
|
|
BUILD_TYPE="development"
|
|
fi
|
|
|
|
{
|
|
echo "version=$VERSION"
|
|
echo "build_type=$BUILD_TYPE"
|
|
echo "build_run_id=$BUILD_RUN_ID"
|
|
echo "tag=${TAG}"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
echo "📊 Resolved:"
|
|
echo " Version: $VERSION"
|
|
echo " Build type: $BUILD_TYPE"
|
|
echo " Build run ID: $BUILD_RUN_ID"
|
|
|
|
# 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
|
|
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: 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"
|
|
ls -la ./binary-artifact/ || 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
|
|
ls -lh ./bin/rustfs
|
|
echo "✅ Binary extracted"
|
|
|
|
- name: Build DEB package
|
|
id: deb
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
VERSION="${{ needs.resolve.outputs.version }}"
|
|
DEB_ARCH="${{ matrix.deb_arch }}"
|
|
# DEB version: replace - with ~ (1.0.0-beta.12 -> 1.0.0~beta.12)
|
|
# Use a variable for ~ to prevent tilde expansion by bash
|
|
TILDE='~'
|
|
DEB_VERSION="${VERSION/-/$TILDE}"
|
|
PKG_DIR="rustfs_${DEB_VERSION}_${DEB_ARCH}"
|
|
|
|
echo "Building DEB: ${PKG_DIR}.deb"
|
|
|
|
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 <support@rustfs.com>
|
|
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
|
|
|
|
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="${PKG_DIR}.deb"
|
|
ls -lh "$DEB_FILE"
|
|
echo "deb_file=$DEB_FILE" >> "$GITHUB_OUTPUT"
|
|
echo "✅ DEB built: $DEB_FILE"
|
|
|
|
- name: Build RPM package
|
|
id: rpm
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
VERSION="${{ needs.resolve.outputs.version }}"
|
|
RPM_ARCH="${{ matrix.rpm_arch }}"
|
|
|
|
echo "Building RPM for ${RPM_ARCH}"
|
|
|
|
sudo apt-get update && sudo apt-get install -y ruby ruby-dev build-essential
|
|
sudo gem install fpm
|
|
|
|
# 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 "$VERSION" \
|
|
--architecture "$RPM_ARCH" \
|
|
--depends "glibc >= 2.31" \
|
|
--maintainer "RustFS Team <support@rustfs.com>" \
|
|
--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
|
|
|
|
RPM_FILE=$(ls -1 rustfs-*.rpm 2>/dev/null | head -1)
|
|
if [[ -z "$RPM_FILE" ]]; then
|
|
echo "❌ RPM build failed"
|
|
exit 1
|
|
fi
|
|
|
|
ls -lh "$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
|
|
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"
|
|
|
|
BUILD_TYPE="${{ needs.resolve.outputs.build_type }}"
|
|
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"
|
|
|
|
DEB_FILE="${{ steps.deb.outputs.deb_file }}"
|
|
RPM_FILE="${{ steps.rpm.outputs.rpm_file }}"
|
|
|
|
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
|
|
|
|
# Summary
|
|
summary:
|
|
name: Summary
|
|
needs: [ resolve, package ]
|
|
if: always()
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Print summary
|
|
shell: bash
|
|
run: |
|
|
echo "## 📦 Package Summary" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Item | Value |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "|------|-------|" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Version | \`${{ needs.resolve.outputs.version }}\` |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Build Type | ${{ needs.resolve.outputs.build_type }} |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Build Run | #${{ needs.resolve.outputs.build_run_id }} |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Package Status | ${{ needs.package.result }} |" >> "$GITHUB_STEP_SUMMARY"
|