Files
OrchestrAD/.gitea/workflows/release.yml
T
Alphaeus Mote 7c6bc94f6f ci: use github.token for registry login and release, add package permissions
The first run failed at docker login with 'unauthorized': secrets.GITEA_TOKEN
is not populated on this instance, so the registry password was empty. Switch
the registry login and release creation to github.token (always injected) and
add an explicit permissions block granting contents+packages write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 07:16:17 -04:00

226 lines
9.2 KiB
YAML

name: Release
# Fires only on a merge into main (a push to the main branch). No per-commit
# or per-PR CI runs on other branches — this is the single pipeline that turns
# what lands on main into a release + container image.
#
# Docs-only merges are skipped: touching just README/docs/LICENSE does not
# produce a new build.
on:
# Allow an on-demand run from the Gitea Actions UI/API without a dummy commit.
workflow_dispatch:
push:
branches: [main]
paths-ignore:
- 'README.md'
- 'LICENSE'
- 'docs/**'
- '.gitignore'
- '.gitattributes'
jobs:
release:
# Label must match a registered Linux runner that has Docker (used for the
# dockerized test gate and the image build/push). This is the same label the
# sibling repos use for their image jobs.
runs-on: ubuntu-host
# Grant the auto-injected Actions token the scopes this job needs: push to
# the built-in container registry (packages) and create a release (contents).
permissions:
contents: write
packages: write
env:
# Optional EXTERNAL registry override. Leave these unset to publish to the
# Gitea instance's own built-in container registry (the default below).
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST }}
REGISTRY_USER: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASS: ${{ secrets.REGISTRY_PASSWORD }}
# Injected automatically by Gitea Actions; used for the built-in registry
# login and for creating the release. github.token is always populated
# (unlike secrets.GITEA_TOKEN, which is not defined on every instance).
BUILTIN_TOKEN: ${{ github.token }}
SERVER_URL: ${{ github.server_url }}
ACTOR: ${{ github.actor }}
OWNER: ${{ github.repository_owner }}
steps:
# OrchestrAD is a SHA-1 repo, so actions/checkout works as-is. fetch-depth
# 0 is required so the HEAD commit date is available for the version.
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# Version = the UTC date of the HEAD commit, formatted yyyy.MM.dd.HHmm
# (the project's documented version scheme). Deriving it from the commit
# rather than "now" makes re-runs reproducible and keeps the image tag,
# the release tag, and the binary's embedded version identical.
- name: Compute version
id: ver
shell: bash
run: |
set -euo pipefail
VERSION="$(TZ=UTC git show -s --format=%cd --date=format-local:'%Y.%m.%d.%H%M' HEAD)"
GIT_COMMIT="$(git rev-parse HEAD)"
GIT_COMMIT_SHORT="$(git rev-parse --short HEAD)"
BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
{
echo "version=$VERSION"
echo "git_commit=$GIT_COMMIT"
echo "git_commit_short=$GIT_COMMIT_SHORT"
echo "build_time=$BUILD_TIME"
} >> "$GITHUB_OUTPUT"
echo "OrchestrAD version: $VERSION ($GIT_COMMIT_SHORT)"
# Test gate: run the Go test suite inside the same toolchain image the
# build uses. Running it in a container means the runner needs only Docker
# (no host Go/gcc), and a failure here stops the release before anything
# is published. CGO is on because the SQLite driver requires it.
- name: Test
shell: bash
run: |
set -euo pipefail
docker run --rm -v "$PWD/backend:/src" -w /src \
-e CGO_ENABLED=1 golang:1.24-alpine \
sh -c "apk add --no-cache gcc musl-dev >/dev/null && go test ./..."
# jq is used to JSON-encode the release body safely. Install it if the
# self-hosted runner does not already have it.
- name: Ensure tooling (jq)
shell: bash
run: |
set -euo pipefail
if ! command -v jq >/dev/null 2>&1; then
echo "Installing jq..."
sudo apt-get update -y && sudo apt-get install -y jq
fi
jq --version
- name: Resolve registry target
id: reg
shell: bash
run: |
set -euo pipefail
if [ -n "${REGISTRY_HOST:-}" ]; then
HOST="$REGISTRY_HOST"; USER="$REGISTRY_USER"; PASS="$REGISTRY_PASS"
echo "Using external registry $HOST"
else
HOST="$(echo "$SERVER_URL" | sed -E 's#^https?://##; s#/$##')"
USER="$ACTOR"; PASS="$BUILTIN_TOKEN"
echo "Using the built-in Gitea registry at $HOST"
fi
OWNER_LC="$(echo "$OWNER" | tr '[:upper:]' '[:lower:]')"
{
echo "host=$HOST"
echo "user=$USER"
echo "image=${HOST}/${OWNER_LC}/orchestrad"
} >> "$GITHUB_OUTPUT"
echo "::add-mask::$PASS"
echo "REGISTRY_LOGIN_PASSWORD=$PASS" >> "$GITHUB_ENV"
- name: Registry login
run: echo "$REGISTRY_LOGIN_PASSWORD" | docker login "${{ steps.reg.outputs.host }}" -u "${{ steps.reg.outputs.user }}" --password-stdin
# One multi-stage build compiles the Next.js UI, embeds it, and produces
# the Go binary. Tag both the immutable version and latest (main only).
- name: Build image
env:
IMAGE: ${{ steps.reg.outputs.image }}
VERSION: ${{ steps.ver.outputs.version }}
GIT_COMMIT: ${{ steps.ver.outputs.git_commit }}
BUILD_TIME: ${{ steps.ver.outputs.build_time }}
run: |
set -euo pipefail
docker build \
--build-arg VERSION="$VERSION" \
--build-arg GIT_COMMIT="$GIT_COMMIT" \
--build-arg BUILD_TIME="$BUILD_TIME" \
-t "${IMAGE}:${VERSION}" \
-t "${IMAGE}:latest" \
.
- name: Push image
env:
IMAGE: ${{ steps.reg.outputs.image }}
VERSION: ${{ steps.ver.outputs.version }}
run: |
set -euo pipefail
docker push "${IMAGE}:${VERSION}"
docker push "${IMAGE}:latest"
echo "Published ${IMAGE}:${VERSION} and ${IMAGE}:latest"
# Pull the linux/amd64 binary back out of the freshly built image so the
# release carries a ready-to-run artifact, not just an image reference.
- name: Extract release binary
env:
IMAGE: ${{ steps.reg.outputs.image }}
VERSION: ${{ steps.ver.outputs.version }}
run: |
set -euo pipefail
mkdir -p dist
CID="$(docker create "${IMAGE}:${VERSION}")"
docker cp "$CID:/app/orchestrad" "dist/orchestrad"
docker rm "$CID" >/dev/null
tar -C dist -czf "dist/orchestrad-${VERSION}-linux-amd64.tar.gz" orchestrad
( cd dist && sha256sum "orchestrad-${VERSION}-linux-amd64.tar.gz" > "orchestrad-${VERSION}-linux-amd64.tar.gz.sha256" )
ls -la dist
# Create the Gitea release for this version and attach the binary. Uses
# the auto-injected token; no manual secret required.
- name: Create Gitea release
shell: bash
env:
API_URL: ${{ github.api_url }}
REPO: ${{ github.repository }}
TOKEN: ${{ github.token }}
VERSION: ${{ steps.ver.outputs.version }}
GIT_COMMIT: ${{ steps.ver.outputs.git_commit }}
GIT_COMMIT_SHORT: ${{ steps.ver.outputs.git_commit_short }}
IMAGE: ${{ steps.reg.outputs.image }}
run: |
set -euo pipefail
# Skip if a release for this tag already exists (e.g. a re-run).
code="$(curl -sS -o /dev/null -w '%{http_code}' \
-H "Authorization: token ${TOKEN}" \
"${API_URL}/repos/${REPO}/releases/tags/${VERSION}")"
if [ "$code" = "200" ]; then
echo "Release ${VERSION} already exists; skipping."
exit 0
fi
# Markdown release notes, JSON-encoded with jq so any characters are
# safely escaped.
body="$(printf '**OrchestrAD %s**\n\n| Field | Value |\n| --- | --- |\n| Version | `%s` |\n| Commit | [`%s`](%s/%s/commit/%s) |\n\n## Container image\n```\ndocker pull %s:%s\ndocker pull %s:latest\n```\n' \
"$VERSION" "$VERSION" "$GIT_COMMIT_SHORT" "$SERVER_URL" "$REPO" "$GIT_COMMIT" "$IMAGE" "$VERSION" "$IMAGE")"
payload="$(jq -n \
--arg tag "$VERSION" \
--arg sha "$GIT_COMMIT" \
--arg name "OrchestrAD $VERSION" \
--arg body "$body" \
'{tag_name:$tag, target_commitish:$sha, name:$name, body:$body, draft:false, prerelease:false}')"
rel="$(curl -sS -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d "$payload" \
"${API_URL}/repos/${REPO}/releases")"
rel_id="$(printf '%s' "$rel" | jq -r '.id')"
if [ -z "$rel_id" ] || [ "$rel_id" = "null" ]; then
echo "Failed to create release:"; echo "$rel"; exit 1
fi
echo "Created release id=$rel_id"
for asset in dist/orchestrad-${VERSION}-linux-amd64.tar.gz dist/orchestrad-${VERSION}-linux-amd64.tar.gz.sha256; do
name="$(basename "$asset")"
echo "Uploading $name"
curl -sS -X POST \
-H "Authorization: token ${TOKEN}" \
-F "attachment=@${asset}" \
"${API_URL}/repos/${REPO}/releases/${rel_id}/assets?name=${name}" >/dev/null
done
echo "Release ${VERSION} published."