Add Rust CI workflow

f137a5d · Gabriel Morell · 2026-09-19 04:56

3 files +326 -0
Message
{commit_body(@commit)}

Files changed

added .github/workflows/rust-ci.yml
+326 −0
@@ -0,0 +1,326 @@
1 +name: Rust CI
2 +
3 +on:
4 + push:
5 + branches: [ master ]
6 + tags: [ 'v*' ]
7 + pull_request:
8 + branches: [ master ]
9 +
10 +env:
11 + CARGO_TERM_COLOR: always
12 +
13 +jobs:
14 + build:
15 + strategy:
16 + matrix:
17 + include:
18 + - runner: arc-runner-set
19 + arch: x86_64
20 + - runner: ubuntu-24.04-arm
21 + arch: aarch64
22 + runs-on: ${{ matrix.runner }}
23 + permissions:
24 + contents: read
25 + packages: write
26 +
27 + steps:
28 + - uses: actions/checkout@v4
29 +
30 + - name: Install system dependencies
31 + run: |
32 + if command -v apt-get &> /dev/null; then
33 + sudo apt-get update
34 + # build-essential/pkg-config for the C shims nix and blake3 build;
35 + # git because a bare self-hosted container has none and some build
36 + # scripts want it. No OpenSSL: the iroh stack is rustls all the way.
37 + sudo apt-get install -y build-essential pkg-config git
38 + elif command -v dnf &> /dev/null; then
39 + sudo dnf install -y gcc gcc-c++ pkgconfig git
40 + elif command -v yum &> /dev/null; then
41 + sudo yum install -y gcc gcc-c++ pkgconfig git
42 + elif command -v pacman &> /dev/null; then
43 + sudo pacman -Sy --noconfirm base-devel pkgconf git
44 + elif command -v apk &> /dev/null; then
45 + sudo apk add --no-cache build-base pkgconfig git
46 + else
47 + echo "Package manager not found. Install a C toolchain, pkg-config and git manually."
48 + exit 1
49 + fi
50 +
51 + - name: Install Rust
52 + uses: dtolnay/rust-toolchain@stable
53 + with:
54 + toolchain: "1.92"
55 + components: rustfmt, clippy
56 +
57 + - name: Setup Rust cache
58 + uses: Swatinem/rust-cache@v2
59 +
60 + - name: Build
61 + run: cargo build --workspace --verbose
62 +
63 + - name: Run tests
64 + run: cargo test --workspace --verbose
65 +
66 + - name: Check formatting
67 + run: cargo fmt --all -- --check
68 +
69 + - name: Run clippy
70 + run: cargo clippy --workspace --all-targets -- -D warnings
71 +
72 + - name: Build release binaries
73 + run: cargo build --workspace --release --verbose
74 +
75 + - name: Strip binaries
76 + run: strip target/release/devendra-agent target/release/devendra-controller
77 +
78 + - name: Install cargo-deb
79 + run: cargo install cargo-deb
80 +
81 + # Every CI build has to carry a distinct version, or the second
82 + # one publishes the same filename with different bytes and the
83 + # registry correctly refuses it with a 409 — and no machine that
84 + # installed the first ever sees the second.
85 + #
86 + # `+` rather than `-`: in both ecosystems `0.1.0+dev.<stamp>`
87 + # sorts *after* 0.1.0, which is what you want from a repository
88 + # you added in order to run the latest build. A tag publishes the
89 + # bare Cargo version.
90 + - name: Stamp the package version
91 + id: version
92 + run: |
93 + set -euo pipefail
94 + # The crates all inherit version.workspace = true, so the one
95 + # version that matters lives under [workspace.package] here.
96 + base=$(grep -m1 '^version = ' Cargo.toml | cut -d'"' -f2)
97 +
98 + if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
99 + version="$base"
100 + else
101 + version="$base+dev.$(date -u +%s)"
102 + fi
103 +
104 + echo "version: $version"
105 + echo "version=$version" >> "$GITHUB_OUTPUT"
106 +
107 + # Rewritten after the release build, so the binaries are cached
108 + # against the unstamped manifest and only packaging sees this.
109 + sed -i "0,/^version = .*/s//version = \"$version\"/" Cargo.toml
110 +
111 + # Only the agent has [package.metadata.deb] — it is the thing that
112 + # gets installed on every managed host. The controller is run from
113 + # the repo, so it has nothing to package yet.
114 + - name: Build Debian package
115 + run: cargo deb -p devendra-agent --no-build --no-strip
116 +
117 + # `vars.CI_FORGE_URL` is what the forge injects into a run; fall
118 + # back to what the runner already knows, and say so plainly if
119 + # neither resolves rather than handing curl an empty URL.
120 + - name: Resolve the forge URL
121 + env:
122 + CI_FORGE_URL: ${{ vars.CI_FORGE_URL }}
123 + run: |
124 + set -euo pipefail
125 + forge_url=${CI_FORGE_URL:-${GITHUB_SERVER_URL:-}}
126 +
127 + if [ -z "$forge_url" ]; then
128 + echo "No forge URL available." >&2
129 + echo "Set a CI_FORGE_URL variable on this repository or its org." >&2
130 + exit 1
131 + fi
132 +
133 + echo "FORGE_URL=$forge_url" >> "$GITHUB_ENV"
134 +
135 + # `${{ github.token }}` is minted for this job and authorized to
136 + # publish to THIS repository's packages and nowhere else, so
137 + # there is no secret to create first.
138 + #
139 + # Pull requests don't publish: a PR build is a check, and a
140 + # registry that carries every proposed change is not a registry
141 + # anyone can install from.
142 + - name: Publish the .deb to the forge
143 + if: github.event_name != 'pull_request'
144 + env:
145 + REPOSITORY: ${{ github.repository }}
146 + TOKEN: ${{ github.token }}
147 + run: |
148 + set -euo pipefail
149 +
150 + for deb in target/debian/*.deb; do
151 + echo "publishing $deb"
152 + curl --fail-with-body -sS \
153 + -u "gitgud-actions:$TOKEN" \
154 + -H "Content-Type: application/vnd.debian.binary-package" \
155 + --data-binary "@$deb" \
156 + "$FORGE_URL/pkg/deb/$REPOSITORY/upload?dist=stable&component=main"
157 + echo
158 + done
159 +
160 + - name: How to install it
161 + if: github.event_name != 'pull_request'
162 + env:
163 + OWNER: ${{ github.repository_owner }}
164 + run: |
165 + cat <<EOF
166 + # Debian / Ubuntu
167 + curl -fsSL $FORGE_URL/pkg/_keys/deb/$OWNER | sudo tee /etc/apt/keyrings/$OWNER.asc >/dev/null
168 + echo "deb [signed-by=/etc/apt/keyrings/$OWNER.asc] $FORGE_URL/pkg/deb/$OWNER/_all stable main" | sudo tee /etc/apt/sources.list.d/$OWNER.list
169 + sudo apt update && sudo apt install devendra-agent
170 +
171 + # Then put a join ticket in /etc/devendra/agent.env and:
172 + sudo systemctl start devendra-agent
173 + EOF
174 +
175 + # Artifact storage is a GitHub feature; this forge has none, so
176 + # these steps have nothing to upload to. They used to run and
177 + # fail with continue-on-error, which is a red step on every
178 + # single run — and a build that is always a bit red teaches
179 + # everyone to stop reading it. Skipping says the same thing
180 + # honestly and leaves the badge meaningful.
181 + - name: Upload binaries
182 + uses: actions/upload-artifact@v4
183 + if: ${{ startsWith(github.server_url, 'https://github.com') }}
184 + with:
185 + name: devendra-${{ matrix.arch }}-linux
186 + path: |
187 + target/release/devendra-agent
188 + target/release/devendra-controller
189 +
190 + - name: Upload Debian package
191 + uses: actions/upload-artifact@v4
192 + if: ${{ startsWith(github.server_url, 'https://github.com') }}
193 + with:
194 + name: devendra-agent-${{ matrix.arch }}-linux.deb
195 + path: target/debian/*.deb
196 +
197 + release:
198 + name: Release
199 + if: startsWith(github.ref, 'refs/tags/v')
200 + needs: build
201 + permissions:
202 + contents: write
203 + packages: write
204 + strategy:
205 + matrix:
206 + include:
207 + - runner: arc-runner-set
208 + arch: x86_64
209 + - runner: ubuntu-24.04-arm
210 + arch: aarch64
211 + runs-on: ${{ matrix.runner }}
212 +
213 + # Linux only, on purpose: the agent reconciles systemd units and
214 + # resolves uids through nix, so there is no macOS or Windows build
215 + # to ship.
216 + steps:
217 + - uses: actions/checkout@v4
218 +
219 + - name: Install system dependencies
220 + run: |
221 + if command -v apt-get &> /dev/null; then
222 + sudo apt-get update
223 + sudo apt-get install -y build-essential pkg-config git
224 + elif command -v dnf &> /dev/null; then
225 + sudo dnf install -y gcc gcc-c++ pkgconfig git
226 + else
227 + echo "Package manager not found. Install a C toolchain, pkg-config and git manually."
228 + exit 1
229 + fi
230 +
231 + - name: Install Rust
232 + uses: dtolnay/rust-toolchain@stable
233 + with:
234 + toolchain: "1.92"
235 +
236 + - name: Setup Rust cache
237 + uses: Swatinem/rust-cache@v2
238 + with:
239 + key: release-${{ matrix.arch }}
240 +
241 + - name: Build release binaries
242 + run: cargo build --workspace --release --verbose
243 +
244 + - name: Strip binaries
245 + run: strip target/release/devendra-agent target/release/devendra-controller
246 +
247 + - name: Create release archive
248 + run: |
249 + tar -czf devendra-${{ matrix.arch }}-linux-gnu.tar.gz \
250 + -C target/release devendra-agent devendra-controller
251 +
252 + - name: Install cargo-deb
253 + run: cargo install cargo-deb
254 +
255 + - name: Build Debian package
256 + run: cargo deb -p devendra-agent --no-build --no-strip
257 +
258 + - name: Upload release archive
259 + uses: actions/upload-artifact@v4
260 + if: ${{ startsWith(github.server_url, 'https://github.com') }}
261 + with:
262 + name: devendra-${{ matrix.arch }}-linux-gnu.tar.gz
263 + path: devendra-${{ matrix.arch }}-linux-gnu.tar.gz
264 +
265 + - name: Upload Debian package
266 + uses: actions/upload-artifact@v4
267 + if: ${{ startsWith(github.server_url, 'https://github.com') }}
268 + with:
269 + name: devendra-agent-${{ matrix.arch }}.deb
270 + path: target/debian/*.deb
271 +
272 + create-release:
273 + name: Create Release
274 + runs-on: arc-runner-set
275 + # Makes a *GitHub* Release out of artifacts a GitHub runner uploaded.
276 + # Off GitHub it would download nothing and then fail, which is the
277 + # permanently-red build this workflow is trying to stop having.
278 + # Tagged builds still publish their packages — that happens in the
279 + # build job, which runs everywhere.
280 + if: >-
281 + startsWith(github.ref, 'refs/tags/v') &&
282 + startsWith(github.server_url, 'https://github.com')
283 + needs: release
284 + permissions:
285 + contents: write
286 + steps:
287 + - uses: actions/checkout@v4
288 +
289 + - name: Download all artifacts
290 + uses: actions/download-artifact@v4
291 + with:
292 + path: artifacts
293 +
294 + - name: Create release notes
295 + run: |
296 + cat > release_notes.md <<EOF
297 + # devendra ${GITHUB_REF_NAME}
298 +
299 + ## Contents
300 +
301 + - \`devendra-agent\` — host agent (systemd unit + quadlet reconciler)
302 + - \`devendra-controller\` — controller web UI
303 +
304 + ## Supported platforms
305 +
306 + - Linux (x86_64)
307 + - Linux (aarch64)
308 +
309 + ## Installation
310 +
311 + Install the \`.deb\` on each managed host, put a join ticket in
312 + \`/etc/devendra/agent.env\`, then \`systemctl start devendra-agent\`.
313 + EOF
314 +
315 + - name: Create GitHub Release
316 + uses: softprops/action-gh-release@v1
317 + with:
318 + files: |
319 + artifacts/*/*.tar.gz
320 + artifacts/*/*.deb
321 + body_path: release_notes.md
322 + draft: false
323 + prerelease: ${{ contains(github.ref_name, '-') }}
324 + generate_release_notes: true
325 + env:
326 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Parents: e832e77