The Docker smoke-test ran the server container under the fixed name
familiar-smoke. On the self-hosted runner the job shares the host
Docker daemon, so a container left behind by a killed or re-run job
(or a concurrent dev push + PR) collides on that name and 'docker run'
fails with exit 125.
Derive the name from the commit SHA, remove any stale container of that
name before starting, and thread the name through the smoke-test and
cleanup steps.
The smoke test probed the server over the network, which fails on the
self-hosted runner: the job step runs in a container that shares the host
Docker socket but sits on a different network, so it can reach neither a
published host port (-p lands on the runner host) nor the server
container's bridge IP (different network → curl connect hangs, so the job
looked stuck rather than failing).
Probe from inside the server container with `docker exec` instead: it goes
through the Docker socket and runs in the server's own netns, where
127.0.0.1:8080 always works regardless of runner topology. The image ships
python + httpx, so no curl or extra image pull is needed. Drop the now
unnecessary -p publish.
New .gitea/workflows/docker.yml (alongside the untouched ci.yml wheel publish).
Runs lint + test, then builds the image and smoke-tests it — non-root uid,
/healthz 200, MCP 401 without a token and 200 with a valid bearer. On a merge to
main/dev it pushes to git.hiddenden.cafe/hiddenden/familiar (main -> :latest +
:main, dev -> :dev, plus :sha-<12>) via the existing REGISTRY_TOKEN, then does a
scoped cleanup. The two workflows are independent and don't interfere.
The repo already has a REGISTRY_TOKEN secret (Gitea access token with
package scope); the workflow referenced a non-existent PACKAGE_REGISTRY_TOKEN
and fell back to the auto GITHUB_TOKEN, which is not granted package-write,
so every upload was rejected. Point twine at REGISTRY_TOKEN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gitea's PyPI registry rejects twine's --skip-existing flag
(UnsupportedConfiguration), which failed every publish run. Remove it and
instead tolerate an "already exists" conflict in the shell, so dev (unique
.devN versions) always publishes and a main re-run of an existing release
is treated as success rather than a hard failure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Setting `permissions: packages: write` alone dropped every other scope to
none, so actions/checkout could not clone the private repo and the publish
job failed before building. Add contents:read, and use github.actor (more
reliably populated than gitea.actor) as the twine username.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lint (ruff) and test (pytest, fail under 80% coverage) on every PR and
push to main/dev. On a merge to main/dev, build the sdist+wheel and
publish to the Gitea PyPI registry — main as a clean release, dev as a
unique .devN pre-release. No Docker; runs on uv.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>