Files
selfphoneng/.github/workflows/release.yml
T
iamdoubz 4143f6d064
ci / check (push) Successful in 59s
ci / images (push) Successful in 49s
probe: downloadable executables for machines without Docker
Each release attaches selfphoneng-probe-linux-x64 and -arm64 (Node's single executable
applications: esbuild bundle + postject into the official Node binary, checked against
nodejs.org's SHASUMS256.txt), the bundle as selfphoneng-probe.cjs (Node 22+) and SHA256SUMS.
Built in binary.Dockerfile with BuildKit's local output, so Gitea's runner can build it too.
deploy/probe/selfphoneng-probe.service runs it under systemd, sandboxed.
2026-10-21 12:57:04 -05:00

131 lines
6.1 KiB
YAML

# SPDX-License-Identifier: AGPL-3.0-or-later
# Container images, built only when a release is published (08 §3.1). Runs on GitHub Actions and
# Gitea Actions (both read .github/workflows); each pushes to its own registry:
# GitHub: ghcr.io/<owner>/selfphoneng-{api,asterisk,probe} (the job's GITHUB_TOKEN)
# Gitea: <gitea host>/<owner>/selfphoneng-{api,asterisk,probe} (secrets REGISTRY_USER + REGISTRY_TOKEN:
# a personal access token with write:package; Gitea's job token can't push packages)
# Tags: the version (0.0.1), plus <major>.<minor> and latest; a pre-release gets the version and dev.
# The release also gets the probe as downloads (selfphoneng-probe-linux-{x64,arm64}, .cjs, SHA256SUMS).
name: release
on:
release:
types: [published]
permissions:
contents: write # attaching the probe downloads to the release
packages: write
jobs:
images:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Version and registry
id: v
shell: bash
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
version="$(node -p "require('./package.json').version")"
# The image reports package.json's version (/api/system/status, About): they must agree.
if [ "$tag" != "v$version" ]; then
echo "::error::release tag $tag doesn't match package.json version $version (expected v$version)"
exit 1
fi
if [ "$GITHUB_SERVER_URL" = "https://github.com" ]; then
registry=ghcr.io
else
registry="${GITHUB_SERVER_URL#*://}"
fi
owner="$(echo "$GITHUB_REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')"
tags="$version"
# A pre-release (0.1.0-rc.1, or ticked "pre-release") gets its own tag and dev.
case "$version" in
*-*) tags="$tags dev" ;;
*) if [ "${{ github.event.release.prerelease }}" = true ]; then tags="$tags dev"; else tags="$tags ${version%.*} latest"; fi ;;
esac
{
echo "version=$version"
echo "registry=$registry"
echo "prefix=$registry/$owner/selfphoneng"
echo "tags=$tags"
} >> "$GITHUB_OUTPUT"
- name: Log in
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -euo pipefail
if [ "${{ steps.v.outputs.registry }}" = ghcr.io ]; then
echo "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
else
if [ -z "$REGISTRY_TOKEN" ]; then
echo "::error::set the REGISTRY_USER and REGISTRY_TOKEN secrets (a token with write:package)"
exit 1
fi
echo "$REGISTRY_TOKEN" | docker login "${{ steps.v.outputs.registry }}" -u "${REGISTRY_USER:-$GITHUB_ACTOR}" --password-stdin
fi
- name: Build
shell: bash
run: |
set -euo pipefail
p="${{ steps.v.outputs.prefix }}"
labels=(
--label "org.opencontainers.image.source=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
--label "org.opencontainers.image.version=${{ steps.v.outputs.version }}"
--label "org.opencontainers.image.revision=$GITHUB_SHA"
--label "org.opencontainers.image.licenses=AGPL-3.0-or-later"
)
docker build "${labels[@]}" -f apps/api/Dockerfile -t "$p-api:build" .
docker build "${labels[@]}" -t "$p-asterisk:build" asterisk
docker build "${labels[@]}" -f apps/probe/Dockerfile -t "$p-probe:build" .
- name: Check
shell: bash
run: |
set -euo pipefail
p="${{ steps.v.outputs.prefix }}"
# Same checks as ci.yml: every module loads, the version is the release's, Asterisk runs.
docker run --rm --entrypoint node "$p-api:build" --input-type=module -e "await import('/app/dist/app.js')"
test "$(docker run --rm --entrypoint cat "$p-api:build" /app/VERSION)" = "${{ steps.v.outputs.version }}"
docker run --rm --entrypoint asterisk "$p-asterisk:build" -V
docker run --rm "$p-probe:build" node --input-type=module -e "await import('/app/src/server.ts')"
- name: Push
shell: bash
run: |
set -euo pipefail
p="${{ steps.v.outputs.prefix }}"
for image in api asterisk probe; do
for t in ${{ steps.v.outputs.tags }}; do
docker tag "$p-$image:build" "$p-$image:$t"
docker push "$p-$image:$t"
done
done
echo "pushed: $p-{api,asterisk,probe}:{${{ steps.v.outputs.tags }}}" | tee -a "${GITHUB_STEP_SUMMARY:-/dev/null}"
- name: Probe downloads
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -euo pipefail
# The probe without Docker (08 §2.6): a .cjs for Node 22+, and Linux executables with Node
# inside. Built in BuildKit and handed back as files, so it works on Gitea's runner too.
docker build -f apps/probe/binary.Dockerfile --output type=local,dest=probe-dist .
id="${{ github.event.release.id }}"
for f in probe-dist/*; do
name=$(basename "$f")
if [ "${{ steps.v.outputs.registry }}" = ghcr.io ]; then
curl -fsS -o /dev/null -X POST -H "Authorization: Bearer $GH_TOKEN" \
-H 'Content-Type: application/octet-stream' --data-binary "@$f" \
"https://uploads.github.com/repos/$GITHUB_REPOSITORY/releases/$id/assets?name=$name"
else
# The REGISTRY_TOKEN personal access token needs write:repository for this.
curl -fsS -o /dev/null -X POST -H "Authorization: token ${REGISTRY_TOKEN:-$GH_TOKEN}" \
-F "attachment=@$f" \
"$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/$id/assets?name=$name"
fi
echo "attached $name" | tee -a "${GITHUB_STEP_SUMMARY:-/dev/null}"
done