7c6bc94f6f
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>
226 lines
9.2 KiB
YAML
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."
|