Add a Gitea Actions workflow that publishes the Linux AppImage.
AppImage release / appimage (push) Successful in 9m35s
AppImage release / appimage (push) Successful in 9m35s
The job builds with scripts/build-appimage.sh on an existing v* tag and uploads the AppImage as a release asset. Co-authored-by: catpersonisme <catpersonisme@gmail.com>
This commit is contained in:
1 file changed
+274
@@ -0,0 +1,274 @@
|
||||
# Builds the Linux AppImage with scripts/build-appimage.sh and uploads it
|
||||
# to the release for an existing v* tag. Pushing this file does not run a build.
|
||||
# The act_runner label ubuntu-latest is docker://catthehacker/ubuntu:full-latest.
|
||||
name: AppImage release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Existing v* tag to publish. Leave empty when this run is already on a v* tag."
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
appimage:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve the existing release tag
|
||||
id: tag
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG=""
|
||||
if [[ "${GITHUB_REF_TYPE:-}" == "tag" && "${GITHUB_REF_NAME:-}" == v* ]]; then
|
||||
TAG="${GITHUB_REF_NAME}"
|
||||
fi
|
||||
if [[ -n "${INPUT_TAG}" ]]; then
|
||||
if [[ "${INPUT_TAG}" != v* ]]; then
|
||||
echo "The tag input must match v*, for example v0.1.0." >&2
|
||||
exit 1
|
||||
fi
|
||||
TAG="${INPUT_TAG}"
|
||||
fi
|
||||
if [[ -z "${TAG}" || "${TAG}" != v* ]]; then
|
||||
echo "This workflow publishes an AppImage for a tag that already exists and starts with v, such as v0.1.0." >&2
|
||||
echo "Push that tag, or run this workflow on it. It does not create a tag." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! git ls-remote --exit-code origin "refs/tags/${TAG}" >/dev/null; then
|
||||
echo "Tag ${TAG} is not on the server. This workflow does not create tags." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${GITHUB_REF_NAME:-}" != "${TAG}" ]]; then
|
||||
git fetch origin "refs/tags/${TAG}:refs/tags/${TAG}"
|
||||
git checkout --detach "refs/tags/${TAG}"
|
||||
fi
|
||||
echo "tag=${TAG}" >> "${GITHUB_OUTPUT}"
|
||||
echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Install build tools
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "$(id -u)" -eq 0 ]]; then
|
||||
apt_get() { apt-get "$@"; }
|
||||
else
|
||||
apt_get() { sudo apt-get "$@"; }
|
||||
fi
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt_get update
|
||||
apt_get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
xz-utils \
|
||||
binutils \
|
||||
git \
|
||||
jq \
|
||||
file \
|
||||
unzip
|
||||
apt_get install -y --no-install-recommends libfuse2 \
|
||||
|| apt_get install -y --no-install-recommends libfuse2t64 \
|
||||
|| echo "No libfuse2 package in this image. AppImage tools will extract instead of mounting."
|
||||
|
||||
need_node=0
|
||||
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
|
||||
need_node=1
|
||||
else
|
||||
major="$(node -p 'Number(process.versions.node.split(".")[0])')"
|
||||
if [[ "${major}" -lt 20 ]]; then
|
||||
need_node=1
|
||||
fi
|
||||
fi
|
||||
if [[ "${need_node}" -eq 1 ]]; then
|
||||
tarball="$(curl -fsSL https://nodejs.org/dist/latest-v22.x/SHASUMS256.txt | awk '/linux-x64\.tar\.xz$/ { print $2; exit }')"
|
||||
if [[ -z "${tarball}" ]]; then
|
||||
echo "Could not find a Node.js 22 linux-x64 tarball." >&2
|
||||
exit 1
|
||||
fi
|
||||
curl -fL --retry 3 -o "/tmp/${tarball}" "https://nodejs.org/dist/latest-v22.x/${tarball}"
|
||||
if [[ "$(id -u)" -eq 0 ]]; then
|
||||
tar -xJf "/tmp/${tarball}" -C /usr/local --strip-components=1
|
||||
else
|
||||
sudo tar -xJf "/tmp/${tarball}" -C /usr/local --strip-components=1
|
||||
fi
|
||||
fi
|
||||
|
||||
command -v node
|
||||
command -v npm
|
||||
command -v curl
|
||||
command -v tar
|
||||
command -v strip
|
||||
node -v
|
||||
npm -v
|
||||
|
||||
- name: Build the AppImage
|
||||
shell: bash
|
||||
env:
|
||||
APPIMAGE_EXTRACT_AND_RUN: "1"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# npm omits devDependencies when NODE_ENV=production. electron-builder is one of them.
|
||||
env -u NODE_ENV bash scripts/build-appimage.sh
|
||||
|
||||
- name: Require the AppImage to be under 600MB
|
||||
id: appimage
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
APPIMAGE="$(find dist -maxdepth 1 -name '*.AppImage' -printf '%T@ %p\n' | sort -nr | head -1 | cut -d' ' -f2-)"
|
||||
if [[ -z "${APPIMAGE}" || ! -f "${APPIMAGE}" ]]; then
|
||||
echo "No AppImage was produced in dist/." >&2
|
||||
exit 1
|
||||
fi
|
||||
SIZE="$(stat -c%s "${APPIMAGE}")"
|
||||
LIMIT=$((600 * 1024 * 1024))
|
||||
if (( SIZE > LIMIT )); then
|
||||
echo "AppImage is ${SIZE} bytes, over 600MB." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Built ${APPIMAGE} (${SIZE} bytes)."
|
||||
echo "path=${APPIMAGE}" >> "${GITHUB_OUTPUT}"
|
||||
echo "name=$(basename "${APPIMAGE}")" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Upload the AppImage to the release
|
||||
shell: bash
|
||||
env:
|
||||
GITEA_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ steps.tag.outputs.tag }}
|
||||
TARGET_SHA: ${{ steps.tag.outputs.sha }}
|
||||
APPIMAGE_PATH: ${{ steps.appimage.outputs.path }}
|
||||
ASSET_NAME: ${{ steps.appimage.outputs.name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TOKEN="${GITEA_TOKEN:-${GITHUB_TOKEN:-}}"
|
||||
if [[ -z "${TOKEN}" ]]; then
|
||||
echo "No job token. act_runner must provide a token that can publish releases." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "${APPIMAGE_PATH}" ]]; then
|
||||
echo "AppImage not found at ${APPIMAGE_PATH}." >&2
|
||||
exit 1
|
||||
fi
|
||||
SIZE="$(stat -c%s "${APPIMAGE_PATH}")"
|
||||
LIMIT=$((600 * 1024 * 1024))
|
||||
if (( SIZE > LIMIT )); then
|
||||
echo "AppImage is ${SIZE} bytes, over 600MB." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||
AUTH=( -H "Authorization: token ${TOKEN}" )
|
||||
|
||||
ref_code="$(curl -sS -o /tmp/tag-ref.json -w "%{http_code}" "${AUTH[@]}" "${API}/git/refs/tags/${TAG}")"
|
||||
if [[ "${ref_code}" != "200" ]]; then
|
||||
echo "Tag ${TAG} is not on the server (HTTP ${ref_code}). This workflow does not create tags." >&2
|
||||
cat /tmp/tag-ref.json >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NOTES_FILE="$(mktemp)"
|
||||
cat > "${NOTES_FILE}" <<EOF
|
||||
Download \`${ASSET_NAME}\` from the assets on this release.
|
||||
|
||||
Make it executable and start it:
|
||||
|
||||
\`\`\`bash
|
||||
chmod +x ${ASSET_NAME}
|
||||
./${ASSET_NAME}
|
||||
\`\`\`
|
||||
|
||||
If it does not start because FUSE is missing, run it this way instead:
|
||||
|
||||
\`\`\`bash
|
||||
APPIMAGE_EXTRACT_AND_RUN=1 ./${ASSET_NAME}
|
||||
\`\`\`
|
||||
EOF
|
||||
# The workflow YAML indent is stripped, but keep the notes flush left.
|
||||
sed -i 's/^[[:space:]]*//' "${NOTES_FILE}"
|
||||
|
||||
TITLE="Recorder Tab Converter ${TAG}"
|
||||
create_body="$(jq -n \
|
||||
--arg tag "${TAG}" \
|
||||
--arg name "${TITLE}" \
|
||||
--rawfile body "${NOTES_FILE}" \
|
||||
--arg target "${TARGET_SHA}" \
|
||||
'{tag_name:$tag, name:$name, body:$body, draft:false, prerelease:false, target_commitish:$target}')"
|
||||
|
||||
create_code="$(curl -sS -o /tmp/release.json -w "%{http_code}" \
|
||||
-X POST "${AUTH[@]}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${create_body}" \
|
||||
"${API}/releases")"
|
||||
|
||||
if [[ "${create_code}" == "201" ]]; then
|
||||
RELEASE_ID="$(jq -r '.id' /tmp/release.json)"
|
||||
elif [[ "${create_code}" == "409" ]]; then
|
||||
get_code="$(curl -sS -o /tmp/release.json -w "%{http_code}" "${AUTH[@]}" "${API}/releases/tags/${TAG}")"
|
||||
if [[ "${get_code}" != "200" ]]; then
|
||||
echo "Release for ${TAG} already exists but could not be read (HTTP ${get_code})." >&2
|
||||
cat /tmp/release.json >&2
|
||||
exit 1
|
||||
fi
|
||||
RELEASE_ID="$(jq -r '.id' /tmp/release.json)"
|
||||
patch_body="$(jq -n --arg name "${TITLE}" --rawfile body "${NOTES_FILE}" '{name:$name, body:$body}')"
|
||||
patch_code="$(curl -sS -o /tmp/release-patch.json -w "%{http_code}" \
|
||||
-X PATCH "${AUTH[@]}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${patch_body}" \
|
||||
"${API}/releases/${RELEASE_ID}")"
|
||||
if [[ "${patch_code}" != "200" ]]; then
|
||||
echo "Could not update the release notes (HTTP ${patch_code})." >&2
|
||||
cat /tmp/release-patch.json >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Could not create the release (HTTP ${create_code})." >&2
|
||||
cat /tmp/release.json >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${RELEASE_ID}" || "${RELEASE_ID}" == "null" ]]; then
|
||||
echo "The release id is missing." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t asset_ids < <(jq -r --arg name "${ASSET_NAME}" '.assets[]? | select(.name == $name) | .id' /tmp/release.json)
|
||||
for asset_id in "${asset_ids[@]}"; do
|
||||
[[ -z "${asset_id}" ]] && continue
|
||||
del_code="$(curl -sS -o /tmp/asset-delete.json -w "%{http_code}" \
|
||||
-X DELETE "${AUTH[@]}" \
|
||||
"${API}/releases/${RELEASE_ID}/assets/${asset_id}")"
|
||||
if [[ "${del_code}" != "204" && "${del_code}" != "200" ]]; then
|
||||
echo "Could not replace the existing asset ${ASSET_NAME} (HTTP ${del_code})." >&2
|
||||
cat /tmp/asset-delete.json >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
enc_name="$(jq -rn --arg name "${ASSET_NAME}" '$name|@uri')"
|
||||
upload_code="$(curl -sS -o /tmp/upload.json -w "%{http_code}" \
|
||||
-X POST "${AUTH[@]}" \
|
||||
-F "attachment=@${APPIMAGE_PATH};filename=${ASSET_NAME}" \
|
||||
"${API}/releases/${RELEASE_ID}/assets?name=${enc_name}")"
|
||||
if [[ "${upload_code}" != "201" ]]; then
|
||||
echo "Upload failed with HTTP ${upload_code}." >&2
|
||||
cat /tmp/upload.json >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Uploaded ${ASSET_NAME} (${SIZE} bytes) to release ${TAG}."
|
||||
jq -r '.browser_download_url // empty' /tmp/upload.json
|
||||
Reference in new issue
Block a user