# 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).