Files
jcoffey-dev afb39fac20
ci / version (pull_request) Skipped
ci / node (pull_request) Successful in 1m15s
ci / publish (pull_request) Skipped
ci / docker-build (pull_request) Successful in 28s
ci: publish tagged releases as INBUXA's own image
A tag named inbuxa-v<version> (the tagged commit's own version from
scripts/version.mjs, '+' as '-') now builds a linux/amd64 + linux/arm64
image at <REGISTRY>/inbuxa/ihasmail-inbuxa, tagged with the version and
latest, links the package to the repository and creates the release.

The inbuxa- prefix keeps upstream ihasmail's v* tags, which this
repository carries on shared commits, from ever publishing under the
INBUXA name. The tag must name its commit's version and the commit must
be on main. No schedule yet: releases are cut by hand.
2026-09-22 09:56:09 -07:00

180 lines
8.7 KiB
YAML

# CI on the self-hosted Gitea, ported from .gitlab-ci.yml during the move off
# GitLab (2026-09-22). Gitea reads .gitea/workflows and ignores .github/ once
# this directory exists; .github/workflows stays as it was for GitHub.
#
# Releases are cut by pushing a tag named `inbuxa-v<version>`, where
# <version> is what scripts/version.mjs says for the tagged commit with the
# `+` turned into `-` (e.g. inbuxa-v2026.9.22-g1a2b3c4). The prefix matters:
# this repository carries upstream ihasmail's own `v...` tags, on commits it
# shares with upstream, and a publish keyed on `v*` would ship plain ihasmail
# under the INBUXA name the moment one arrived. Only `inbuxa-v` tags publish.
# A tag publishes only if it names its own commit's version and that commit is
# on main. There is no release schedule yet; tags are cut by hand.
# Every job runs in an image pinned by digest (tag in the trailing comment),
# and the only action used is coffey-labs/actions/checkout pinned by SHA. The
# instance resolves short `uses:` against itself, never GitHub, so nothing
# unreviewed can be pulled in. Read the comment for the version; the digest is
# what runs. Do not "simplify" one back to a bare tag.
#
# Jobs run on the runner's `ci-net` network and clone from Gitea's internal
# address, never through the Cloudflare-proxied public name, which caps
# request bodies at 100 MB.
name: ci
on:
push:
branches: [main]
tags: ['**']
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# -------------------------------------------------------------- test ------
node:
runs-on: light
container:
image: node:26-bookworm-slim@sha256:582460f614631b59b824ac6020533b9bf339c7fdf3a6d7db31abb6b4065f0212 # 26-bookworm-slim
env:
NPM_CONFIG_CACHE: ${{ github.workspace }}/.npm
steps:
# version.test.ts shells out to git to resolve a build version, and the
# slim image ships without it; the checkout action installs it when it
# is missing, so it is there for the tests too. Full history, because
# the version is computed from it.
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
with:
fetch-depth: 0
# config.test.ts chmods a directory to 0555 and expects the write to be
# refused. Root ignores the permission bits, so as root that assertion
# can never hold. The tests run as the image's unprivileged `node` user
# for that reason; -p keeps the environment.
#
# imageproxy.test.ts needs IPv6 as well, which is not set here but on the
# runner: jobs run on the `ci-net` docker network, created with --ipv6.
# Without a non-loopback IPv6 address on the container, getaddrinfo's
# AI_ADDRCONFIG drops ::1 from the results entirely, localhost resolves
# to IPv4 only, and the test's control case connects to a port nothing
# is listening on. That is a runner property, so it cannot be fixed from
# this file -- if these tests ever fail again with ECONNREFUSED on
# 127.0.0.1, check that the runner still puts jobs on an IPv6-enabled
# network.
- run: chown -R node:node "$GITHUB_WORKSPACE"
- run: su node -p -c "npm ci --ignore-scripts"
- run: su node -p -c "npm run typecheck"
- run: su node -p -c "npm test"
- run: su node -p -c "npm run build"
# ------------------------------------------------------------- build ------
# Proves the Dockerfile still builds on every change, without pushing. The
# equivalent of ci.yml's final `docker build -t ihasmail:ci .` step. The
# Dockerfile builds everything itself; `needs` only keeps the order.
docker-build:
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
needs: [node]
runs-on: docker
container:
image: docker:28-cli@sha256:625d9431a9f54c5a2bc90f24f0e1c3d55b1349fd857dd85035f98c2c9acbdd4d # 28-cli
volumes:
- /var/run/docker.sock:/var/run/docker.sock
steps:
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
- run: |
tag="ihasmail:ci-$(echo "$GITHUB_SHA" | cut -c1-8)"
docker build -t "$tag" .
docker image rm "$tag"
# ----------------------------------------------------------- release ------
# Only for `inbuxa-v` tags (see the top of this file). The tag has to name
# its own commit's version, so the image, the release and the About screen
# all agree, and the commit has to be on main, so a release never describes
# code that was not reviewed onto the default branch.
version:
if: ${{ startsWith(github.ref, 'refs/tags/inbuxa-v') }}
runs-on: light
container:
image: node:26-bookworm-slim@sha256:582460f614631b59b824ac6020533b9bf339c7fdf3a6d7db31abb6b4065f0212 # 26-bookworm-slim
outputs:
version: ${{ steps.v.outputs.VERSION }}
docker_tag: ${{ steps.v.outputs.DOCKER_TAG }}
steps:
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
with:
fetch-depth: 0
- id: v
shell: bash
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
V="$(node scripts/version.mjs)"
want="inbuxa-v${V/+/-}"
[ "$TAG" = "$want" ] || { echo "!! $TAG does not name this commit's version; expected $want"; exit 1; }
git merge-base --is-ancestor "$(git rev-parse "${TAG}^{commit}")" origin/main \
|| { echo "!! $TAG is not on main"; exit 1; }
echo "VERSION=$V" >> "$GITHUB_OUTPUT"
echo "DOCKER_TAG=${V/+/-}" >> "$GITHUB_OUTPUT"
echo "VERSION=$V DOCKER_TAG=${V/+/-}"
# Multi-arch image at <REGISTRY>/inbuxa/ihasmail-inbuxa, then the release.
# arm64 is built under QEMU on this amd64 host, which is slow but fine for
# a hand-cut release. PACKAGE_TOKEN (jcoffey-dev, write:package) logs in:
# the job's own token is refused by the container registry. The release is
# created last, so a release on the page always has its image behind it.
publish:
if: ${{ startsWith(github.ref, 'refs/tags/inbuxa-v') }}
needs: [node, version]
runs-on: docker
container:
image: docker:28-cli@sha256:625d9431a9f54c5a2bc90f24f0e1c3d55b1349fd857dd85035f98c2c9acbdd4d # 28-cli
volumes:
- /var/run/docker.sock:/var/run/docker.sock
env:
DOCKER_BUILDKIT: "1"
REGISTRY: ${{ vars.REGISTRY }}
IMAGE: ${{ vars.REGISTRY }}/${{ github.repository }}
VERSION: ${{ needs.version.outputs.version }}
DOCKER_TAG: ${{ needs.version.outputs.docker_tag }}
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
steps:
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
- run: |
test -n "$REGISTRY" && test -n "$VERSION" && test -n "$DOCKER_TAG"
test -n "$PACKAGE_TOKEN" || { echo "PACKAGE_TOKEN secret is not set on this repository" >&2; exit 1; }
echo "$PACKAGE_TOKEN" | docker login -u jcoffey-dev --password-stdin "$REGISTRY"
docker run --privileged --rm tonistiigi/binfmt --install arm64
docker buildx create --use --name gitea-builder --driver docker-container || docker buildx use gitea-builder
- run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg IHASMAIL_VERSION="$VERSION" \
--provenance=false --sbom=false \
--tag "$IMAGE:$DOCKER_TAG" \
--tag "$IMAGE:latest" \
--push .
docker buildx imagetools inspect "$IMAGE:$DOCKER_TAG"
# Show the package on the repository's Packages tab. Idempotent.
- run: |
apk add --no-cache -q curl
curl -fsS -o /dev/null -X POST -H "Authorization: token $PACKAGE_TOKEN" \
"$CI_SERVER_INTERNAL/api/v1/packages/${GITHUB_REPOSITORY%%/*}/container/${GITHUB_REPOSITORY#*/}/-/link/${GITHUB_REPOSITORY#*/}" \
|| echo "package already linked (or link refused); not fatal"
# The release, on the internal address. The job's own token may create
# releases; a tag it creates would not start a workflow, but this one
# already exists.
- env:
TAG: ${{ github.ref_name }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eu
body="INBUXA webmail $VERSION.\n\nImage: \`$IMAGE:$DOCKER_TAG\` (linux/amd64, linux/arm64), also tagged \`latest\`."
curl -fsS -o /dev/null -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
--data "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"$body\"}" \
"$CI_SERVER_INTERNAL/api/v1/repos/$GITHUB_REPOSITORY/releases"
echo "release $TAG created"
- if: always()
run: docker logout "$REGISTRY" || true