Files
Oxicloud/.github/workflows/release-binaries.yml
T
Edouard Vanbelle fac4214856 feat(bundled-binary): release-binaries.yml + install docs + binstall metadata
Adds the tag-triggered workflow that builds 4 musl-linux + macOS
tarballs and attaches them to the tag's GitHub Release. Ships a
matching install guide (docs/install/binary.md) with SHA256SUMS
verify, systemd unit, upgrade flow, and hardware notes. Adds
[package.metadata.binstall] so 'cargo binstall oxicloud' works
automatically once the first release lands.

Also re-enables incremental compilation in the dev profile — the
'modest single-crate savings' rationale from when the crate was small
has been outgrown; full rebuild ~10 min is now the dev-loop bottleneck.
2026-08-29 11:57:48 +02:00

276 lines
11 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: "Release Binaries (musl-linux + macOS)"
# Per-run title shown in the Actions tab — makes it obvious at a
# glance which tag is being packaged and whether a manual run is a
# dry-run (build tarballs into workflow artifacts, DON'T attach to
# any GitHub Release).
run-name: >-
Release Binaries
${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && '[DRY-RUN]' || '' }}
— ${{ github.event.inputs.version || github.ref_name }}
# TRIGGERS — deliberately narrow. This workflow builds 4 platform
# binaries (~15-25 min wall-clock, matrix of native runners) and
# attaches them to a GitHub Release. Running on every push to main
# would be gratuitous CI cost + noise — the point is to package
# releases, not to sanity-check the tip. The bundled-binary
# integration test in ci.yml already covers "does the embed still
# work" on every PR.
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
version:
description: 'Existing tag to package (e.g. v0.9.0). Must exist on origin.'
required: true
dry_run:
description: 'Dry run — build + upload tarballs as workflow artifacts, skip attaching to a Release. Use to smoke-test workflow edits without publishing.'
required: false
type: boolean
default: false
# Concurrency key includes the tag ref so different tags don't cancel
# each other; `cancel-in-progress: false` because tag builds are
# unique + immutable — a superseded release build has nothing to cancel.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
jobs:
# ── Stage 1: Build the SPA once, share across all platforms ─────────
#
# The SvelteKit build is arch-independent so a single ubuntu runner
# produces static-dist/ for every downstream binary-build matrix
# entry — saves ~2 min × 4 = 8 min vs building it per platform.
frontend-build:
name: Build SPA (Vite → static-dist/)
# Publish gate — same fork-friendly pattern as docker-publish.yml.
# Canonical repo always builds; forks stay quiet unless the fork
# owner opts in via `vars.ENABLE_BINARY_RELEASE=true` under Settings
# → Secrets and variables → Actions → Variables.
if: |
github.repository == 'AtalayaLabs/OxiCloud' ||
vars.ENABLE_BINARY_RELEASE == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Build the exact tag being packaged. `github.ref` is
# refs/tags/vX.Y.Z on push, refs/heads/... on dispatch (we
# override via `inputs.version` in that case).
ref: ${{ github.event.inputs.version || github.ref }}
- uses: actions/setup-node@v4
with:
node-version: 26.3.0
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Build SPA
working-directory: frontend
run: npm ci && npm run build
- uses: actions/upload-artifact@v4
with:
name: static-dist
# Repo-root output (SvelteKit adapter-static's `pages:
# '../static-dist'`). Downstream jobs restore it to the same
# location so rust-embed's `#[folder = "static-dist/"]`
# resolves without any path juggling.
path: static-dist/
retention-days: 1
# ── Stage 2: Build one binary per target ────────────────────────────
#
# 4-way matrix — 2 musl-linux (native amd64 + arm64) + 2 macOS
# (Apple Silicon + last Intel runner tier). Windows is deferred.
#
# Linux builds run inside the `rust:1.96-alpine3.24` container the
# Dockerfile already uses — guarantees byte-for-byte parity with the
# published Docker image; zero new toolchain to maintain. macOS builds
# run natively (no cross-compile). See docs/plan/bundled-binary.md § 3
# for the target-matrix rationale.
binary-build:
name: Build ${{ matrix.triple }}
needs: frontend-build
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
strategy:
# `fail-fast: false` — one platform's compile failure shouldn't
# cancel the other three. Partial releases are better than none.
fail-fast: false
matrix:
include:
- triple: x86_64-unknown-linux-musl
runner: ubuntu-22.04
container: rust:1.96-alpine3.24
rustflags: "-C target-cpu=x86-64-v2"
- triple: aarch64-unknown-linux-musl
runner: ubuntu-22.04-arm
container: rust:1.96-alpine3.24
# ARMv8-A baseline — covers Pi 4/5, Graviton, every 64-bit
# ARM Linux server. `generic` is rustc's neutral baseline.
rustflags: "-C target-cpu=generic"
- triple: aarch64-apple-darwin
runner: macos-latest
container: ""
rustflags: "-C target-cpu=apple-m1"
- triple: x86_64-apple-darwin
runner: macos-13
container: ""
rustflags: "-C target-cpu=x86-64-v2"
container: ${{ matrix.container || null }}
steps:
# Alpine container image doesn't ship the deps our build needs
# (git for build.rs's GIT_HASH stamping, musl-dev for aws-lc-sys
# C parts, plus the toolchain scaffold from the Dockerfile
# builder stage). Install once at job start.
- name: Install Alpine build deps
if: matrix.container != ''
run: apk add --no-cache musl-dev pkgconfig gcc perl make bash git curl tar
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.version || github.ref }}
- uses: Swatinem/rust-cache@v2
with:
# Key by triple so the 4 targets don't share caches
# (different feature set + different target triple = different
# compiled artefacts).
key: ${{ matrix.triple }}
- uses: actions/download-artifact@v4
with:
name: static-dist
path: static-dist/
# rustup targets are pre-installed for the runner's own host
# triple; other targets need explicit `rustup target add`. macOS
# runners already have both apple-* triples; only touch this in
# the container path where rustup's default target list is minimal.
- name: Add rustup target
if: matrix.container == '' && matrix.triple != ''
run: rustup target add ${{ matrix.triple }}
# `--features bundled-assets` bakes static-dist/ into the binary
# via rust-embed. `--bin oxicloud` — the single binary the merge
# (Deliverable 1b) consolidated everything into.
- name: Build binary
env:
# Per-triple CPU baseline — release binaries target the widest
# realistic install base for their arch. See
# docs/plan/bundled-binary.md § 3.
RUSTFLAGS: ${{ matrix.rustflags }}
# Git metadata injection — build.rs reads these env vars to
# stamp GIT_HASH / GIT_BRANCH into the binary. Without them
# `oxicloud --version` reports "unknown".
GITHUB_SHA: ${{ github.sha }}
GITHUB_REF_NAME: ${{ github.ref_name }}
run: |
cargo build --release --features bundled-assets --bin oxicloud --target ${{ matrix.triple }}
# Assemble the tarball layout documented in
# docs/plan/bundled-binary.md § 4: oxicloud + example.env +
# LICENSE + README-install.md, rooted under a per-version-per-
# triple directory so `tar xzf` lands cleanly.
- name: Package tarball
run: |
set -euo pipefail
# Version = tag stripped of leading `v` (workflow_dispatch)
# or ref_name stripped (push tag). Falls back to ref_name
# verbatim if neither strip matches.
RAW_REF="${{ github.event.inputs.version || github.ref_name }}"
VERSION="${RAW_REF#v}"
DIST="oxicloud-${VERSION}-${{ matrix.triple }}"
mkdir -p "dist/${DIST}"
cp "target/${{ matrix.triple }}/release/oxicloud" "dist/${DIST}/oxicloud"
cp example.env "dist/${DIST}/example.env"
cp LICENSE "dist/${DIST}/LICENSE"
# README-install.md may not exist yet in early releases —
# ship a stub that points at the docs site so users have
# something in the tarball. Deliverable 6 replaces it with
# a proper install guide.
if [ -f docs/install/binary.md ]; then
cp docs/install/binary.md "dist/${DIST}/README-install.md"
else
cat > "dist/${DIST}/README-install.md" <<'MD'
# OxiCloud — Installation
Full documentation: https://github.com/AtalayaLabs/OxiCloud/tree/main/docs
Quickstart:
1. Set DATABASE_URL to a PostgreSQL 13+ instance
(with pg_trgm + ltree extensions).
2. Copy example.env → .env, edit as needed.
3. Run ./oxicloud.
Optional: install ffmpeg for server-side video thumbnails
(or set OXICLOUD_ENABLE_VIDEO_THUMBNAILS=false to disable).
MD
fi
# Deterministic tar (owner/group/mtime pinned) so re-running
# the build produces byte-identical archives — helps with
# reproducible-build audits and cheap hash verification.
tar --owner=0 --group=0 -czf "dist/${DIST}.tar.gz" -C dist "${DIST}"
ls -la "dist/${DIST}.tar.gz"
- uses: actions/upload-artifact@v4
with:
name: tarball-${{ matrix.triple }}
path: dist/*.tar.gz
retention-days: 1
# ── Stage 3: Attach all tarballs + SHA256SUMS to the Release ────────
#
# `dry_run: true` (workflow_dispatch only) skips this job — the
# binary tarballs stay as workflow artifacts (accessible from the
# run page for 1 day) but nothing lands on any Release.
release:
name: Attach tarballs to GitHub Release
needs: binary-build
if: |
needs.binary-build.result == 'success' &&
(github.event_name != 'workflow_dispatch' || github.event.inputs.dry_run != 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: tarball-*
path: dist/
merge-multiple: true
- name: Compute SHA256SUMS
run: |
set -euo pipefail
cd dist
# Sort output for stable ordering across re-runs — the file
# doubles as a manifest an operator can `diff` between two
# release runs to prove they're identical.
sha256sum *.tar.gz | sort > SHA256SUMS
cat SHA256SUMS
# softprops/action-gh-release@v2 semantics:
# - If the Release for this tag EXISTS (created by release.yml
# which runs in parallel on the same tag push), attaches the
# files to it.
# - If it doesn't yet exist (race — release.yml still running),
# creates a bare Release which release.yml then fills in with
# notes when it finishes.
# Benign either way; see docs/plan/bundled-binary.md § 5
# "Parallel-fire behaviour on tag push".
- name: Attach to Release
uses: softprops/action-gh-release@v2
with:
files: |
dist/*.tar.gz
dist/SHA256SUMS
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}