ci: publish Gitea container image

This commit is contained in:
Lucas Winther committed 2026-09-28 03:05:46 +02:00
1 parent 354d8fbf99
commit f67e0c2ea0
4 files changed
+90 -5

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
name: Check and publish image
on:
pull_request:
push:
branches: [main]
tags: ["v*"]
permissions:
contents: read
jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- name: Install locked dependencies
run: uv sync --locked
- name: Test
run: uv run pytest
- name: Lint
run: uv run ruff check .
- name: Type check
run: uv run mypy src tests
- name: Build image
run: docker build --platform linux/amd64 -t recoll-modern:ci .
publish:
needs: checks
if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Resolve publish tags
id: tags
run: |
python3 scripts/image_tags.py "$GITHUB_REF" > image-tags.txt
if [ -s image-tags.txt ]; then
echo "valid=true" >> "$GITHUB_OUTPUT"
fi
- name: Log in to Gitea registry
if: ${{ steps.tags.outputs.valid == 'true' }}
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
printf '%s' "$REGISTRY_TOKEN" | docker login gitea.lucaswinther.info --username "$REGISTRY_USERNAME" --password-stdin
- name: Build and push
if: ${{ steps.tags.outputs.valid == 'true' }}
run: |
set -eu
docker build --platform linux/amd64 \
--label "org.opencontainers.image.source=https://gitea.lucaswinther.info/lucasw89/recoll-modern" \
--label "org.opencontainers.image.description=Self-hosted Recoll search API" \
--label "org.opencontainers.image.revision=$GITHUB_SHA" \
--label "org.opencontainers.image.version=$GITHUB_REF_NAME" \
-t recoll-modern:publish .
while IFS= read -r image; do
docker tag recoll-modern:publish "$image"
docker push "$image"
done < image-tags.txt
+7
View File
@@ -31,6 +31,7 @@ web UI and (b) an MCP server, running in Docker, with several independent collec
| `src/recoll_modern/indexing/map_export.py` | Writes a durable atomic map from canonical paths to opaque collection IDs |
| `helper/` | C reader run by Recoll's `metadatacmds` for each indexed file |
| `Dockerfile`, `compose.yaml`, `docker/` | Non-root Recoll runtime, read-only document mount, persistent state/index volumes and config templates |
| `.gitea/workflows/image.yaml`, `scripts/image_tags.py` | CI checks and Gitea registry publication; Git refs select published tags |
| `src/recoll_modern/engine/` | Shared Protocol, deterministic fake, and isolated Recoll Python/indexer adapter |
| `src/recoll_modern/domain/auth.py`, `storage/auth.py` | Argon2 password checks, seeded administrator, hashed server-side sessions and CSRF verification |
| `src/recoll_modern/api/` | HTTP auth, health, file browsing, collection/membership/rule/job resources, search, document retrieval, prefix/proxy handling and JSON errors |
@@ -131,6 +132,12 @@ absent; Recoll may generate additional support files there. Compose mounts docum
keeps the SQLite/config and Xapian index volumes separate. Initial health can be 503 until an indexing
job creates the first index.
Gitea Actions tests and builds on pull requests, main pushes and version tags. A dependent job publishes
`linux/amd64` images only for main and full `vX.Y.Z` tags. `main` follows the branch; `latest` follows
the newest pushed version tag. The job uses package-write repository Actions secrets. The local
Compose deployment builds from source; `compose.published.yaml` pulls the registry image with the same
runtime mounts.
The host Recoll contract tests skip when no container is running. Required-container mode executes the
adapter inside Compose, avoiding a host Recoll dependency; the generated-corpus test proves that a
read-only file can be indexed, searched in two collections, and retrieved as extracted text. The HTTP
+16
View File
@@ -3,6 +3,22 @@
Append-only, newest first. Never edit or delete an entry — supersede it with a new one that links back.
One entry per decision a future maintainer would otherwise have to reverse-engineer.
## 2026-09-28 — Publish one amd64 image through Gitea Actions
**Status:** accepted
**Context:** The Docker deployment exists, but operators must build it locally. [Gitea's documented job
token limitation](https://docs.gitea.com/usage/actions/comparison/) means the workflow needs separate
package credentials.
**Decision:** run checks and build on pull requests, main and version tags. After checks, publish
`gitea.lucaswinther.info/lucasw89/recoll-modern:main` for main and `:vX.Y.Z` plus `:latest` for a full
version tag. Use a package-write personal access token stored as repository Actions secrets. The first
published architecture is `linux/amd64`; the existing local build remains available.
**Alternatives:** tag every main commit as `latest` (makes releases ambiguous); use Buildx for multiple
architectures now (requires more runner setup); use `GITEA_TOKEN` (currently lacks package publishing).
**Consequences:** operators can pin a version, while `main` is a development image. Publishing requires
a Docker-capable Gitea runner and configured secrets; no external deploy occurs automatically.
---
## 2026-09-28 — Reject non-regular explicit sources before opening
**Status:** accepted
**Context:** Directory discovery filters non-regular entries, but an explicit file source could name
+5 -5
View File
@@ -62,11 +62,11 @@ spec: docs/specs/006-gitea-image-publishing.md
- Produces `:main` on main, and `:vX.Y.Z` plus `:latest` on a full release tag after all gates pass.
- Requires a Docker-capable Gitea runner with `ubuntu-latest` label and Gitea Actions secrets `REGISTRY_USERNAME` and `REGISTRY_TOKEN` for the publish job.
- [ ] **Step 1: Run a failing structure check** (`test -f .gitea/workflows/image.yaml`) and record that no workflow exists.
- [ ] **Step 2: Add** one workflow triggered by pull requests, main pushes and `v*` tag pushes. Use `actions/checkout`, `astral-sh/setup-uv`, `uv sync --locked`, `uv run pytest`, `uv run ruff check .`, `uv run mypy src tests`, and `docker build --platform linux/amd64`. Publishing is a separate job after checks, gated to main or validated release tags; log in through `docker login --password-stdin`, then tag and push each output of Task 1's script. Grant the built-in token read-only repository access; expose PAT secrets only in the publishing job. Add OCI labels for source, description, revision and ref/version.
- [ ] **Step 3: Validate** workflow syntax with `docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:1.7.12 .gitea/workflows/image.yaml`, inspect that no pull request path reaches login/push, then run project tests, lint, type checking and `docker build --platform linux/amd64 -t recoll-modern:ci .` locally. If the validator or Docker daemon is unavailable, record the exact unverified gate rather than claiming success.
- [ ] **Step 4: Document** the new delivery boundary in `docs/ARCHITECTURE.md` and the tag/PAT/platform choice in an append-only `docs/DECISIONS.md` entry.
- [ ] **Step 5: Commit** workflow, docs and checked plan boxes as `ci: publish Gitea container image`.
- [x] **Step 1: Run a failing structure check** (`test -f .gitea/workflows/image.yaml`) and record that no workflow exists.
- [x] **Step 2: Add** one workflow triggered by pull requests, main pushes and `v*` tag pushes. Use `actions/checkout`, `astral-sh/setup-uv`, `uv sync --locked`, `uv run pytest`, `uv run ruff check .`, `uv run mypy src tests`, and `docker build --platform linux/amd64`. Publishing is a separate job after checks, gated to main or validated release tags; log in through `docker login --password-stdin`, then tag and push each output of Task 1's script. Grant the built-in token read-only repository access; expose PAT secrets only in the publishing job. Add OCI labels for source, description, revision and ref/version.
- [x] **Step 3: Validate** workflow syntax with `docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:1.7.12 .gitea/workflows/image.yaml`, inspect that no pull request path reaches login/push, then run project tests, lint, type checking and `docker build --platform linux/amd64 -t recoll-modern:ci .` locally. If the validator or Docker daemon is unavailable, record the exact unverified gate rather than claiming success.
- [x] **Step 4: Document** the new delivery boundary in `docs/ARCHITECTURE.md` and the tag/PAT/platform choice in an append-only `docs/DECISIONS.md` entry.
- [x] **Step 5: Commit** workflow, docs and checked plan boxes as `ci: publish Gitea container image`.
### Task 3: Pull-based deployment and operator guide