cronicle-docker/.forgejo/workflows/build.yaml
Emil Lerch 9d4ce24631
All checks were successful
Build Cronicle image / build (push) Successful in 43s
fix CI
2026-08-11 17:50:34 -07:00

119 lines
4.8 KiB
YAML

name: Build Cronicle image
on:
workflow_dispatch:
# Nightly upstream check. 13:00 UTC / 6AM Pacific, deliberately offset from
# aws-zig's 12:30 nightly because the runner has capacity: 1.
schedule:
- cron: '0 13 * * *'
push:
branches:
- master
env:
# Hardcoded rather than derived from ${{ github.repository }}: the packaging
# repo is cronicle-docker, but the image it produces is plain Cronicle.
IMAGE: git.lerch.org/lobo/cronicle
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/catthehacker/ubuntu:act-22.04
steps:
- name: Check out repository code
uses: actions/checkout@v4
# Upstream publishes a GitHub Release per tag, so releases/latest is the
# authoritative "newest stable" pointer. Cronicle is marked
# "private": true in package.json, so npm is not an option -- the
# Dockerfile builds from the release tarball.
#
# NOTE: every `run:` block here is POSIX sh. The runner executes these
# with dash, which has no `set -o pipefail`. Rather than depend on shell
# selection, the fetch and the parse are separate statements: under
# `set -e`, an assignment from a failing command substitution aborts, so
# `curl -f` failing is caught without needing pipefail.
- name: Resolve latest upstream Cronicle version
id: upstream
run: |
set -eu
release_json="$(curl -fsSL https://api.github.com/repos/jhuckaby/Cronicle/releases/latest)"
version="$(printf '%s' "$release_json" | jq -r '.tag_name // empty' | sed 's/^v//')"
if [ -z "$version" ]; then
echo "Could not resolve upstream Cronicle version" >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Upstream latest release: v$version"
- name: Compute image tags
id: tags
run: |
set -eu
shortsha="$(git rev-parse --short HEAD)"
echo "shortsha=$shortsha" >> "$GITHUB_OUTPUT"
echo "immutable=${{ steps.upstream.outputs.version }}-$shortsha" >> "$GITHUB_OUTPUT"
- name: Login to Gitea
uses: docker/login-action@v3
with:
registry: git.lerch.org
username: ${{ github.actor }}
password: ${{ secrets.PACKAGE_PUSH }}
# Set up before the existence check below, which uses buildx.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# The immutable tag encodes BOTH the upstream version and the packaging
# commit, so this single check covers both reasons to rebuild: a new
# Cronicle release, or a change to this repo. Nightly runs that find
# nothing new become a no-op with no state file to maintain.
#
# `buildx imagetools inspect` rather than `docker manifest inspect`:
# Forgejo's registry serves OCI media types
# (application/vnd.oci.image.manifest.v1+json) and authenticates via the
# Bearer token flow. imagetools handles both natively, whereas
# `docker manifest inspect` fails outright on OCI manifests with older
# docker CLIs ("unsupported manifest media type and no default
# available"). imagetools reuses the credentials login-action just wrote.
- name: Skip if this exact image was already built
id: check
run: |
set -u
tag="${IMAGE}:${{ steps.tags.outputs.immutable }}"
if docker buildx imagetools inspect "$tag" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "$tag is already published; nothing to do."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "$tag not found; building."
fi
# Three tags: one immutable (what deployments should pin), two moving.
- name: Build and push
if: steps.check.outputs.exists == 'false'
uses: docker/build-push-action@v6
with:
context: .
push: true
build-args: |
CRONICLE_VERSION=${{ steps.upstream.outputs.version }}
tags: |
${{ env.IMAGE }}:${{ steps.tags.outputs.immutable }}
${{ env.IMAGE }}:${{ steps.upstream.outputs.version }}
${{ env.IMAGE }}:latest
# Only ping when a build was actually attempted -- a nightly "nothing to
# do" notification 365 times a year is noise. Uses != 'true' rather than
# == 'false' so that a failure *before* the check step still notifies.
- name: Notify
uses: https://git.lerch.org/lobo/action-notify-ntfy@v2
if: always() && steps.check.outputs.exists != 'true'
with:
host: ${{ secrets.NTFY_HOST }}
topic: ${{ secrets.NTFY_TOPIC }}
status: ${{ job.status }}
user: ${{ secrets.NTFY_USER }}
password: ${{ secrets.NTFY_PASSWORD }}