Skip to content

Commit 4249084

Browse files
ci: tag-driven release pipeline (crates.io + Hex + GitHub Releases) (#16)
1 parent 59190e5 commit 4249084

3 files changed

Lines changed: 216 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
8+
# Least privilege by default; jobs that push the version bump or touch the
9+
# GitHub release escalate to contents: write at the job level.
10+
permissions:
11+
contents: read
12+
13+
# Never cancel a release mid-flight (a cancelled publish can leave a half-done
14+
# release); serialise per-ref instead.
15+
concurrency:
16+
group: release-${{ github.ref }}
17+
cancel-in-progress: false
18+
19+
jobs:
20+
prepare-release:
21+
name: Bump version from tag
22+
runs-on: ubuntu-latest
23+
permissions:
24+
contents: write
25+
outputs:
26+
# The release version (tag minus its leading "v") and the SHA of the bump
27+
# commit. Every downstream job checks out `sha` so it builds/publishes the
28+
# bumped tree exactly, regardless of where main moves afterwards.
29+
version: ${{ steps.bump.outputs.version }}
30+
sha: ${{ steps.bump.outputs.sha }}
31+
steps:
32+
- uses: actions/checkout@v7
33+
with:
34+
ref: main
35+
36+
- name: Write tag version into manifests and push to main
37+
id: bump
38+
run: |
39+
version="${GITHUB_REF_NAME#v}"
40+
41+
# The tag is the single source of truth: stamp it into both manifests.
42+
# Cargo.toml's [package] version is the only line starting with
43+
# `version = `; mix.exs has exactly one `version: "..."` (the project
44+
# version). `0,/re/` rewrites just the first match.
45+
sed -i -E "0,/^version = \"[^\"]*\"/s//version = \"${version}\"/" native/suidhelper/Cargo.toml
46+
sed -i -E "0,/version: \"[^\"]*\"/s//version: \"${version}\"/" mix.exs
47+
48+
git config user.name "github-actions[bot]"
49+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
50+
git add native/suidhelper/Cargo.toml mix.exs
51+
52+
# Idempotent: a re-run after the bump already landed has nothing to commit.
53+
if git diff --cached --quiet; then
54+
echo "manifests already at ${version}; nothing to commit"
55+
else
56+
git commit -m "release: v${version}"
57+
git push origin HEAD:main
58+
fi
59+
60+
echo "version=${version}" >> "$GITHUB_OUTPUT"
61+
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
62+
63+
build-suidhelper:
64+
name: Build suidhelper (${{ matrix.target }})
65+
needs: prepare-release
66+
runs-on: ubuntu-latest
67+
strategy:
68+
fail-fast: true
69+
matrix:
70+
target:
71+
- x86_64-unknown-linux-musl
72+
- aarch64-unknown-linux-musl
73+
defaults:
74+
run:
75+
working-directory: native/suidhelper
76+
steps:
77+
- uses: actions/checkout@v7
78+
with:
79+
ref: ${{ needs.prepare-release.outputs.sha }}
80+
81+
# rustup reads native/suidhelper/rust-toolchain.toml on the first cargo
82+
# call and installs the pinned nightly + both musl targets.
83+
- name: Show toolchain
84+
run: rustup show
85+
86+
- uses: Swatinem/rust-cache@v2
87+
with:
88+
workspaces: native/suidhelper
89+
90+
- name: Build release binary
91+
run: cargo build --release --target ${{ matrix.target }}
92+
93+
- name: Stage renamed artifact
94+
run: |
95+
v="${{ needs.prepare-release.outputs.version }}"
96+
out="hyper-suidhelper-${v}-${{ matrix.target }}"
97+
mkdir -p dist
98+
cp "target/${{ matrix.target }}/release/hyper-suidhelper" "dist/${out}"
99+
100+
- uses: actions/upload-artifact@v7
101+
with:
102+
name: suidhelper-${{ matrix.target }}
103+
path: native/suidhelper/dist/*
104+
if-no-files-found: error
105+
106+
build-hex:
107+
name: Build Hex package
108+
needs: prepare-release
109+
runs-on: ubuntu-latest
110+
env:
111+
MIX_ENV: dev
112+
steps:
113+
- uses: actions/checkout@v7
114+
with:
115+
ref: ${{ needs.prepare-release.outputs.sha }}
116+
117+
- uses: erlef/setup-beam@v1
118+
id: beam
119+
with:
120+
elixir-version: "1.20"
121+
otp-version: "28"
122+
123+
- name: Cache deps and _build
124+
uses: actions/cache@v6
125+
with:
126+
path: |
127+
deps
128+
_build
129+
key: ${{ runner.os }}-release-mix-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-${{ hashFiles('mix.lock') }}
130+
restore-keys: |
131+
${{ runner.os }}-release-mix-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-
132+
133+
- name: Install dependencies
134+
run: mix deps.get
135+
136+
- name: Build package tarball
137+
run: mix hex.build --output "hypervm-${{ needs.prepare-release.outputs.version }}.tar"
138+
139+
- uses: actions/upload-artifact@v7
140+
with:
141+
name: hex-package
142+
path: hypervm-*.tar
143+
if-no-files-found: error
144+
145+
github-release:
146+
name: Draft GitHub release
147+
needs: [build-suidhelper, build-hex]
148+
runs-on: ubuntu-latest
149+
permissions:
150+
contents: write
151+
steps:
152+
- name: Download all build artifacts
153+
uses: actions/download-artifact@v8
154+
with:
155+
path: artifacts
156+
157+
- name: Collect assets and compute checksums
158+
run: |
159+
mkdir -p release
160+
find artifacts -type f \( -name 'hyper-suidhelper-*' -o -name 'hypervm-*.tar' \) \
161+
-exec cp {} release/ \;
162+
( cd release && sha256sum hyper-suidhelper-* hypervm-*.tar > SHA256SUMS )
163+
ls -la release
164+
165+
- name: Create draft release
166+
uses: softprops/action-gh-release@v3
167+
with:
168+
draft: true
169+
tag_name: ${{ github.ref_name }}
170+
name: ${{ github.ref_name }}
171+
generate_release_notes: true
172+
files: release/*
173+
174+
publish-hex:
175+
name: Publish to Hex
176+
needs: [prepare-release, build-hex, github-release]
177+
runs-on: ubuntu-latest
178+
environment: release
179+
env:
180+
MIX_ENV: dev
181+
steps:
182+
- uses: actions/checkout@v7
183+
with:
184+
ref: ${{ needs.prepare-release.outputs.sha }}
185+
186+
- uses: erlef/setup-beam@v1
187+
with:
188+
elixir-version: "1.20"
189+
otp-version: "28"
190+
191+
- name: Install dependencies
192+
run: mix deps.get
193+
194+
# HEX_API_KEY authenticates non-interactively; --yes skips the prompt.
195+
# Publishes both the package and its docs.
196+
- name: hex publish
197+
env:
198+
HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
199+
run: mix hex.publish --yes
200+
201+
finalize-release:
202+
name: Publish GitHub release
203+
needs: [publish-hex]
204+
runs-on: ubuntu-latest
205+
permissions:
206+
contents: write
207+
steps:
208+
# Flip the draft (created earlier) to live only after both registries
209+
# have the new version. github.token has the contents:write granted above.
210+
- name: Mark release as published
211+
env:
212+
GH_TOKEN: ${{ github.token }}
213+
run: gh release edit "${{ github.ref_name }}" --draft=false

mix.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ defmodule Hyper.MixProject do
160160
# Hex package metadata. Required for `mix hex.publish`.
161161
defp package do
162162
[
163+
description: "A distributed orchestrator for Firecracker microVMs.",
163164
# The OTP app is `:hyper`, but `hyper` is already taken on Hex, so the
164165
# package publishes as `hypervm`. Package name and app name are independent.
165166
name: "hypervm",

native/suidhelper/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ name = "hyper-suidhelper"
33
version = "0.1.0"
44
edition = "2021"
55
description = "Tiny setuid-root helper that runs a whitelisted set of device commands for the unprivileged Hyper node."
6+
license = "AGPL-3.0-or-later"
7+
repository = "https://github.com/harmont-dev/hyper"
68

79
[[bin]]
810
name = "hyper-suidhelper"

0 commit comments

Comments
 (0)