Antoine Vandeplanque e57144c9b1
All checks were successful
CI / Lint, PostgreSQL tests, and package (push) Successful in 12s
CI / Build and publish tested image (push) Successful in 1m14s
run selected releases through restricted SSH instead of GitOps polling
2026-10-01 17:20:40 +02:00

155 lines
8.1 KiB
Markdown

# 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 tests and publishes images on master. Manual **Run app** dispatch runs a
selected image over restricted SSH; see [SSH_RUN.md](SSH_RUN.md) for setup.
No live scraping, production migration, or production DB access occurs in CI.
## What Runs
One Docker-backed job, selected by the runner label `python-ci`, performs:
1. Checkout using a commit-pinned action, without persisting checkout credentials.
2. Install uv 0.9.3 and Python 3.13, then `uv sync --locked --dev`.
3. Run Ruff's correctness-focused checks over `src` and `tests`.
4. Run the full unittest suite, including integration tests against a fresh
PostgreSQL 17 service named `postgres` and database `car_tracker_test`, matching
the homelab's PostgreSQL major version.
5. 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 45-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 now receive the socket for builds, but arbitrary
workflow host bind mounts are not allowlisted. This does 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:
```sh
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 `gitea-runner` (the name does not affect job routing) |
| 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 `ops/ci-runner/data/.runner` via the `./data` bind.
The deployed copy on the new VM is `~/containers/gitea-runner/data/.runner`.
Keep this directory private and outside Git. No registration token is needed in Compose's
environment. Start the registered daemon:
```sh
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 directory.
## 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.
- `postgres` must resolve inside the job's Docker network. Leave `container.network`
empty so act_runner creates the service network; do not use host networking.
- The integration tests must execute, not be skipped. `TEST_DATABASE_URL` is 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
```sh
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](POSTGRESQL.md#tests) to run them, never the
homelab application database. Keep `.env`, `.pgpass`, `.runner`, and `data/` out of Git.
References: [Gitea 1.23 quick start](https://docs.gitea.com/1.23/usage/actions/quickstart),
[runner setup](https://docs.gitea.com/1.23/usage/actions/act-runner), and
[compatibility differences](https://docs.gitea.com/1.23/usage/actions/comparison).