Files
Netherwarlord 22011ce6ac
tests / python (3.10) (push) Failing after 7s
tests / python (3.11) (push) Successful in 15s
tests / web (push) Successful in 40s
feat: implement Gitea CI workflows for release and testing, enhance build script for multi-platform support
2026-07-29 05:16:43 -04:00

5.7 KiB

Running CI on a self-hosted Gitea

The workflows in .gitea/workflows/ mirror the GitHub ones in .github/workflows/, with one structural difference: releases build on a single Linux runner rather than three.

That is not a compromise. packaging/build.py normalises everything that would otherwise vary by build host — tar member modes, uid/gid, and launcher line endings — so the Windows .zip produced on Linux is equivalent to one produced on Windows. Dropping the matrix also drops the artifact hand-off between jobs and the race of three jobs racing to create the same release.

Multi-OS runners would still be worth adding for testing. Building does not need them.


1. Enable Actions

In app.ini:

[actions]
ENABLED = true
DEFAULT_ACTIONS_URL = https://github.com

DEFAULT_ACTIONS_URL is where actions/checkout and friends are fetched from. Public repositories clone anonymously, so a locked or absent GitHub account does not affect this. Restart Gitea afterwards.

2. Register a runner

Get a registration token from Site Administration → Actions → Runners, then:

docker run -d --restart always --name gitea-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /srv/gitea-runner:/data \
  -e GITEA_INSTANCE_URL=https://git.example.lan \
  -e GITEA_RUNNER_REGISTRATION_TOKEN=<token> \
  -e GITEA_RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:act-22.04 \
  gitea/act_runner:latest

The label mapping is the part that matters. runs-on: ubuntu-latest in the workflow resolves through it to a container image. catthehacker/ubuntu images carry the tooling GitHub's hosted runners have; the bare ubuntu:22.04 image does not, and actions/setup-python will fail on it for want of basic utilities.

Mounting the Docker socket lets the runner start job containers. It also grants effective root on the host, which is the usual trade — acceptable on a machine that only serves this purpose, worth knowing about regardless.

3. Add the release token

Releases are published through Gitea's own API by packaging/publish_gitea.py, so the job needs a token:

  1. Settings → Applications → Generate New Token, scope write:repository.
  2. In the repository: Settings → Actions → Secrets → Add Secret, named GITEA_TOKEN.

GITEA_URL and GITEA_REPOSITORY come from github.server_url and github.repository, which Gitea populates for compatibility. Nothing else to configure.

4. If the runner cannot reach Gitea

Worth understanding before it bites, because the failure is confusing.

ROOT_URL is what Gitea advertises to the world, and job containers inherit it as github.server_url — so actions/checkout and publish_gitea.py both call the public address, even though the runner may be sitting on the same host as Gitea. When that address only resolves outside your network, jobs fail at checkout with a DNS or connection error while Gitea itself is plainly fine.

Two ways out.

Let it hairpin. If your router does NAT loopback, the request leaves and comes straight back to the reverse proxy, and nothing needs configuring. Try this first — it is the common case and costs nothing to test.

Pin the name to a local address. Map the public hostname to your reverse proxy's LAN address, on the runner and on the containers it spawns:

services:
  gitea-runner:
    extra_hosts:
      - "git.example.lan:192.0.2.10"      # the reverse proxy, not Gitea

and in act_runner's config.yaml, so job containers inherit it:

container:
  options: --add-host=git.example.lan:192.0.2.10

The trap is which host to point at. It must be whatever terminates the scheme in ROOT_URL. If ROOT_URL is https://… then the override has to reach something serving TLS on 443 — your reverse proxy. Pointing it straight at the Gitea host sends an HTTPS request to a port where Gitea is only speaking plain HTTP, and the connection is refused.

Also: do not put Gitea behind a forward-auth proxy (Authentik outposts, oauth2-proxy and friends). Runners authenticate with their own token and cannot satisfy an SSO interstitial, so registration and every job will fail. Use the identity provider as an OIDC source inside Gitea's own login instead.

5. Cut a release

Dry run first — workflow_dispatch builds all three archives and prints their sizes without creating anything:

Actions → release → Run workflow

Then tag:

git tag v2.0.0 && git push origin v2.0.0

That leaves a draft release with the three archives attached. Review, then publish from the web UI. Re-running the workflow on the same tag replaces the assets rather than duplicating them, so a half-finished publish is safe to retry.

To publish immediately instead of drafting, add --publish to the publish_gitea.py invocation in .gitea/workflows/release.yml.


Notes for a DL380p Gen8

Those E5-2600 v1/v2 chips are x86-64-v2 — they have AVX but not AVX2, which arrived with the v3 (Haswell) generation. Two consequences:

  • Pick the distribution with that in mind. Debian, Ubuntu and RHEL 9 target a baseline these CPUs meet. RHEL 10 and recent Fedora raised their floor to x86-64-v3 and will not run. Node 22 and Docker are fine either way.

  • Don't plan to run benchmarks on this box. Many prebuilt llama-cpp-python wheels assume AVX2, and the ones that don't will be slow on 2012 silicon regardless. It is a fine CI and git server; keep inference on the workstation. Nothing in tests_py/ needs llama-cpp-python, which is exactly why requirements-ci.txt omits it.

Two dual-socket Sandy/Ivy Bridge sockets have plenty of throughput for Gitea plus a runner — this is an I/O and RAM workload, not a vector-math one.