Author SHA1 Message Date
patrick 412a73cedf fix: tell the tests where the database actually is
ci / backend (push) Failing after 1m16s
ci / frontend (push) Successful in 1m58s
DOCKER_HOST only decides which daemon creates the container; the address the
test dials comes from POSTGRES_HOST, which defaults to the job container's own
loopback. Setting one without the other still times out.
2026-08-18 00:49:41 -04:00
patrick 5176dfcb77 fix: give the backend CI job a docker daemon it can address
ci / backend (push) Failing after 1m30s
ci / frontend (push) Successful in 1m58s
The runner has docker, so the database container started; the job just could
not reach it. pytest-databases takes the address from DOCKER_HOST, and unset
means 127.0.0.1 -- the job container's loopback, not the namespace the sibling
published on. A dind service makes it resolve to a host that answers.
2026-08-18 00:45:43 -04:00
patrick b85ba92380 fix: declare ruff as a dev dependency
ci / backend (push) Failing after 1m7s
ci / frontend (push) Successful in 1m59s
CI has no nix shell, and ruff was only ever a nix package, so both lint steps
died with "Failed to spawn: ruff". Pinned exactly: the formatter deciding what
is correct must be one version, or CI rejects what the dev shell produced.
2026-08-18 00:37:00 -04:00
5 changed files with 86 additions and 10 deletions
+24 -2
View File
@@ -19,6 +19,30 @@ jobs:
backend:
runs-on: ubuntu-latest
# pytest-databases starts PostgreSQL in a container and then connects to it, and those are
# two different addresses that have to be set separately:
#
# DOCKER_HOST which daemon to create the container on (_service.py get_docker_host)
# POSTGRES_HOST where the test then connects (docker/postgres.py, default
# 127.0.0.1 -- the job container's own loopback, where nothing listens,
# because the database is a sibling container on another namespace)
#
# Setting only the first leaves the tests dialling 127.0.0.1 and timing out with
# "Service 'pytest_databases_postgres' failed to come online".
#
# If the runner is ever given its own dind sidecar, drop this services block and keep the
# two env vars pointed at whatever host it exposes.
services:
docker:
image: docker:27-dind
options: --privileged
env:
DOCKER_TLS_CERTDIR: ''
env:
DOCKER_HOST: tcp://docker:2375
POSTGRES_HOST: docker
defaults:
run:
working-directory: backend
@@ -42,8 +66,6 @@ jobs:
- name: Lint
run: uv run ruff check src/ tests/
# pytest-databases starts a throwaway PostgreSQL container, so this needs a working
# Docker daemon on the runner — the same requirement the release workflow has.
- name: Tests
run: uv run pytest tests/ -q
+2 -1
View File
@@ -36,6 +36,7 @@ dev = [
"pytest>=8.4.2",
"pytest-asyncio>=1.2.0",
"pytest-databases[postgres]>=0.15.0",
"ruff==0.15.14",
]
[tool.ruff.lint.per-file-ignores]
@@ -48,4 +49,4 @@ asyncio_mode = "auto"
testpaths = ["tests"]
filterwarnings = [
"ignore::jwt.warnings.InsecureKeyLengthWarning",
]
]
+2 -1
View File
@@ -4,7 +4,8 @@ pkgs.mkShell {
buildInputs = with pkgs; [
# Python development environment for Chitai
python313Packages.greenlet
python313Packages.ruff
# ruff is a dev dependency in pyproject.toml, not a shell package: CI has no nix, so a
# nix-only formatter is one CI cannot run, and two copies could disagree on formatting.
uv
# postgres database
+27
View File
@@ -261,6 +261,7 @@ dev = [
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-databases", extra = ["postgres"] },
{ name = "ruff" },
]
[package.metadata]
@@ -286,6 +287,7 @@ dev = [
{ name = "pytest", specifier = ">=8.4.2" },
{ name = "pytest-asyncio", specifier = ">=1.2.0" },
{ name = "pytest-databases", extras = ["postgres"], specifier = ">=0.15.0" },
{ name = "ruff", specifier = "==0.15.14" },
]
[[package]]
@@ -1277,6 +1279,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/e5/d708d262b600a352abe01c2ae360d8ff75b0af819b78e9af293191d928e6/rich_click-1.9.7-py3-none-any.whl", hash = "sha256:2f99120fca78f536e07b114d3b60333bc4bb2a0969053b1250869bcdc1b5351b", size = 71491, upload-time = "2026-01-31T04:29:26.777Z" },
]
[[package]]
name = "ruff"
version = "0.15.14"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" },
{ url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" },
{ url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" },
{ url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" },
{ url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" },
{ url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" },
{ url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" },
{ url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" },
{ url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" },
{ url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" },
{ url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" },
{ url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" },
{ url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" },
{ url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" },
{ url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" },
{ url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" },
{ url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" },
]
[[package]]
name = "six"
version = "1.17.0"
+31 -6
View File
@@ -99,12 +99,37 @@ the workflow.
1. **A registered `act_runner`.** Gitea Actions is enabled instance-side but does nothing without a
runner. Register one against the repo or the instance with the label `ubuntu-latest`.
2. **The runner needs a Docker daemon.** This is the single most common failure for image-building
workflows on Gitea. `act_runner` in docker mode runs each job inside a container that has no
daemon of its own. Either run a `docker:dind` sidecar next to the runner and set
`DOCKER_HOST=tcp://docker:2376` (with TLS certs shared over a volume), or run the runner in host
mode with the socket mounted. The dind sidecar is the safer of the two — mounting the host socket
into job containers gives any workflow root on the runner host.
2. **The runner needs a Docker daemon, at an address the job can reach.** `act_runner` in docker
mode runs each job inside a container with no daemon of its own. Either run a `docker:dind`
sidecar next to the runner and set `DOCKER_HOST=tcp://docker:2376` (with TLS certs shared over a
volume), or run the runner in host mode. The dind sidecar is the safer of the two — mounting the
host socket into job containers gives any workflow root on the runner host.
**Reachability is a separate question from availability, and it bit us.** A containerised job
with a working daemon still fails `pytest tests/` with
`Service 'pytest_databases_postgres' failed to come online`: the database container starts
fine, but its published port lands on the daemon's network namespace while the test process
looks for it on the job container's loopback.
Two variables control two different things, and both must be set:
| Variable | Decides | Read by |
| --- | --- | --- |
| `DOCKER_HOST` | which daemon the container is created on | `_service.py` `get_docker_host()` |
| `POSTGRES_HOST` | the address the test then connects to | `docker/postgres.py` `postgres_host`, default `127.0.0.1` |
Setting only `DOCKER_HOST` is not enough — `DockerService.run()` takes `container_host` as a
plain argument defaulting to `127.0.0.1`, and the postgres fixture fills it from
`POSTGRES_HOST`. (There *is* a `DOCKER_HOST`-parsing helper in `pytest_databases`, but it is
`_get_docker_ip()` on the docker-compose class in `docker/__init__.py` and no part of this
path uses it. Do not be misled by it, as I was.)
Until the runner grows its own sidecar, `ci.yml`'s backend job carries a `docker:dind` service
of its own with `DOCKER_HOST: tcp://docker:2375`. That needs the runner to permit
`--privileged`. The same treatment is still owed to `release.yml` — its `quality` job runs the
same tests, and its `smoke` job talks to compose, where the published ports would move to the
dind host too, so `curl http://localhost:3000` becomes `curl http://docker:3000`. Configuring
the runner once (option 1) avoids all of that.
3. **Action resolution.** A bare `uses: docker/build-push-action@v6` does not mean github.com here.
Gitea resolves it against `[actions] DEFAULT_ACTIONS_URL`, which defaults to `https://gitea.com`.
That is fine as it stands — `actions/checkout@v4`, `docker/setup-buildx-action@v3`,