7.9 KiB
Gitea CI
This repository targets Gitea 1.23.8. The workflow is in
.gitea/workflows/ci.yaml; it runs on pushes, pull requests, and manual dispatch.
It adds continuous integration only: no application deployment, scheduled scraping,
production migrations, or access to the homelab application database.
What Runs
One Docker-backed job, selected by the runner label python-ci, performs:
- Checkout using a commit-pinned action, without persisting checkout credentials.
- Install uv 0.9.3 and Python 3.13, then
uv sync --locked --dev. - Run Ruff's correctness-focused checks over
srcandtests. - Run the full unittest suite, including integration tests against a fresh
PostgreSQL 17 service named
postgresand databasecar_tracker_test, matching the homelab's PostgreSQL major version. - Build the source distribution and wheel, install that wheel in an independent environment, and verify that its SQL migrations are packaged.
The service password ci-test-only is deliberately public and only for the
ephemeral test database. No database port is published on the runner host. Never
replace TEST_DATABASE_URL with a production connection string: the integration
tests truncate their test tables. No browser installation or live scraping occurs.
No raw data, browser profiles, password files, or laptop environment is mounted.
The job image is node:22-bookworm so JavaScript actions have Node, Git, and the
basic Linux tools they need; uv supplies Python. The image is not your application
deployment image. Both action references use explicit GitHub URLs and commit SHAs.
The setup-uv action receives an empty github-token, so the Gitea job token is not
sent to GitHub's release API. Caching and problem matchers are disabled for this
first Gitea setup. PostgreSQL and Node images track their declared version tags;
action commits, uv, and the Python dependency lockfile are explicitly pinned.
Gitea 1.23 ignores several GitHub workflow keys, including job-level
timeout-minutes, permissions, and concurrency; the workflow does not pretend
they provide protections. Runner configuration enforces capacity 1 and a 30-minute
job timeout; the test command also has a 10-minute timeout.
Enable Repository Actions
In Gitea, open the tonio/car-tracker repository's Settings and enable
Repository Actions. Actions are enabled globally by default since Gitea 1.21,
but disabled for repositories by default. If the repository option is unavailable,
check the instance's [actions] ENABLED setting and your permissions.
Pushing a workflow is not enough: a runner with the matching label must be online. Until then, runs can remain waiting. If Actions were disabled when the workflow was pushed, enable them and use the manual run button afterward.
Register A Runner
Use a dedicated Linux CI VM with Docker and Docker Compose, preferably separate
from the Gitea and PostgreSQL hosts. A laptop can host a temporary runner, but jobs
will stop being serviced when it sleeps or disconnects. The included configuration
uses gitea/act_runner:0.2.13, with the configuration format verified from that
release. Do not substitute the newer renamed runner without checking compatibility
with your Gitea version.
The runner daemon needs Docker socket access, which is effectively root access to the CI host. Do not put production database credentials, backups, or raw listing data on that host. Job containers do not receive the socket or arbitrary host bind mounts. These restrictions reduce exposure but do not make self-hosted execution safe for arbitrary hostile workflows. Register at repository scope, accept only trusted contributors/workflows, and review fork pull requests before allowing them to run. Runner and server updates should be planned independently of this initial setup; Gitea 1.23.8 is an older release.
On the CI host, check out this repository, then run from its root:
docker compose -f ops/ci-runner/compose.yaml pull
docker compose -f ops/ci-runner/compose.yaml run --rm runner \
--config /config.yaml register
Registration is interactive, so a registration token is not placed in shell
arguments or a committed environment file. Obtain a repository-scoped token
from tonio/car-tracker -> Settings -> Actions -> Runners. Enter:
| Prompt | Value |
|---|---|
| Instance URL | Your Gitea HTTP(S) web URL, including its port if needed |
| Registration token | The repository token, entered locally; do not send it in chat |
| Runner name | For example car-tracker-ci |
| Labels | python-ci:docker://node:22-bookworm |
The instance URL is not the Git SSH URL ending in port 2222. It must resolve
and be reachable from both the runner container and its job containers. Avoid
localhost, which would refer to a container. If using a VPN-only hostname,
verify DNS and routing inside Docker as well as on the CI host. HTTPS certificate
verification remains enabled; configure trust for private certificates rather
than disabling it.
Registration writes credentials to /data/.runner in the persistent Docker volume.
Treat that volume as a secret. No registration token is needed in Compose's
environment. Start the registered daemon:
docker compose -f ops/ci-runner/compose.yaml up -d
docker compose -f ops/ci-runner/compose.yaml logs --tail=100 runner
Confirm in Gitea that the runner is online and advertises python-ci, then
trigger the CI workflow. The runner needs outbound access to your Gitea web URL,
GitHub/action releases, PyPI/Python downloads, and the container registry. It does
not need your application PostgreSQL credentials or an HBA rule for that database.
Restrict CI-host network access to production services where practical.
Do not run docker compose down -v unless you intend to erase runner registration.
Normal container recreation preserves the registration volume.
First Run Checks
- A job stuck waiting usually means Actions are disabled, the runner is offline,
or its label does not match
python-ci. - Checkout failures often mean job containers cannot resolve/reach Gitea's web URL or trust its TLS certificate. Successful Git SSH access from the laptop does not prove HTTP(S) checkout works from a Docker container.
postgresmust resolve inside the job's Docker network. Leavecontainer.networkempty so act_runner creates the service network; do not use host networking.- The integration tests must execute, not be skipped.
TEST_DATABASE_URLis set in the workflow and points only to the disposable service. - Caching is deliberately absent, so no cache-server ports or credentials are needed.
The workflow was executed locally using act_runner 0.2.13 against an archive of
the staged source, with no laptop credentials or raw snapshots included. Linting,
all 82 tests (including PostgreSQL integration tests), the build, and installed-wheel
migration checks passed. Local execution substituted the source archive for remote
checkout; Gitea HTTP(S) checkout and server-triggered execution still need verification
after a runner is registered and the repository's Actions feature is enabled.
The suite was subsequently rerun against disposable PostgreSQL 17.11: all 82 tests
passed, including all database integration tests. CI now uses postgres:17 to match
the homelab's major version.
Local Checks
uv sync --locked --dev
uv run --locked ruff check src tests
uv run --locked python -m unittest discover -s tests -v
uv build --no-sources
Without TEST_DATABASE_URL, database integration tests are skipped locally. Use
the disposable PostgreSQL instructions to run them, never the
homelab application database. Keep .env, .pgpass, .runner, and data/ out of Git.
References: Gitea 1.23 quick start, runner setup, and compatibility differences.