mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
00536da80c
* refactor(obs): make dial9 telemetry opt-in and actually record events
The dial9 Tokio-runtime profiler was disabled by default, yet every build
paid for it, and enabling it produced trace files with no events in them.
Recorded empty traces
---------------------
`build_traced_runtime` called `TracedRuntime::builder()...build(..)`, but dial9
only starts recording in `build_and_start*`. `build` still returns a live guard
whose `is_enabled()` reports true, and still creates and seals segment files —
they just contain a header and no events. It also skipped `with_trace_path`, so
the background worker driving the segment pipeline was never spawned.
Measured on the new smoke example: 310 bytes of bare segment header, against
5640 bytes for the same workload once recording actually starts.
Switch to `with_trace_path(..).build_and_start(..)`.
Cost was unconditional
----------------------
`--cfg tokio_unstable` was a global `[build] rustflags` entry and `rustfs-obs`
depended on `dial9-tokio-telemetry` unconditionally, so all builds depended on
Tokio's non-semver API. Worse, an environment `RUSTFLAGS` replaces (never
appends to) the config-file value, so any caller exporting their own RUSTFLAGS
silently dropped the flag — the long comment in build.yml was a scar from that.
dial9 is now an opt-in feature (`dial9`, plus `dial9-s3` and `dial9-taskdump`),
the global rustflag is gone, and `crates/obs/build.rs` fails the compile if the
feature is on without the flag. Telemetry builds go through `make build-profiling`.
Metrics that could not lie
--------------------------
`rustfs_dial9_{events_total,bytes_written_total,rotations_total,cpu_overhead_percent}`
were hard-coded to zero — a Counter pinned at 0 reads as "nothing happened".
Removed. `rustfs_dial9_enabled` was sourced from the environment, so it read 1
even when the traced runtime failed and the process fell back to a standard
runtime; it is replaced by `rustfs_dial9_supported` (compile-time),
`rustfs_dial9_configured` (intent) and `rustfs_dial9_active_sessions` (reality).
No `writer_healthy` gauge is exported: dial9's `RotatingWriter` can enter its
`Finished` state and stop writing, but exposes no way to observe that, so the
gauge could only ever be hard-coded to 1. Documented as a known gap instead.
Final events were lost
----------------------
The `TelemetryGuard` lived in a `static OnceLock`, which is never dropped, so
buffered events were never flushed at exit. `build_tokio_runtime` now returns
the guard and `run_process` drops it before any exit path.
Also
----
- `disk_usage_bytes` was a `read_dir` + per-file `stat` on the metrics
collection path. It is now sampled by a background task into an atomic.
- `SAMPLING_RATE`/`S3_BUCKET`/`S3_PREFIX` were parsed, warned about, and
discarded. S3 upload is now wired to dial9's `with_s3_uploader` behind
`dial9-s3`; `SAMPLING_RATE` has no upstream equivalent and is removed.
- Wire `with_task_dumps` (async backtraces of stalled tasks), configurable via
`RUSTFS_RUNTIME_DIAL9_TASK_DUMP_{ENABLED,IDLE_THRESHOLD_MS}`.
- Split `telemetry/dial9.rs` into `config`/`state`/`enabled`/`disabled`; the
stub keeps the public API identical so callers need no `#[cfg]`.
- Drop four print-only examples and the manual test bin that exercised the
removed `init_session` scaffolding.
Verified: cargo check/clippy/test across default, `dial9`, and `dial9-s3`;
build.rs correctly rejects `dial9` without `--cfg tokio_unstable`;
`make pre-commit` passes.
Co-Authored-By: heihutu <heihutu@gmail.com>
* docs(obs): document dial9 as an on-demand profiler
scripts/run.sh advertised a `SAMPLING_RATE` knob that was never passed to dial9,
and claimed "CPU overhead < 5% (with sampling rate 1.0)" and "lower values reduce
CPU overhead" on the strength of it. The knob is gone; the guidance built on it
had to go too.
Replace it with what is actually true: dial9 needs a `make build-profiling`
binary, its disk budget evicts oldest-first (so a high poll rate can overwrite
the incident you are chasing), and it cannot be toggled without a restart.
Add docs/operations/dial9-runtime-profiling.md covering the build variants, an
investigation walkthrough, the configuration table, how to read the three
supported/configured/active_sessions gauges against each other, and the upstream
gap that makes writer death only indirectly observable.
Co-Authored-By: heihutu <heihutu@gmail.com>
* test(obs): add a dial9 smoke example that proves events are recorded
The bug this guards against is invisible to every existing signal: with
`build` instead of `build_and_start`, dial9 creates the trace file, seals
segments, and reports `TelemetryGuard::is_enabled() == true` — it simply
records no events. Only the segment's byte count tells the two apart.
Measured on this workload: 5640 bytes when recording, 310 bytes (a bare
segment header) when not. The example asserts >= 2048 bytes, and was verified
to fail with the `build` call restored.
Also correct the comment on the `is_enabled` check in `finish_traced_runtime`.
It claimed to catch "recording silently off"; it does not. It only rejects the
inert guard a lenient config yields after a build failure. Recording is
guaranteed by `build_and_start`, not by that check.
Co-Authored-By: heihutu <heihutu@gmail.com>
* test(rustfs): accept Unsupported runtime telemetry capability
A binary built without the `dial9` feature now reports the runtime-telemetry
capability as `Unsupported` rather than `Disabled`. The distinction matters to
operators: `Disabled` implies the capability can be switched on by setting an
environment variable, which is not true here — telemetry needs a rebuild.
Widen the assertion and pin the new semantics: when `dial9::is_supported()` is
false, the state must be exactly `Unsupported`.
Co-Authored-By: heihutu <heihutu@gmail.com>
* fix(obs): drop the dial9-s3 feature, its TLS stack is vulnerable
CI's Dependency Review and `cargo deny` both reject the branch: dial9's
`worker-s3` feature depends on aws-sdk-s3-transfer-manager 0.1.3, which pins
aws-smithy-http-client onto hyper-rustls 0.24 and rustls-webpki 0.101.7. That
webpki carries RUSTSEC-2026-0098, -0099 and -0104.
0.1.3 is the latest release of the transfer manager, and 1.2.0 the latest of the
smithy client, so there is nothing to upgrade to. Cargo's feature unification can
add features but cannot drop a transitive dependency, so it cannot be worked
around from here either — the rest of the workspace already resolves to the safe
rustls-webpki 0.103 / hyper-rustls 0.27.
Remove the `dial9-s3` feature and the `with_s3_uploader` wiring. The two S3
environment variables stay parsed and warned about, now naming the real reason
rather than a missing build feature. Trace segments are collected from the output
directory instead. Tracked as D9-14 in rustfs/backlog#1157.
With this, Cargo.lock is byte-identical to main: the PR no longer touches the
dependency graph at all.
Also correct the `dial9-taskdump` documentation. It claimed the feature "compiles
to a no-op elsewhere"; in fact `tokio/taskdump` raises a `compile_error!` on any
target other than linux/{aarch64,x86,x86_64}. Verified by trying to build it on
macOS, which is how the claim was found to be wrong.
Co-Authored-By: heihutu <heihutu@gmail.com>
---------
Co-authored-by: heihutu <heihutu@gmail.com>
944 lines
36 KiB
YAML
944 lines
36 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.
|
|
|
|
# Build and Release Workflow
|
|
#
|
|
# This workflow builds RustFS binaries and automatically triggers Docker image builds.
|
|
#
|
|
# Flow:
|
|
# 1. Build binaries for multiple platforms
|
|
# 2. Upload binaries to OSS storage
|
|
# 3. Trigger docker.yml to build and push images using the uploaded binaries
|
|
#
|
|
# Platform scope:
|
|
# - Pushes to main (development builds) build the Linux targets only — they
|
|
# feed the dev download channel and Docker dev images. Tags, the weekly
|
|
# schedule and manual dispatch build the full platform matrix.
|
|
#
|
|
# Manual Parameters:
|
|
# - build_docker: Build and push Docker images (default: true)
|
|
# - platforms: Comma-separated platform IDs or 'all' (default: all)
|
|
|
|
name: Build and Release
|
|
|
|
on:
|
|
push:
|
|
tags: [ "*.*.*" ]
|
|
branches: [ main ]
|
|
paths-ignore:
|
|
- "**.md"
|
|
- "**.txt"
|
|
- ".github/**"
|
|
- "docs/**"
|
|
- "deploy/**"
|
|
- "scripts/dev_*.sh"
|
|
- "LICENSE*"
|
|
- "README*"
|
|
- "**/*.png"
|
|
- "**/*.jpg"
|
|
- "**/*.svg"
|
|
- ".gitignore"
|
|
- ".dockerignore"
|
|
schedule:
|
|
- cron: "0 1 * * 0" # Weekly on Sunday 01:00 UTC (staggered after the ci.yml midnight cron)
|
|
workflow_dispatch:
|
|
inputs:
|
|
build_docker:
|
|
description: "Build and push Docker images after binary build"
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
platforms:
|
|
description: "Comma-separated targets or 'all' (e.g. linux-x86_64-musl,macos-aarch64)"
|
|
required: false
|
|
default: "all"
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref || github.run_id }}
|
|
cancel-in-progress: ${{ github.event_name == 'push' }}
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
RUST_BACKTRACE: 1
|
|
# Optimize build performance
|
|
CARGO_INCREMENTAL: 0
|
|
|
|
jobs:
|
|
# Build strategy check - determine build type based on trigger
|
|
build-check:
|
|
name: Build Strategy Check
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
should_build: ${{ steps.check.outputs.should_build }}
|
|
build_type: ${{ steps.check.outputs.build_type }}
|
|
version: ${{ steps.check.outputs.version }}
|
|
short_sha: ${{ steps.check.outputs.short_sha }}
|
|
is_prerelease: ${{ steps.check.outputs.is_prerelease }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
|
|
- name: Determine build strategy
|
|
id: check
|
|
run: |
|
|
should_build=false
|
|
build_type="none"
|
|
version=""
|
|
short_sha=""
|
|
is_prerelease=false
|
|
|
|
# Get short SHA for all builds
|
|
short_sha=$(git rev-parse --short HEAD)
|
|
|
|
# Determine build type based on trigger
|
|
if [[ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]]; then
|
|
# Tag push - release or prerelease
|
|
should_build=true
|
|
tag_name="${GITHUB_REF#refs/tags/}"
|
|
version="${tag_name}"
|
|
|
|
# Check if this is a prerelease
|
|
if [[ "$tag_name" == *"alpha"* ]] || [[ "$tag_name" == *"beta"* ]] || [[ "$tag_name" == *"rc"* ]]; then
|
|
build_type="prerelease"
|
|
is_prerelease=true
|
|
echo "🚀 Prerelease build detected: $tag_name"
|
|
else
|
|
build_type="release"
|
|
echo "📦 Release build detected: $tag_name"
|
|
fi
|
|
elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
|
|
# Main branch push - development build
|
|
should_build=true
|
|
build_type="development"
|
|
version="dev-${short_sha}"
|
|
echo "🛠️ Development build detected"
|
|
elif [[ "${{ github.event_name }}" == "schedule" ]] || \
|
|
[[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
|
# Scheduled or manual build
|
|
should_build=true
|
|
build_type="development"
|
|
version="dev-${short_sha}"
|
|
echo "⚡ Manual/scheduled build detected"
|
|
fi
|
|
|
|
echo "should_build=$should_build" >> $GITHUB_OUTPUT
|
|
echo "build_type=$build_type" >> $GITHUB_OUTPUT
|
|
echo "version=$version" >> $GITHUB_OUTPUT
|
|
echo "short_sha=$short_sha" >> $GITHUB_OUTPUT
|
|
echo "is_prerelease=$is_prerelease" >> $GITHUB_OUTPUT
|
|
|
|
echo "📊 Build Summary:"
|
|
echo " - Should build: $should_build"
|
|
echo " - Build type: $build_type"
|
|
echo " - Version: $version"
|
|
echo " - Short SHA: $short_sha"
|
|
echo " - Is prerelease: $is_prerelease"
|
|
|
|
# Build RustFS binaries
|
|
prepare-platform-matrix:
|
|
name: Prepare Platform Matrix
|
|
needs: build-check
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
matrix: ${{ steps.select.outputs.matrix }}
|
|
selected: ${{ steps.select.outputs.selected }}
|
|
steps:
|
|
- name: Select target platforms
|
|
id: select
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
selected="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.platforms || 'all' }}"
|
|
selected="$(echo "${selected}" | tr -d '[:space:]')"
|
|
if [[ -z "${selected}" ]]; then
|
|
selected="all"
|
|
fi
|
|
|
|
# Per-merge development builds only feed the dev download channel
|
|
# and the Docker dev images, which consume the Linux binaries; at
|
|
# the usual merge cadence most per-merge builds are cancelled by the
|
|
# next merge anyway. macOS and Windows stay covered by tag builds,
|
|
# the weekly scheduled build and manual dispatch.
|
|
if [[ "${selected}" == "all" \
|
|
&& "${{ github.event_name }}" == "push" \
|
|
&& "${{ needs.build-check.outputs.build_type }}" == "development" ]]; then
|
|
selected="linux-x86_64-musl,linux-aarch64-musl,linux-x86_64-gnu,linux-aarch64-gnu"
|
|
echo "Development (main push) build: restricting to Linux targets"
|
|
fi
|
|
|
|
all='{"include":[
|
|
{"target_id":"linux-x86_64-musl","os":"sm-standard-2","target":"x86_64-unknown-linux-musl","cross":false,"platform":"linux","rustflags":""},
|
|
{"target_id":"linux-aarch64-musl","os":"sm-standard-2","target":"aarch64-unknown-linux-musl","cross":true,"platform":"linux","rustflags":""},
|
|
{"target_id":"linux-x86_64-gnu","os":"sm-standard-2","target":"x86_64-unknown-linux-gnu","cross":false,"platform":"linux","rustflags":""},
|
|
{"target_id":"linux-aarch64-gnu","os":"sm-standard-2","target":"aarch64-unknown-linux-gnu","cross":true,"platform":"linux","rustflags":""},
|
|
{"target_id":"macos-aarch64","os":"macos-latest","target":"aarch64-apple-darwin","cross":false,"platform":"macos","rustflags":""},
|
|
{"target_id":"macos-x86_64","os":"macos-26-intel","target":"x86_64-apple-darwin","cross":false,"platform":"macos","rustflags":""},
|
|
{"target_id":"windows-x86_64","os":"windows-latest","target":"x86_64-pc-windows-msvc","cross":false,"platform":"windows","rustflags":""}
|
|
]}'
|
|
|
|
if [[ "${selected}" == "all" ]]; then
|
|
matrix="$(jq -c . <<<"${all}")"
|
|
else
|
|
unknown="$(jq -rn --arg selected "${selected}" --argjson all "${all}" '
|
|
($selected | split(",") | map(select(length > 0))) as $req
|
|
| ($all.include | map(.target_id)) as $known
|
|
| [$req[] | select(( $known | index(.) ) == null)]
|
|
')"
|
|
if [[ "$(jq 'length' <<<"${unknown}")" -gt 0 ]]; then
|
|
echo "Unknown platforms: $(jq -r 'join(\",\")' <<<"${unknown}")" >&2
|
|
echo "Allowed: $(jq -r '.include[].target_id' <<<"${all}" | paste -sd ',' -)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
matrix="$(jq -c --arg selected "${selected}" '
|
|
($selected | split(",") | map(select(length > 0))) as $req
|
|
| .include |= map(select(.target_id as $id | ($req | index($id))))
|
|
' <<<"${all}")"
|
|
fi
|
|
|
|
echo "selected=${selected}" >> "$GITHUB_OUTPUT"
|
|
echo "matrix=${matrix}" >> "$GITHUB_OUTPUT"
|
|
echo "Selected platforms: ${selected}"
|
|
|
|
build-rustfs:
|
|
name: Build RustFS
|
|
needs: [ build-check, prepare-platform-matrix ]
|
|
if: needs.build-check.outputs.should_build == 'true' && needs.prepare-platform-matrix.result == 'success'
|
|
runs-on: ${{ matrix.platform == 'linux' && fromJSON('["self-hosted","linux","sm-standard-2"]') || matrix.os }}
|
|
timeout-minutes: 90
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
# Release binaries ship without dial9 telemetry and therefore do not need
|
|
# --cfg tokio_unstable. Telemetry builds opt in explicitly (see
|
|
# `make build-profiling`); crates/obs/build.rs fails the compile if the
|
|
# `dial9` feature is enabled without the flag.
|
|
RUSTFLAGS: "${{ matrix.rustflags }}"
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.prepare-platform-matrix.outputs.matrix) }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Rust environment
|
|
uses: ./.github/actions/setup
|
|
with:
|
|
rust-version: stable
|
|
target: ${{ matrix.target }}
|
|
cache-shared-key: build-${{ matrix.target }}
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
cache-save-if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
|
|
install-cross-tools: ${{ matrix.cross }}
|
|
|
|
- name: Download static console assets
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: |
|
|
mkdir -p ./rustfs/static
|
|
|
|
verify_sha256() {
|
|
local expected="$1"
|
|
local file="$2"
|
|
local actual=""
|
|
local actual_line=""
|
|
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
actual_line=$(sha256sum -- "$file") || return 1
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
actual_line=$(shasum -a 256 -- "$file") || return 1
|
|
else
|
|
echo "No SHA-256 verification tool found" >&2
|
|
return 1
|
|
fi
|
|
actual="${actual_line%%[[:space:]]*}"
|
|
|
|
if [ "$actual" = "$expected" ]; then
|
|
echo "SHA256 verified OK: $actual"
|
|
return 0
|
|
else
|
|
echo "SHA256 mismatch: expected=$expected actual=$actual" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
download_console_assets() {
|
|
local curl_bin="curl"
|
|
local python_bin="python3"
|
|
local console_api="https://api.github.com/repos/rustfs/console/releases/latest"
|
|
local console_json="console-release.json"
|
|
local console_url
|
|
local console_sha256
|
|
local curl_auth_args=()
|
|
|
|
if [[ "${{ matrix.platform }}" == "windows" ]]; then
|
|
curl_bin="curl.exe"
|
|
else
|
|
chmod +w ./rustfs/static/LICENSE || true
|
|
fi
|
|
|
|
if ! command -v "$python_bin" >/dev/null 2>&1; then
|
|
python_bin="python"
|
|
fi
|
|
if ! command -v "$python_bin" >/dev/null 2>&1; then
|
|
echo "No Python interpreter found for release metadata parsing" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
curl_auth_args=(-H "Authorization: Bearer ${GITHUB_TOKEN}" -H "X-GitHub-Api-Version: 2022-11-28")
|
|
fi
|
|
|
|
"$curl_bin" "${curl_auth_args[@]}" --fail -L "$console_api" \
|
|
-o "$console_json" --retry 3 --retry-delay 5 --max-time 300 || return 1
|
|
|
|
read -r console_url console_sha256 < <("$python_bin" - "$console_json" <<'PY'
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
release = json.load(handle)
|
|
|
|
for asset in release.get("assets", []):
|
|
name = asset.get("name", "")
|
|
digest = asset.get("digest", "")
|
|
url = asset.get("browser_download_url", "")
|
|
if name.startswith("rustfs-console-") and name.endswith(".zip") and digest.startswith("sha256:") and url:
|
|
sha256 = digest.split(":", 1)[1]
|
|
if not re.fullmatch(r"[0-9a-fA-F]{64}", sha256):
|
|
raise SystemExit(f"console zip asset has invalid sha256 digest: {sha256}")
|
|
sys.stdout.buffer.write(f"{url} {sha256}\n".encode("utf-8"))
|
|
break
|
|
else:
|
|
raise SystemExit("no console zip asset with sha256 digest found")
|
|
PY
|
|
) || return 1
|
|
|
|
if [[ -z "$console_url" || -z "$console_sha256" ]]; then
|
|
echo "Console release metadata is missing URL or SHA-256 digest" >&2
|
|
return 1
|
|
fi
|
|
|
|
"$curl_bin" --fail -L "$console_url" -o console.zip --retry 3 --retry-delay 5 --max-time 300 || return 1
|
|
verify_sha256 "$console_sha256" console.zip || return 2
|
|
unzip -o console.zip -d ./rustfs/static || return 2
|
|
}
|
|
|
|
if download_console_assets; then
|
|
rm -f console.zip console-release.json
|
|
else
|
|
status=$?
|
|
rm -f console.zip console-release.json
|
|
if [[ "$status" -eq 2 ]]; then
|
|
echo "Console asset integrity verification failed" >&2
|
|
exit 1
|
|
fi
|
|
echo "Warning: Failed to download verified console assets, continuing without them"
|
|
echo "// Static assets not available" > ./rustfs/static/empty.txt
|
|
fi
|
|
|
|
- name: Build RustFS
|
|
shell: bash
|
|
run: |
|
|
# Force rebuild by touching build.rs
|
|
touch rustfs/build.rs
|
|
|
|
if [[ "${{ matrix.cross }}" == "true" ]]; then
|
|
# All cross targets in the matrix are Linux; zigbuild handles them.
|
|
cargo zigbuild --release --target ${{ matrix.target }} -p rustfs --bins
|
|
else
|
|
cargo build --release --target ${{ matrix.target }} -p rustfs --bins
|
|
fi
|
|
|
|
- name: Create release package
|
|
id: package
|
|
shell: bash
|
|
run: |
|
|
BUILD_TYPE="${{ needs.build-check.outputs.build_type }}"
|
|
VERSION="${{ needs.build-check.outputs.version }}"
|
|
SHORT_SHA="${{ needs.build-check.outputs.short_sha }}"
|
|
|
|
# Extract platform and arch from target
|
|
TARGET="${{ matrix.target }}"
|
|
PLATFORM="${{ matrix.platform }}"
|
|
# Map target to architecture and variant
|
|
case "$TARGET" in
|
|
*x86_64*musl*)
|
|
ARCH="x86_64"
|
|
VARIANT="musl"
|
|
;;
|
|
*x86_64*gnu*)
|
|
ARCH="x86_64"
|
|
VARIANT="gnu"
|
|
;;
|
|
*x86_64*)
|
|
ARCH="x86_64"
|
|
VARIANT=""
|
|
;;
|
|
*aarch64*musl*|*arm64*musl*)
|
|
ARCH="aarch64"
|
|
VARIANT="musl"
|
|
;;
|
|
*aarch64*gnu*|*arm64*gnu*)
|
|
ARCH="aarch64"
|
|
VARIANT="gnu"
|
|
;;
|
|
*aarch64*|*arm64*)
|
|
ARCH="aarch64"
|
|
VARIANT=""
|
|
;;
|
|
*armv7*)
|
|
ARCH="armv7"
|
|
VARIANT=""
|
|
;;
|
|
*)
|
|
ARCH="unknown"
|
|
VARIANT=""
|
|
;;
|
|
esac
|
|
|
|
# Normalize version used for package filenames
|
|
PACKAGE_VERSION="${VERSION}"
|
|
if [[ "$PACKAGE_VERSION" == v* ]]; then
|
|
PACKAGE_VERSION="${PACKAGE_VERSION#v}"
|
|
fi
|
|
|
|
# Generate package name based on build type
|
|
if [[ -n "$VARIANT" ]]; then
|
|
ARCH_WITH_VARIANT="${ARCH}-${VARIANT}"
|
|
else
|
|
ARCH_WITH_VARIANT="${ARCH}"
|
|
fi
|
|
PACKAGE_BASENAME="rustfs-${PLATFORM}-${ARCH_WITH_VARIANT}"
|
|
|
|
if [[ "$BUILD_TYPE" == "development" ]]; then
|
|
# Development build: rustfs-${platform}-${arch}-${variant}-dev-${short_sha}.zip
|
|
PACKAGE_NAME="rustfs-${PLATFORM}-${ARCH_WITH_VARIANT}-dev-${SHORT_SHA}"
|
|
else
|
|
# Release/Prerelease build: rustfs-${platform}-${arch}-${variant}-v${version}.zip
|
|
PACKAGE_NAME="${PACKAGE_BASENAME}-v${PACKAGE_VERSION}"
|
|
fi
|
|
|
|
# Create zip packages for all platforms
|
|
# Ensure zip is available
|
|
if ! command -v zip &> /dev/null; then
|
|
if [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then
|
|
sudo apt-get update && sudo apt-get install -y zip
|
|
fi
|
|
fi
|
|
|
|
cd target/${{ matrix.target }}/release
|
|
# Determine the binary name based on platform
|
|
if [[ "${{ matrix.platform }}" == "windows" ]]; then
|
|
BINARY_NAME="rustfs.exe"
|
|
else
|
|
BINARY_NAME="rustfs"
|
|
fi
|
|
|
|
# Verify the binary exists before packaging
|
|
if [[ ! -f "$BINARY_NAME" ]]; then
|
|
echo "❌ Binary $BINARY_NAME not found in $(pwd)"
|
|
if [[ "${{ matrix.platform }}" == "windows" ]]; then
|
|
dir
|
|
else
|
|
ls -la
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
# Universal packaging function
|
|
package_zip() {
|
|
local src=$1
|
|
local dst=$2
|
|
if [[ "${{ matrix.platform }}" == "windows" ]]; then
|
|
# Windows uses PowerShell Compress-Archive
|
|
powershell -Command "Compress-Archive -Path '$src' -DestinationPath '$dst' -Force"
|
|
elif command -v zip &> /dev/null; then
|
|
# Unix systems use zip command
|
|
zip "$dst" "$src"
|
|
else
|
|
echo "❌ No zip utility available"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Create the zip package
|
|
echo "Start packaging: $BINARY_NAME -> ../../../${PACKAGE_NAME}.zip"
|
|
package_zip "$BINARY_NAME" "../../../${PACKAGE_NAME}.zip"
|
|
|
|
cd ../../..
|
|
|
|
# Verify the package was created
|
|
if [[ -f "${PACKAGE_NAME}.zip" ]]; then
|
|
echo "✅ Package created successfully: ${PACKAGE_NAME}.zip"
|
|
if [[ "${{ matrix.platform }}" == "windows" ]]; then
|
|
dir
|
|
else
|
|
ls -lh ${PACKAGE_NAME}.zip
|
|
fi
|
|
else
|
|
echo "❌ Failed to create package: ${PACKAGE_NAME}.zip"
|
|
exit 1
|
|
fi
|
|
|
|
# Create latest version files right after the main package
|
|
LATEST_FILES=""
|
|
if [[ "$BUILD_TYPE" == "release" ]] || [[ "$BUILD_TYPE" == "prerelease" ]]; then
|
|
# Create latest version filename
|
|
# Convert from rustfs-linux-x86_64-musl-v1.0.0 to rustfs-linux-x86_64-musl-latest
|
|
LATEST_FILE="${PACKAGE_BASENAME}-latest.zip"
|
|
|
|
echo "🔄 Creating latest version: ${PACKAGE_NAME}.zip -> $LATEST_FILE"
|
|
cp "${PACKAGE_NAME}.zip" "$LATEST_FILE"
|
|
|
|
if [[ -f "$LATEST_FILE" ]]; then
|
|
echo "✅ Latest version created: $LATEST_FILE"
|
|
LATEST_FILES="$LATEST_FILE"
|
|
fi
|
|
elif [[ "$BUILD_TYPE" == "development" ]]; then
|
|
# Development builds (only main branch triggers development builds)
|
|
# Create main-latest version filename
|
|
# Convert from rustfs-linux-x86_64-dev-abc123 to rustfs-linux-x86_64-main-latest
|
|
MAIN_LATEST_FILE="${PACKAGE_BASENAME}-main-latest.zip"
|
|
|
|
echo "🔄 Creating main-latest version: ${PACKAGE_NAME}.zip -> $MAIN_LATEST_FILE"
|
|
cp "${PACKAGE_NAME}.zip" "$MAIN_LATEST_FILE"
|
|
|
|
if [[ -f "$MAIN_LATEST_FILE" ]]; then
|
|
echo "✅ Main-latest version created: $MAIN_LATEST_FILE"
|
|
LATEST_FILES="$MAIN_LATEST_FILE"
|
|
|
|
# Also create a generic main-latest for Docker builds (Linux only)
|
|
if [[ "${{ matrix.platform }}" == "linux" ]]; then
|
|
DOCKER_MAIN_LATEST_FILE="rustfs-linux-${ARCH_WITH_VARIANT}-main-latest.zip"
|
|
|
|
echo "🔄 Creating Docker main-latest version: ${PACKAGE_NAME}.zip -> $DOCKER_MAIN_LATEST_FILE"
|
|
cp "${PACKAGE_NAME}.zip" "$DOCKER_MAIN_LATEST_FILE"
|
|
|
|
if [[ -f "$DOCKER_MAIN_LATEST_FILE" ]]; then
|
|
echo "✅ Docker main-latest version created: $DOCKER_MAIN_LATEST_FILE"
|
|
LATEST_FILES="$LATEST_FILES $DOCKER_MAIN_LATEST_FILE"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo "package_name=${PACKAGE_NAME}" >> $GITHUB_OUTPUT
|
|
echo "package_file=${PACKAGE_NAME}.zip" >> $GITHUB_OUTPUT
|
|
echo "latest_files=${LATEST_FILES}" >> $GITHUB_OUTPUT
|
|
echo "build_type=${BUILD_TYPE}" >> $GITHUB_OUTPUT
|
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
|
|
echo "📦 Package created: ${PACKAGE_NAME}.zip"
|
|
if [[ -n "$LATEST_FILES" ]]; then
|
|
echo "📦 Latest files created: $LATEST_FILES"
|
|
fi
|
|
echo "🔧 Build type: ${BUILD_TYPE}"
|
|
echo "📊 Version: ${VERSION}"
|
|
|
|
- name: Upload to GitHub artifacts
|
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
|
with:
|
|
name: ${{ steps.package.outputs.package_name }}
|
|
path: "rustfs-*.zip"
|
|
retention-days: ${{ startsWith(github.ref, 'refs/tags/') && 30 || 7 }}
|
|
|
|
- name: Upload to Cloudflare R2
|
|
if: env.R2_ACCESS_KEY_ID != '' && (needs.build-check.outputs.build_type == 'release' || needs.build-check.outputs.build_type == 'prerelease' || needs.build-check.outputs.build_type == 'development')
|
|
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: |
|
|
BUILD_TYPE="${{ needs.build-check.outputs.build_type }}"
|
|
|
|
if [[ -z "$R2_ACCESS_KEY_ID" || -z "$R2_SECRET_ACCESS_KEY" || -z "$R2_ENDPOINT" || -z "$R2_BUCKET" ]]; then
|
|
echo "⚠️ R2 credentials or endpoint missing, skipping artifact upload"
|
|
exit 0
|
|
fi
|
|
|
|
if ! command -v aws >/dev/null 2>&1; then
|
|
echo "❌ aws CLI not found on runner; cannot upload to R2"
|
|
exit 1
|
|
fi
|
|
|
|
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY_ID"
|
|
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY"
|
|
export AWS_DEFAULT_REGION="auto"
|
|
|
|
# Determine upload path based on build type
|
|
if [[ "$BUILD_TYPE" == "development" ]]; then
|
|
R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs/dev/"
|
|
echo "📤 Uploading development build to R2 dev directory"
|
|
else
|
|
R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs/release/"
|
|
echo "📤 Uploading release build to R2 release directory"
|
|
fi
|
|
|
|
# Upload all rustfs zip files to R2 using S3-compatible API
|
|
echo "📤 Uploading all rustfs-*.zip files to $R2_PATH..."
|
|
for zip_file in rustfs-*.zip; do
|
|
if [[ -f "$zip_file" ]]; then
|
|
echo "Uploading: $zip_file to $R2_PATH..."
|
|
aws s3 cp "$zip_file" "$R2_PATH" --endpoint-url "$R2_ENDPOINT" --only-show-errors
|
|
echo "✅ Uploaded: $zip_file"
|
|
fi
|
|
done
|
|
|
|
echo "✅ Upload completed successfully"
|
|
|
|
# Build summary
|
|
build-summary:
|
|
name: Build Summary
|
|
needs: [ build-check, build-rustfs ]
|
|
if: always() && needs.build-check.outputs.should_build == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Build completion summary
|
|
shell: bash
|
|
run: |
|
|
BUILD_TYPE="${{ needs.build-check.outputs.build_type }}"
|
|
VERSION="${{ needs.build-check.outputs.version }}"
|
|
|
|
echo "🎉 Build completed successfully!"
|
|
echo "📦 Build type: $BUILD_TYPE"
|
|
echo "🔢 Version: $VERSION"
|
|
echo ""
|
|
|
|
# Check build status
|
|
BUILD_STATUS="${{ needs.build-rustfs.result }}"
|
|
|
|
echo "📊 Build Results:"
|
|
echo " 📦 All platforms: $BUILD_STATUS"
|
|
echo ""
|
|
|
|
case "$BUILD_TYPE" in
|
|
"development")
|
|
echo "🛠️ Development build artifacts have been uploaded to OSS dev directory"
|
|
echo "⚠️ This is a development build - not suitable for production use"
|
|
;;
|
|
"release")
|
|
echo "🚀 Release build artifacts have been uploaded to OSS release directory"
|
|
echo "✅ This build is ready for production use"
|
|
echo "🏷️ GitHub Release will be created in this workflow"
|
|
;;
|
|
"prerelease")
|
|
echo "🧪 Prerelease build artifacts have been uploaded to OSS release directory"
|
|
echo "⚠️ This is a prerelease build - use with caution"
|
|
echo "🏷️ GitHub Release will be created in this workflow"
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
echo "🐳 Docker Images:"
|
|
if [[ "${{ github.event.inputs.build_docker }}" == "false" ]]; then
|
|
echo "⏭️ Docker image build was skipped (binary only build)"
|
|
elif [[ "$BUILD_STATUS" == "success" ]]; then
|
|
echo "🔄 Docker images will be built and pushed automatically via workflow_run event"
|
|
else
|
|
echo "❌ Docker image build will be skipped due to build failure"
|
|
fi
|
|
|
|
# Create GitHub Release (only for tag pushes)
|
|
create-release:
|
|
name: Create GitHub Release
|
|
needs: [ build-check, build-rustfs ]
|
|
if: startsWith(github.ref, 'refs/tags/') && needs.build-check.outputs.build_type != 'development'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
outputs:
|
|
release_id: ${{ steps.create.outputs.release_id }}
|
|
release_url: ${{ steps.create.outputs.release_url }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Create GitHub Release
|
|
id: create
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
TAG="${{ needs.build-check.outputs.version }}"
|
|
VERSION="${{ needs.build-check.outputs.version }}"
|
|
IS_PRERELEASE="${{ needs.build-check.outputs.is_prerelease }}"
|
|
BUILD_TYPE="${{ needs.build-check.outputs.build_type }}"
|
|
|
|
# Determine release type for title
|
|
if [[ "$BUILD_TYPE" == "prerelease" ]]; then
|
|
if [[ "$TAG" == *"alpha"* ]]; then
|
|
RELEASE_TYPE="alpha"
|
|
elif [[ "$TAG" == *"beta"* ]]; then
|
|
RELEASE_TYPE="beta"
|
|
elif [[ "$TAG" == *"rc"* ]]; then
|
|
RELEASE_TYPE="rc"
|
|
else
|
|
RELEASE_TYPE="prerelease"
|
|
fi
|
|
else
|
|
RELEASE_TYPE="release"
|
|
fi
|
|
|
|
# Check if release already exists
|
|
if gh release view "$TAG" >/dev/null 2>&1; then
|
|
echo "Release $TAG already exists"
|
|
RELEASE_ID=$(gh release view "$TAG" --json databaseId --jq '.databaseId')
|
|
RELEASE_URL=$(gh release view "$TAG" --json url --jq '.url')
|
|
else
|
|
# Get release notes from tag message
|
|
RELEASE_NOTES=$(git tag -l --format='%(contents)' "${TAG}")
|
|
if [[ -z "$RELEASE_NOTES" || "$RELEASE_NOTES" =~ ^[[:space:]]*$ ]]; then
|
|
if [[ "$IS_PRERELEASE" == "true" ]]; then
|
|
RELEASE_NOTES="Pre-release ${VERSION} (${RELEASE_TYPE})"
|
|
else
|
|
RELEASE_NOTES="Release ${VERSION}"
|
|
fi
|
|
fi
|
|
|
|
# Create release title
|
|
if [[ "$IS_PRERELEASE" == "true" ]]; then
|
|
TITLE="RustFS $VERSION (${RELEASE_TYPE})"
|
|
else
|
|
TITLE="RustFS $VERSION"
|
|
fi
|
|
|
|
# Create the release
|
|
PRERELEASE_FLAG=""
|
|
if [[ "$IS_PRERELEASE" == "true" ]]; then
|
|
PRERELEASE_FLAG="--prerelease"
|
|
fi
|
|
|
|
gh release create "$TAG" \
|
|
--title "$TITLE" \
|
|
--notes "$RELEASE_NOTES" \
|
|
$PRERELEASE_FLAG \
|
|
--draft
|
|
|
|
RELEASE_ID=$(gh release view "$TAG" --json databaseId --jq '.databaseId')
|
|
RELEASE_URL=$(gh release view "$TAG" --json url --jq '.url')
|
|
fi
|
|
|
|
echo "release_id=$RELEASE_ID" >> $GITHUB_OUTPUT
|
|
echo "release_url=$RELEASE_URL" >> $GITHUB_OUTPUT
|
|
echo "Created release: $RELEASE_URL"
|
|
|
|
# Prepare and upload release assets
|
|
upload-release-assets:
|
|
name: Upload Release Assets
|
|
needs: [ build-check, build-rustfs, create-release ]
|
|
if: startsWith(github.ref, 'refs/tags/') && needs.build-check.outputs.build_type != 'development'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
actions: read
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
|
|
- name: Download all build artifacts
|
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
|
with:
|
|
path: ./artifacts
|
|
pattern: rustfs-*
|
|
merge-multiple: true
|
|
|
|
- name: Prepare release assets
|
|
id: prepare
|
|
shell: bash
|
|
run: |
|
|
VERSION="${{ needs.build-check.outputs.version }}"
|
|
TAG="${{ needs.build-check.outputs.version }}"
|
|
|
|
mkdir -p ./release-assets
|
|
|
|
# Copy and verify artifacts (including latest files created during build)
|
|
ASSETS_COUNT=0
|
|
for file in ./artifacts/*.zip; do
|
|
if [[ -f "$file" ]]; then
|
|
cp "$file" ./release-assets/
|
|
ASSETS_COUNT=$((ASSETS_COUNT + 1))
|
|
fi
|
|
done
|
|
|
|
if [[ $ASSETS_COUNT -eq 0 ]]; then
|
|
echo "❌ No artifacts found!"
|
|
exit 1
|
|
fi
|
|
|
|
cd ./release-assets
|
|
|
|
# Generate checksums for all files (including latest versions)
|
|
if ls *.zip >/dev/null 2>&1; then
|
|
sha256sum *.zip > SHA256SUMS
|
|
sha512sum *.zip > SHA512SUMS
|
|
fi
|
|
|
|
cd ..
|
|
python3 scripts/security/generate_release_supply_chain_assets.py \
|
|
--asset-dir ./release-assets \
|
|
--version "$VERSION" \
|
|
--tag "$TAG" \
|
|
--build-type "${{ needs.build-check.outputs.build_type }}" \
|
|
--repository "${{ github.repository }}" \
|
|
--ref "${{ github.ref }}" \
|
|
--sha "${{ github.sha }}" \
|
|
--run-id "${{ github.run_id }}" \
|
|
--run-attempt "${{ github.run_attempt }}"
|
|
cd ./release-assets
|
|
|
|
echo "📦 Prepared assets:"
|
|
ls -la
|
|
|
|
echo "🔢 Total asset count: $ASSETS_COUNT"
|
|
|
|
- name: Upload to GitHub Release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
TAG="${{ needs.build-check.outputs.version }}"
|
|
|
|
cd ./release-assets
|
|
|
|
# Upload all files
|
|
for file in *; do
|
|
if [[ -f "$file" ]]; then
|
|
echo "📤 Uploading $file..."
|
|
gh release upload "$TAG" "$file" --clobber
|
|
fi
|
|
done
|
|
|
|
echo "✅ All assets uploaded successfully"
|
|
|
|
# Update latest.json for stable releases only: prerelease tags (alpha/beta/
|
|
# rc) must never overwrite the stable version pointer.
|
|
update-latest-version:
|
|
name: Update Latest Version
|
|
needs: [ build-check, upload-release-assets ]
|
|
if: startsWith(github.ref, 'refs/tags/') && needs.build-check.outputs.build_type == 'release'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Update latest.json
|
|
env:
|
|
OSS_ACCESS_KEY_ID: ${{ secrets.ALICLOUDOSS_KEY_ID }}
|
|
OSS_ACCESS_KEY_SECRET: ${{ secrets.ALICLOUDOSS_KEY_SECRET }}
|
|
OSS_REGION: cn-beijing
|
|
OSS_ENDPOINT: https://oss-cn-beijing.aliyuncs.com
|
|
shell: bash
|
|
run: |
|
|
if [[ -z "$OSS_ACCESS_KEY_ID" ]]; then
|
|
echo "⚠️ OSS credentials not available, skipping latest.json update"
|
|
exit 0
|
|
fi
|
|
|
|
VERSION="${{ needs.build-check.outputs.version }}"
|
|
TAG="${{ needs.build-check.outputs.version }}"
|
|
|
|
# Install ossutil
|
|
OSSUTIL_VERSION="2.1.1"
|
|
OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-linux-amd64.zip"
|
|
OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-linux-amd64"
|
|
OSSUTIL_SHA256="60f3a808cbbdfd4b5269b8f967c6a66f564c9dacff52d93a5d21a588976ab5a2"
|
|
|
|
curl --fail -L -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}"
|
|
printf '%s %s\n' "$OSSUTIL_SHA256" "$OSSUTIL_ZIP" | sha256sum -c -
|
|
unzip "$OSSUTIL_ZIP"
|
|
OSSUTIL_BIN="${RUNNER_TEMP}/ossutil"
|
|
mv "${OSSUTIL_DIR}/ossutil" "$OSSUTIL_BIN"
|
|
rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP"
|
|
chmod +x "$OSSUTIL_BIN"
|
|
|
|
# Create latest.json
|
|
cat > latest.json << EOF
|
|
{
|
|
"version": "${VERSION}",
|
|
"tag": "${TAG}",
|
|
"release_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"release_type": "stable",
|
|
"download_url": "https://github.com/${{ github.repository }}/releases/tag/${TAG}"
|
|
}
|
|
EOF
|
|
|
|
# Upload to OSS
|
|
"$OSSUTIL_BIN" cp latest.json oss://rustfs-version/latest.json --force
|
|
|
|
echo "✅ Updated latest.json for stable release $VERSION"
|
|
|
|
# Publish release (remove draft status)
|
|
publish-release:
|
|
name: Publish Release
|
|
needs: [ build-check, create-release, upload-release-assets ]
|
|
if: startsWith(github.ref, 'refs/tags/') && needs.build-check.outputs.build_type != 'development'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
|
|
- name: Update release notes and publish
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
TAG="${{ needs.build-check.outputs.version }}"
|
|
VERSION="${{ needs.build-check.outputs.version }}"
|
|
IS_PRERELEASE="${{ needs.build-check.outputs.is_prerelease }}"
|
|
BUILD_TYPE="${{ needs.build-check.outputs.build_type }}"
|
|
|
|
# Determine release type
|
|
if [[ "$BUILD_TYPE" == "prerelease" ]]; then
|
|
if [[ "$TAG" == *"alpha"* ]]; then
|
|
RELEASE_TYPE="alpha"
|
|
elif [[ "$TAG" == *"beta"* ]]; then
|
|
RELEASE_TYPE="beta"
|
|
elif [[ "$TAG" == *"rc"* ]]; then
|
|
RELEASE_TYPE="rc"
|
|
else
|
|
RELEASE_TYPE="prerelease"
|
|
fi
|
|
else
|
|
RELEASE_TYPE="release"
|
|
fi
|
|
|
|
# Get original release notes from tag
|
|
ORIGINAL_NOTES=$(git tag -l --format='%(contents)' "${TAG}")
|
|
if [[ -z "$ORIGINAL_NOTES" || "$ORIGINAL_NOTES" =~ ^[[:space:]]*$ ]]; then
|
|
if [[ "$IS_PRERELEASE" == "true" ]]; then
|
|
ORIGINAL_NOTES="Pre-release ${VERSION} (${RELEASE_TYPE})"
|
|
else
|
|
ORIGINAL_NOTES="Release ${VERSION}"
|
|
fi
|
|
fi
|
|
|
|
# Publish the release (remove draft status)
|
|
gh release edit "$TAG" --draft=false
|
|
|
|
echo "🎉 Released $TAG successfully!"
|
|
echo "📄 Release URL: ${{ needs.create-release.outputs.release_url }}"
|