From b70859e086acb1af370e1aa0f0f7e03500628c0b Mon Sep 17 00:00:00 2001 From: Sagent Date: Mon, 8 Jun 2026 21:45:25 +0000 Subject: [PATCH 1/9] feat: migrate to 4-job CI pipeline with SHA-pinned actions --- .gitea/workflows/docker-build.yaml | 84 ++++++++++++++++++++-------- tests/test.sh | 89 ++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 24 deletions(-) create mode 100644 tests/test.sh diff --git a/.gitea/workflows/docker-build.yaml b/.gitea/workflows/docker-build.yaml index 95951c9..c2e8bf7 100644 --- a/.gitea/workflows/docker-build.yaml +++ b/.gitea/workflows/docker-build.yaml @@ -9,38 +9,74 @@ on: - cron: '0 0 * * *' jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Hadolint + uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0 + with: + dockerfile: Dockerfile + build: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + - name: Build image + run: docker build -t ci-image:${{ github.sha }} . + + - name: Save image + run: docker save -o image.tar ci-image:${{ github.sha }} + + - name: Upload artifact + uses: https://github.com/ChristopherHX/gitea-upload-artifact@62ac910c5d3dfa85c7cb2df15afe2e342b2407c2 # main + with: + name: docker-image + path: image.tar + retention-days: 1 + + test: + runs-on: ubuntu-latest + needs: [lint, build] + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Download artifact + uses: https://github.com/ChristopherHX/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7 # main + with: + name: docker-image + + - name: Load image + run: docker load < image.tar + + - name: Run tests + run: bash tests/test.sh ci-image:${{ github.sha }} + + push: + runs-on: ubuntu-latest + needs: test + if: github.event_name != 'pull_request' + steps: + - name: Download artifact + uses: https://github.com/ChristopherHX/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7 # main + with: + name: docker-image + + - name: Load image + run: docker load < image.tar - name: Login to Docker Hub - if: github.event_name != 'pull_request' - uses: docker/login-action@v4 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Docker metadata - id: meta - uses: docker/metadata-action@v6 - with: - images: jcabillot/ttrss - tags: | - #type=ref,event=branch - #type=ref,event=pr - #type=sha - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} - - - name: Build and push - uses: docker/build-push-action@v7 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - pull: true + - name: Tag and push + run: | + docker tag ci-image:${{ github.sha }} jcabillot/ttrss:latest + docker push jcabillot/ttrss:latest diff --git a/tests/test.sh b/tests/test.sh new file mode 100644 index 0000000..411de5c --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,89 @@ +#!/bin/bash +set -euo pipefail + +IMAGE="$1" +CONTAINER_NAME="test-$(echo "$IMAGE" | tr ':/' '-')-$$" +DB_CONTAINER="ttrss-db-$$" +TMPDIR="$(mktemp -d)" +trap 'docker rm -f "$CONTAINER_NAME" 2>/dev/null; docker rm -f "$DB_CONTAINER" 2>/dev/null; rm -rf "$TMPDIR"' EXIT + +DOCKER_GW=$(docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}') +BASE_URL="http://${DOCKER_GW}:8080" +FAILED=0 +PASSED=0 + +assert_eq() { + local desc="$1" expected="$2" actual="$3" + if [ "$expected" = "$actual" ]; then + echo "PASS: $desc" + PASSED=$((PASSED + 1)) + else + echo "FAIL: $desc (expected '$expected', got '$actual')" + FAILED=$((FAILED + 1)) + fi +} + +assert_contains() { + local desc="$1" pattern="$2" file="$3" + if grep -qEi "$pattern" "$file"; then + echo "PASS: $desc" + PASSED=$((PASSED + 1)) + else + echo "FAIL: $desc" + FAILED=$((FAILED + 1)) + fi +} + +echo "Starting PostgreSQL..." +docker pull postgres:16-alpine > /dev/null 2>&1 +docker run -d --name "$DB_CONTAINER" \ + -e POSTGRES_DB=ttrss -e POSTGRES_USER=ttrss -e POSTGRES_PASSWORD=ttrss \ + postgres:16-alpine + +echo "Waiting for PostgreSQL to be ready..." +for i in $(seq 1 30); do + if docker exec "$DB_CONTAINER" pg_isready -U ttrss -d ttrss 2>/dev/null; then + echo "PostgreSQL ready (attempt $i)" + break + fi + sleep 2 +done + +echo "Starting TTRSS..." +docker run -d --name "$CONTAINER_NAME" --link "$DB_CONTAINER:db" -p 8080:8080 "$IMAGE" + +echo "Waiting for TTRSS to be ready..." +READY=false +for i in $(seq 1 60); do + if curl -sf -o /dev/null "$BASE_URL/"; then + echo "TTRSS ready (attempt $i)" + READY=true + break + fi + sleep 3 +done + +if [ "$READY" = false ]; then + echo "Container did not become ready within 3 minutes" + echo "=== Container logs ===" + docker logs "$CONTAINER_NAME" 2>&1 || true + exit 1 +fi + +# Test 1: HTTP status +STATUS=$(curl -sf -o "$TMPDIR/body" -w '%{http_code}' "$BASE_URL/") +echo "HTTP status: $STATUS" +assert_eq "HTTP 200" "200" "$STATUS" + +# Test 2: Content-Type contains text/html +curl -sf -o /dev/null -D "$TMPDIR/headers" "$BASE_URL/" +assert_contains "Content-Type text/html" "text/html" "$TMPDIR/headers" + +# Test 3: Body contains HTML +assert_contains "Body is HTML" " Date: Mon, 8 Jun 2026 21:47:04 +0000 Subject: [PATCH 2/9] fix: add hadolint ignores for DL3018 (apk pin) and DL3002 (s6 needs root) --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 96e191a..3d69f06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM alpine:3.23 LABEL description="A complete, self-hosted Tiny Tiny RSS (TTRSS) environment." \ maintainer="Julien Cabillot " +# hadolint ignore=DL3018 RUN set -xe && \ apk update && apk upgrade && \ apk add --no-cache --virtual=run-deps \ @@ -53,6 +54,7 @@ RUN git clone "https://git.tt-rss.org/fox/tt-rss.git/" "/var/www/ttrss" && \ git clone --depth=1 https://github.com/levito/tt-rss-feedly-theme.git /var/www/ttrss/themes.local/levito-feedly-git && \ git clone --depth=1 https://github.com/Gravemind/tt-rss-feedlish-theme.git /var/www/ttrss/themes.local/gravemind-feedly-git +# hadolint ignore=DL3002 USER root ENTRYPOINT ["/init"] -- 2.52.0 From a578c1bba199b39750f156bb30309f4cc420c696 Mon Sep 17 00:00:00 2001 From: Sagent Date: Mon, 8 Jun 2026 21:54:37 +0000 Subject: [PATCH 3/9] fix: readiness check without -f (TT-RSS may return 5xx during boot) --- tests/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.sh b/tests/test.sh index 411de5c..1db95da 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -55,7 +55,7 @@ docker run -d --name "$CONTAINER_NAME" --link "$DB_CONTAINER:db" -p 8080:8080 "$ echo "Waiting for TTRSS to be ready..." READY=false for i in $(seq 1 60); do - if curl -sf -o /dev/null "$BASE_URL/"; then + if curl -s -o /dev/null "$BASE_URL/"; then echo "TTRSS ready (attempt $i)" READY=true break -- 2.52.0 From 074d1ec402e0a36fc0a49a290d2891234348096b Mon Sep 17 00:00:00 2001 From: Sagent Date: Mon, 8 Jun 2026 21:57:54 +0000 Subject: [PATCH 4/9] fix: remove -f from assertion curls (TT-RSS returns 5xx during boot) --- tests/test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test.sh b/tests/test.sh index 1db95da..f264303 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -71,12 +71,12 @@ if [ "$READY" = false ]; then fi # Test 1: HTTP status -STATUS=$(curl -sf -o "$TMPDIR/body" -w '%{http_code}' "$BASE_URL/") +STATUS=$(curl -s -o "$TMPDIR/body" -w '%{http_code}' "$BASE_URL/") echo "HTTP status: $STATUS" assert_eq "HTTP 200" "200" "$STATUS" # Test 2: Content-Type contains text/html -curl -sf -o /dev/null -D "$TMPDIR/headers" "$BASE_URL/" +curl -s -o /dev/null -D "$TMPDIR/headers" "$BASE_URL/" assert_contains "Content-Type text/html" "text/html" "$TMPDIR/headers" # Test 3: Body contains HTML -- 2.52.0 From 15544e6e9a862aa2ea7a542bc966f66633c3aab0 Mon Sep 17 00:00:00 2001 From: Sagent Date: Mon, 8 Jun 2026 22:11:25 +0000 Subject: [PATCH 5/9] fix: run update.php --update-schema before assertions --- tests/test.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test.sh b/tests/test.sh index f264303..61134b8 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -70,6 +70,10 @@ if [ "$READY" = false ]; then exit 1 fi +echo "Updating TT-RSS database schema..." +docker exec "$CONTAINER_NAME" php /var/www/ttrss/update.php --update-schema 2>&1 || true +sleep 2 + # Test 1: HTTP status STATUS=$(curl -s -o "$TMPDIR/body" -w '%{http_code}' "$BASE_URL/") echo "HTTP status: $STATUS" -- 2.52.0 From 77a01fc5f6b6de4e13384503cf5ffcf963c062e4 Mon Sep 17 00:00:00 2001 From: Sagent Date: Mon, 8 Jun 2026 22:25:34 +0000 Subject: [PATCH 6/9] retrigger CI -- 2.52.0 From d8b7ffc44519e3f5a55a135db4b24d220442ccce Mon Sep 17 00:00:00 2001 From: Sagent Date: Mon, 8 Jun 2026 22:30:09 +0000 Subject: [PATCH 7/9] fix: run update.php as www-data (refuses root) --- tests/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.sh b/tests/test.sh index 61134b8..3c891b9 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -71,7 +71,7 @@ if [ "$READY" = false ]; then fi echo "Updating TT-RSS database schema..." -docker exec "$CONTAINER_NAME" php /var/www/ttrss/update.php --update-schema 2>&1 || true +docker exec -u www-data "$CONTAINER_NAME" php /var/www/ttrss/update.php --update-schema 2>&1 || true sleep 2 # Test 1: HTTP status -- 2.52.0 From 000c52d548cdad447f15fffe42563e0ce410de84 Mon Sep 17 00:00:00 2001 From: Sagent Date: Mon, 8 Jun 2026 22:33:10 +0000 Subject: [PATCH 8/9] fix: pipe yes into interactive update.php --update-schema --- tests/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.sh b/tests/test.sh index 3c891b9..d25544f 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -71,7 +71,7 @@ if [ "$READY" = false ]; then fi echo "Updating TT-RSS database schema..." -docker exec -u www-data "$CONTAINER_NAME" php /var/www/ttrss/update.php --update-schema 2>&1 || true +echo yes | docker exec -i -u www-data "$CONTAINER_NAME" php /var/www/ttrss/update.php --update-schema 2>&1 || true sleep 2 # Test 1: HTTP status -- 2.52.0 From afabde99c4ea4364c98f8e4dbb5901f076a98e7d Mon Sep 17 00:00:00 2001 From: Sagent Date: Mon, 8 Jun 2026 22:37:07 +0000 Subject: [PATCH 9/9] =?UTF-8?q?fix:=20ERE=20alternation=20pattern=20in=20H?= =?UTF-8?q?TML=20body=20check=20(BRE=20\|=20=E2=86=92=20ERE=20|)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.sh b/tests/test.sh index d25544f..bf2092b 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -84,7 +84,7 @@ curl -s -o /dev/null -D "$TMPDIR/headers" "$BASE_URL/" assert_contains "Content-Type text/html" "text/html" "$TMPDIR/headers" # Test 3: Body contains HTML -assert_contains "Body is HTML" "