Drops docker/login-action, docker/setup-buildx-action and docker/build-push-action. This is a single-platform build of one Dockerfile, which dockerd's integrated BuildKit already does; the only thing the actions added was buildx's docker-container driver, and standing that up means pulling moby/buildkit and handing it the socket for no gain. docker login reads the token from stdin rather than -p, and a logout step runs with always() so a failed build does not leave credentials in ~/.docker/config.json on this persistent runner. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
216 lines
9.8 KiB
YAML
216 lines
9.8 KiB
YAML
name: Release
|
|
|
|
# Builds the Docker image, the Windows installers (NSIS + portable) and the Linux
|
|
# AppImage, then publishes all of it: the image goes to the Gitea container
|
|
# registry as `latest` + the tag, the installers are attached to a Gitea release
|
|
# for that tag. Flatpak is intentionally not built here — flatpak-builder's
|
|
# sandbox (bwrap) needs working user namespaces, which a containerized Actions
|
|
# runner is not guaranteed to have; it stays a local-only step via
|
|
# scripts/publish.ps1.
|
|
#
|
|
# There is no Windows runner in this setup, so the "win" target (NSIS + portable)
|
|
# is cross-built on the Linux runner via Wine, the same path
|
|
# scripts/build-release.ps1 uses for -UseWsl. Unsigned either way.
|
|
#
|
|
# Requires two Actions secrets on this repo (Settings > Actions > Secrets), a
|
|
# token with package read/write *and* repository write (write:repository) scope:
|
|
# BUILD_USER - registry/API username
|
|
# BUILD_TOKEN - registry/API token
|
|
# (named BUILD_* rather than GITEA_* because Gitea rejects secrets whose name
|
|
# starts with GITEA)
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- '*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: 'Tag to publish (e.g. v1.2.3)'
|
|
required: true
|
|
type: string
|
|
|
|
env:
|
|
IMAGE: git.azuze.fr/kawa/motionity
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: docker-build
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0 # full tag history, for the changelog compare link
|
|
|
|
- name: Resolve tag
|
|
id: vars
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
DISPATCH_TAG: ${{ inputs.tag }}
|
|
REF_TAG: ${{ github.ref_name }}
|
|
run: |
|
|
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
|
|
echo "tag=$DISPATCH_TAG" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "tag=$REF_TAG" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
|
|
- name: Install build tools (jq, Wine for the NSIS uninstaller)
|
|
run: |
|
|
sudo dpkg --add-architecture i386
|
|
sudo apt-get update
|
|
sudo apt-get install -y jq wine locales
|
|
# makensis converts its argv through the locale charset and aborts with
|
|
# "FATAL: main argv conversion failed!" under C/POSIX. C.UTF-8 is a glibc
|
|
# built-in only from 2.35 (Debian 12+), so en_US.UTF-8 is generated instead.
|
|
#
|
|
# `locale-gen <name>` is an Ubuntu extension: plain Debian's locale-gen
|
|
# takes no arguments and only reads /etc/locale.gen, so the entry goes in
|
|
# there first. The anchored grep does not match the line the package ships
|
|
# commented out.
|
|
grep -q '^en_US.UTF-8 UTF-8' /etc/locale.gen \
|
|
|| echo 'en_US.UTF-8 UTF-8' | sudo tee -a /etc/locale.gen
|
|
sudo locale-gen
|
|
locale -a
|
|
locale -a | grep -qiE '^en_US\.utf-?8$' || {
|
|
echo "en_US.UTF-8 was not generated; makensis will abort on argv conversion" >&2
|
|
exit 1
|
|
}
|
|
|
|
- name: Ensure PowerShell
|
|
# Debian ships no snapd by default, so this pulls Microsoft's portable
|
|
# linux-x64 tarball instead — no package repo or GPG key setup needed.
|
|
# A no-op on repeat runs once it's on PATH, since this runner is a
|
|
# persistent host, not a fresh container per job.
|
|
run: |
|
|
if command -v pwsh >/dev/null; then exit 0; fi
|
|
url=$(curl -sL https://api.github.com/repos/PowerShell/PowerShell/releases/latest \
|
|
| jq -r '.assets[] | select(.name | test("linux-x64\\.tar\\.gz$")) | .browser_download_url')
|
|
[ -n "$url" ] || { echo "could not resolve a PowerShell linux-x64 release asset" >&2; exit 1; }
|
|
sudo mkdir -p /opt/microsoft/powershell/7
|
|
curl -sL "$url" | sudo tar xz -C /opt/microsoft/powershell/7
|
|
sudo ln -sf /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh
|
|
sudo chmod +x /opt/microsoft/powershell/7/pwsh
|
|
|
|
- name: makensis locale probe
|
|
# Diagnostic only, never fails the job. If `-VERSION` prints a version under
|
|
# en_US.UTF-8 and "FATAL: main argv conversion failed!" under C, the locale
|
|
# really is the whole story. If it fails under both, it is not the locale and
|
|
# the next thing to try is DEBUG=electron-builder on the build step to see the
|
|
# argv makensis is actually handed. Prints nothing useful on the very first
|
|
# run of a fresh runner, where the NSIS bundle has not been downloaded yet.
|
|
continue-on-error: true
|
|
run: |
|
|
locale || true
|
|
mk=$(find "$HOME/.cache/electron-builder" /var/lib/gitea-runner/.cache/electron-builder \
|
|
-type f -name makensis -path '*/linux/*' 2>/dev/null | head -n1)
|
|
echo "makensis: ${mk:-<not downloaded yet>}"
|
|
[ -n "$mk" ] || exit 0
|
|
echo "--- LC_ALL=en_US.UTF-8"
|
|
LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 "$mk" -VERSION || echo "failed, exit $?"
|
|
echo "--- LC_ALL=C"
|
|
LC_ALL=C LANG=C "$mk" -VERSION || echo "failed, exit $?"
|
|
|
|
- name: Build Windows installers + Linux AppImage
|
|
shell: pwsh
|
|
env:
|
|
TAG: ${{ steps.vars.outputs.tag }}
|
|
# makensis's argv conversion aborts with ERR_ELECTRON_BUILDER_CANNOT_EXECUTE
|
|
# / "main argv conversion failed!" under the C/POSIX locale that a
|
|
# minimal Debian box defaults to. en_US.UTF-8 is generated above.
|
|
LANG: en_US.UTF-8
|
|
LC_ALL: en_US.UTF-8
|
|
run: ./scripts/build-release.ps1 -Targets win,linux-appimage -Tag $env:TAG
|
|
|
|
# Plain docker rather than docker/login-action + setup-buildx-action +
|
|
# build-push-action. This is a single-platform build of one Dockerfile, which
|
|
# dockerd's own integrated BuildKit does; the actions' only added value here
|
|
# would be the docker-container driver, and standing that up means pulling
|
|
# moby/buildkit and mounting the socket into it for no gain.
|
|
#
|
|
# --password-stdin, never -p: an argument is visible in ps and in the daemon's
|
|
# process list for as long as the login runs.
|
|
- name: Build and push image
|
|
env:
|
|
TAG: ${{ steps.vars.outputs.tag }}
|
|
REGISTRY_USER: ${{ secrets.BUILD_USER }}
|
|
REGISTRY_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
printf '%s' "$REGISTRY_TOKEN" \
|
|
| docker login git.azuze.fr -u "$REGISTRY_USER" --password-stdin
|
|
docker build -t "$IMAGE:latest" -t "$IMAGE:$TAG" .
|
|
docker push "$IMAGE:latest"
|
|
docker push "$IMAGE:$TAG"
|
|
|
|
# This runner is a persistent host, so the credentials written by docker login
|
|
# would otherwise sit in ~/.docker/config.json between jobs. always(): a failed
|
|
# build is exactly when they must not be left behind.
|
|
- name: Log out of git.azuze.fr
|
|
if: always()
|
|
run: docker logout git.azuze.fr || true
|
|
|
|
- name: Publish Gitea release
|
|
env:
|
|
TAG: ${{ steps.vars.outputs.tag }}
|
|
GITEA_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
SERVER_URL: ${{ github.server_url }}
|
|
REPO_PATH: ${{ github.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
api="$SERVER_URL/api/v1/repos/$REPO_PATH/releases"
|
|
auth=(-H "Authorization: token $GITEA_TOKEN")
|
|
|
|
prev_tag=$(git tag --sort=-v:refname | grep -A1 -x -F "$TAG" | tail -n1)
|
|
if [ "$prev_tag" = "$TAG" ]; then prev_tag=""; fi
|
|
if [ -n "$prev_tag" ]; then
|
|
body="**Full Changelog**: $SERVER_URL/$REPO_PATH/compare/$prev_tag...$TAG"
|
|
else
|
|
body="**Full Changelog**: first release"
|
|
fi
|
|
echo "changelog: $body"
|
|
|
|
status=$(curl -s -o /tmp/release.json -w '%{http_code}' "${auth[@]}" "$api/tags/$TAG")
|
|
if [ "$status" = "200" ]; then
|
|
release_id=$(jq -r '.id' /tmp/release.json)
|
|
echo "reusing release $TAG (id $release_id)"
|
|
elif [ "$status" = "404" ]; then
|
|
echo "creating release $TAG"
|
|
payload=$(jq -n --arg tag "$TAG" --arg name "Motionity $TAG" --arg body "$body" \
|
|
'{tag_name:$tag, name:$name, body:$body, draft:false}')
|
|
status=$(curl -s -o /tmp/release.json -w '%{http_code}' -X POST "${auth[@]}" \
|
|
-H "Content-Type: application/json" -d "$payload" "$api")
|
|
[ "$status" = "201" ] || { echo "release creation failed ($status):"; cat /tmp/release.json; exit 1; }
|
|
release_id=$(jq -r '.id' /tmp/release.json)
|
|
else
|
|
echo "unexpected status $status fetching release:"; cat /tmp/release.json; exit 1
|
|
fi
|
|
|
|
# Keep the changelog link authoritative even when reusing an existing
|
|
# release (a manual re-run after a later tag was pushed).
|
|
curl -sf -X PATCH "${auth[@]}" -H "Content-Type: application/json" \
|
|
-d "$(jq -n --arg body "$body" '{body:$body}')" \
|
|
"$api/$release_id" -o /dev/null
|
|
|
|
for f in "dist/motionity-$TAG-win-x64-setup.exe" \
|
|
"dist/motionity-$TAG-win-x64-portable.exe" \
|
|
"dist/motionity-$TAG-linux-x86_64.AppImage" \
|
|
"dist/SHA256SUMS.txt"; do
|
|
[ -f "$f" ] || { echo "expected artifact missing: $f" >&2; exit 1; }
|
|
name=$(basename "$f")
|
|
existing_id=$(jq -r --arg n "$name" '.assets[]? | select(.name==$n) | .id' /tmp/release.json)
|
|
if [ -n "$existing_id" ]; then
|
|
echo "replacing attachment $name (asset $existing_id)"
|
|
curl -sf -X DELETE "${auth[@]}" "$api/$release_id/assets/$existing_id" -o /dev/null
|
|
else
|
|
echo "adding attachment $name"
|
|
fi
|
|
encoded=$(jq -rn --arg n "$name" '$n|@uri')
|
|
curl -sf "${auth[@]}" -F "attachment=@$f" "$api/$release_id/assets?name=$encoded" -o /dev/null
|
|
done
|
|
|
|
echo "release: $SERVER_URL/$REPO_PATH/releases/tag/$TAG"
|