Gitea stays where the project lives and push-mirrors every branch and tag to GitHub. With the Actions variable BUILD_ON set to 'github' on both forges, the GitHub copy does the building and reports back to Gitea as a commit status; unset, nothing changes and Gitea builds as before. .github/workflows/ci.yml replaces the GitHub-era files. Branch pushes run what Gitea's ci.yml checks (fork checks, dev build, test targets, the release profile on main). v* tags run what publish.yml does, with the same two guards: the image per architecture on native runners side by side, the multi-arch index and :latest, the Gitea Release if the tag has none, and the host-install binaries taken out of the image. A final job posts "github/ci (branch)" or "github/ci (tag)" to the commit on Gitea. On Gitea, the heavy jobs skip under BUILD_ON=github and a `github` job waits for that status and passes or fails with it, so pull requests and merges still look at a Gitea run. The weekly release, the upstream watch and the announcement stay on Gitea. Removed: cleanup.yml and publish.yml (GHCR), release.yml (a second weekly schedule), and dependabot.yml, whose pull request branches every mirror sync would delete.
145 lines
7.2 KiB
YAML
145 lines
7.2 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.
|
|
#
|
|
# 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.
|
|
#
|
|
# BUILD_ON: when the Actions variable BUILD_ON is 'github' (org or repo),
|
|
# fork-checks and build skip here and the `github` job below waits for the
|
|
# same work done by .github/workflows/ci.yml on the GitHub mirror, passing or
|
|
# failing with it -- so this run still carries the answer pull requests and
|
|
# merges look at. Unset, everything builds here as before. If GitHub is
|
|
# unavailable, unset BUILD_ON and nothing else has to change.
|
|
name: ci
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# What an upstream merge can bring in or leave behind without a conflict:
|
|
# the upstream name in a new string literal, and a changed upstream file
|
|
# without the AGPL 5(a) notice. Seconds, and needs no toolchain. The notice
|
|
# check diffs against the upstream snapshot branch, hence the full fetch.
|
|
fork-checks:
|
|
if: ${{ vars.BUILD_ON != 'github' }}
|
|
runs-on: light
|
|
container:
|
|
image: python:3.13-slim@sha256:8d9d0b8bcf6506481eae4907c18f5e3e7902e629f5f6d684f9e7c32e85e3ddf0 # 3.13-slim
|
|
steps:
|
|
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
|
|
with:
|
|
fetch-depth: 0
|
|
- run: python3 tools/fork/name-check.py
|
|
- if: always()
|
|
run: python3 tools/fork/notice-check.py
|
|
# Cargo can patch a dependency to a directory in this repository, and
|
|
# the image builds from a context .dockerignore prunes to almost
|
|
# nothing. CI never sees the difference; a release does.
|
|
- if: always()
|
|
run: python3 tools/fork/context-check.py
|
|
# The personal-data catalog must classify every object and field the
|
|
# schema has, and name nothing that is gone.
|
|
- if: always()
|
|
run: python3 tools/fork/privacy-check.py
|
|
# The admin reads each expression field's allowed values and variables
|
|
# from the schema; they're generated from the registry and must match it.
|
|
- if: always()
|
|
run: python3 tools/fork/expr-schema.py --check
|
|
- if: always()
|
|
run: python3 -m unittest discover -s tools/fork/tests
|
|
|
|
build:
|
|
if: ${{ vars.BUILD_ON != 'github' }}
|
|
# Either runner (host1 or host2): the build needs no docker socket.
|
|
runs-on: light
|
|
container:
|
|
image: rust:1-bookworm@sha256:93ce27a88655056a51dbdd8f5f2d7ddc071c7b0070fb288a37b5a285fc83971e # 1-bookworm
|
|
# A named volume per host that outlives the job: Cargo's registry/git
|
|
# cache and the target dir. Without it every run recompiled RocksDB and
|
|
# the rest of the dependency tree from scratch. Each runner allows this
|
|
# one volume in its valid_volumes; each host keeps its own copy.
|
|
volumes:
|
|
- inbuxa-server-cargo:/cache
|
|
env:
|
|
CARGO_HOME: /cache/cargo-home
|
|
CARGO_TARGET_DIR: /cache/target
|
|
# Dependencies are reused whole; incremental data for the workspace
|
|
# crates would only bloat a shared target dir.
|
|
CARGO_INCREMENTAL: "0"
|
|
steps:
|
|
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
|
|
# Cargo sizes its parallelism from the host's core count, not the job's
|
|
# CPU cap (2 on host2, 4 on host1); a C++ build of RocksDB at 8-way
|
|
# parallelism inside 6 GB gets OOM-killed. Match jobs to the cap.
|
|
- run: |
|
|
jobs=$(awk '$1 != "max" { printf "%d", $1 / $2 }' /sys/fs/cgroup/cpu.max 2>/dev/null)
|
|
echo "CARGO_BUILD_JOBS=${jobs:-$(nproc)}" >> "$GITHUB_ENV"
|
|
echo "cargo jobs: ${jobs:-$(nproc)}; cache: $(du -sh /cache 2>/dev/null | cut -f1)"
|
|
- run: apt-get update -qq && apt-get install -y -qq --no-install-recommends clang >/dev/null
|
|
- run: cargo build -p inbuxa --locked
|
|
# --no-run: the workflow compiled every test target without running them,
|
|
# which catches a test that no longer builds without paying for the suite.
|
|
- run: cargo test --workspace --locked --no-run
|
|
# The release profile, on main only. It is the profile the image is
|
|
# built with, and it fails in ways the dev profile does not: v2026.9.24
|
|
# was tagged on a commit whose CI was green and whose release build
|
|
# could not compile the scim crate at all. A few minutes per merge is
|
|
# cheaper than finding that out from a tag, which throws away a
|
|
# multi-architecture build and leaves a version half-cut.
|
|
#
|
|
# Pull requests stay on the dev profile, where the wait is worth less.
|
|
- if: github.event_name == 'push'
|
|
run: cargo build -p inbuxa --locked --release
|
|
# Keep the cache from growing without bound: past 60 GB the target dir
|
|
# is dropped and the next build starts cold. The download cache stays.
|
|
# Two builds (dev + test profiles) already fill ~22 GB, so the limit
|
|
# has to sit well above that or it would wipe a warm cache every run.
|
|
- if: always()
|
|
run: |
|
|
used=$(du -s --block-size=1G /cache/target 2>/dev/null | cut -f1)
|
|
echo "target dir: ${used:-0} GB"
|
|
if [ "${used:-0}" -gt 60 ]; then rm -rf /cache/target && echo "over 60 GB: target dir cleared"; fi
|
|
|
|
# BUILD_ON=github: the GitHub mirror builds this commit and posts the result
|
|
# back as the commit status "github/ci (branch)". This waits for that status
|
|
# and takes its answer. The mirror pushes on every commit, so a missing
|
|
# status means GitHub has not got the push or is not running: after the
|
|
# timeout this fails, which is the cue to unset BUILD_ON.
|
|
github:
|
|
if: ${{ vars.BUILD_ON == 'github' }}
|
|
runs-on: light
|
|
timeout-minutes: 150
|
|
container:
|
|
image: python:3.13-slim@sha256:8d9d0b8bcf6506481eae4907c18f5e3e7902e629f5f6d684f9e7c32e85e3ddf0 # 3.13-slim
|
|
steps:
|
|
- env:
|
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
SHA: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
CONTEXT: github/ci (branch)
|
|
run: |
|
|
python3 - <<'EOF'
|
|
import json, os, time, urllib.request
|
|
url = (f"{os.environ['CI_SERVER_INTERNAL']}/api/v1/repos/{os.environ['GITHUB_REPOSITORY']}"
|
|
f"/commits/{os.environ['SHA']}/statuses?limit=50")
|
|
req = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['TOKEN']}"})
|
|
ctx, last = os.environ["CONTEXT"], None
|
|
print(f"waiting for '{ctx}' on {os.environ['SHA']}", flush=True)
|
|
while True:
|
|
mine = [s for s in json.load(urllib.request.urlopen(req)) if s["context"] == ctx]
|
|
state = max(mine, key=lambda s: s["id"]) if mine else None
|
|
if state and state["status"] != last:
|
|
last = state["status"]; print(f"{ctx}: {last} {state.get('target_url', '')}", flush=True)
|
|
if last == "success": raise SystemExit(0)
|
|
if last in ("failure", "error"): raise SystemExit(1)
|
|
time.sleep(20)
|
|
EOF
|