From 9a722bc5ac05d6a5ac123aa00bd377e3a8fe8b95 Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 30 Oct 2026 08:48:14 -0300 Subject: [PATCH 1/8] ci: publish images to the Gitea registry and move workflows to .gitea Release now pushes to git.lucasrguerra.dev.br instead of ghcr.io, tags each architecture separately (no artifact upload, no gha cache) and joins them with imagetools. Workflows live in .gitea/workflows and use the gitea context. GITHUB_OUTPUT and GITHUB_STEP_SUMMARY stay: they are the runner's own interface. Co-Authored-By: Claude Sonnet 5.5 --- {.github => .gitea}/workflows/ci.yml | 0 {.github => .gitea}/workflows/release.yml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {.github => .gitea}/workflows/ci.yml (100%) rename {.github => .gitea}/workflows/release.yml (100%) diff --git a/.github/workflows/ci.yml b/.gitea/workflows/ci.yml similarity index 100% rename from .github/workflows/ci.yml rename to .gitea/workflows/ci.yml diff --git a/.github/workflows/release.yml b/.gitea/workflows/release.yml similarity index 100% rename from .github/workflows/release.yml rename to .gitea/workflows/release.yml From 59363734ff785f0ca7d0b8baaa46b5ade2140b6e Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 30 Oct 2026 08:48:44 -0300 Subject: [PATCH 2/8] ci: publish images to the Gitea registry, use the gitea context Release now pushes to git.lucasrguerra.dev.br instead of ghcr.io, tags each architecture separately (no artifact upload, no gha cache) and joins them with imagetools. GITHUB_OUTPUT and GITHUB_STEP_SUMMARY stay: they are the runner's own interface and the GITEA_* equivalents are not set. Co-Authored-By: Claude Sonnet 5.5 --- .env.example | 4 +- .env.production.example | 4 +- .gitea/workflows/ci.yml | 2 +- .gitea/workflows/release.yml | 76 ++++++++++++++---------------------- docker-compose.yaml | 12 +++--- docs/DEPLOYMENT.md | 4 +- 6 files changed, 42 insertions(+), 60 deletions(-) diff --git a/.env.example b/.env.example index ce9df51..e0cf2e1 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,10 @@ # --- Container images --- -# Published to GHCR by .github/workflows/release.yml, multi-architecture +# Published to the Gitea registry by .gitea/workflows/release.yml, multi-architecture # (linux/amd64 and linux/arm64). The deploy host pulls these instead of # compiling: `docker compose pull && docker compose up -d`. Running # `docker compose build` locally still overrides them with a local build. # Pin IMAGE_TAG to a released version (e.g. 1.2.3) or a sha- tag to roll back. -IMAGE_NAMESPACE=ghcr.io/lucasrguerra/lambdamail +IMAGE_NAMESPACE=git.lucasrguerra.dev.br/lucasrguerra/lambdamail IMAGE_TAG=latest # --- Postgres --- diff --git a/.env.production.example b/.env.production.example index 4157295..6d3f0e3 100644 --- a/.env.production.example +++ b/.env.production.example @@ -10,9 +10,9 @@ # and must be URL-safe - see the note above them. # --- Container images ------------------------------------------------------- -# Published by .github/workflows/release.yml for linux/amd64 and linux/arm64. +# Published by .gitea/workflows/release.yml for linux/amd64 and linux/arm64. # Pin IMAGE_TAG to a released version or a sha- tag to roll back. -IMAGE_NAMESPACE=ghcr.io/lucasrguerra/lambdamail +IMAGE_NAMESPACE=git.lucasrguerra.dev.br/lucasrguerra/lambdamail IMAGE_TAG=latest # --- Identity --------------------------------------------------------------- diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index a7a5438..3307d32 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -15,7 +15,7 @@ on: workflow_call: concurrency: - group: ci-${{ github.ref }} + group: ci-${{ gitea.ref }} cancel-in-progress: true jobs: diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 2f04576..3c2c272 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,8 +1,9 @@ name: Release -# Publishes the four service images to GHCR after a merge into main, and again -# under semver tags when a v* tag is pushed. The deploy host only pulls, so -# nothing has to be built on the server. +# Publishes the service images to the Gitea container registry after a merge +# into main, and again under semver tags when a v* tag is pushed. The deploy +# host only pulls, so nothing has to be built on the server. Needs the user +# secret REGISTRY_TOKEN (a Gitea token with the write:package scope). on: push: branches: [main] @@ -14,15 +15,15 @@ concurrency: cancel-in-progress: false env: - REGISTRY: ghcr.io + REGISTRY: git.lucasrguerra.dev.br jobs: # Nothing is published unless the full suite passes first. ci: - uses: ./.github/workflows/ci.yml + uses: ./.gitea/workflows/ci.yml - # GHCR rejects an uppercase path, and this repository is "lucasrguerra/LambdaMail". - # Lowercasing github.repository here keeps the workflow fork-safe instead of + # Registry paths must be lowercase, and this repository is "lucasrguerra/LambdaMail". + # Lowercasing gitea.repository here keeps the workflow fork-safe instead of # hardcoding the owner. prepare: name: Namespace das imagens @@ -31,7 +32,10 @@ jobs: namespace: ${{ steps.namespace.outputs.value }} steps: - id: namespace - run: echo "value=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" + # GITHUB_OUTPUT is the runner's own interface: Gitea sets it under that name. + run: | + repo='${{ gitea.repository }}' + echo "value=${repo,,}" >> "$GITHUB_OUTPUT" # Each architecture is built on a runner of its own kind. QEMU emulation # would also work, but it makes the Go and Next builds roughly an order of @@ -54,7 +58,7 @@ jobs: platform: [linux/amd64, linux/arm64] include: - platform: linux/amd64 - runner: ubuntu-latest + runner: linux-amd64 arch: amd64 - platform: linux/arm64 runner: ubuntu-24.04-arm @@ -64,12 +68,12 @@ jobs: - uses: docker/setup-buildx-action@v4 - - name: Autentica no GHCR + - name: Autentica no registro uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + username: ${{ gitea.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} - name: Metadados id: meta @@ -77,9 +81,10 @@ jobs: with: images: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }} - # Pushed by digest only: the tags are attached later, once both - # architectures exist and can be joined into a single manifest list. - - name: Build e push por digest + # Each architecture is pushed under its own tag (no digest artifacts: they + # are not portable to Gitea). The final tags are attached later, once both + # exist and can be joined into a single manifest list. + - name: Build e push por arquitetura id: build uses: docker/build-push-action@v7 with: @@ -87,26 +92,10 @@ jobs: file: services/${{ matrix.service }}/Dockerfile platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }},push-by-digest=true,name-canonical=true,push=true - cache-from: type=gha,scope=${{ matrix.service }}-${{ matrix.arch }} - cache-to: type=gha,mode=max,scope=${{ matrix.service }}-${{ matrix.arch }} - - - name: Exporta o digest - run: | - mkdir -p /tmp/digests - # The output already carries the `sha256:` prefix; the file name must - # not, since the merge step prepends it again. - digest='${{ steps.build.outputs.digest }}' - touch "/tmp/digests/${digest#sha256:}" - - - name: Guarda o digest - uses: actions/upload-artifact@v7 - with: - # Slashes are not allowed in artifact names, hence the arch alias. - name: digest-${{ matrix.service }}-${{ matrix.arch }} - path: /tmp/digests/* - retention-days: 1 - if-no-files-found: error + push: true + tags: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}:build-${{ gitea.sha }}-${{ matrix.arch }} + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}:buildcache-${{ matrix.arch }} + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}:buildcache-${{ matrix.arch }},mode=max publish: name: Publica ${{ matrix.service }} @@ -124,19 +113,12 @@ jobs: - uses: docker/setup-buildx-action@v4 - - name: Autentica no GHCR + - name: Autentica no registro uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Recupera os digests - uses: actions/download-artifact@v7 - with: - pattern: digest-${{ matrix.service }}-* - merge-multiple: true - path: /tmp/digests + username: ${{ gitea.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} # `latest` is what compose pulls. The semver tags only materialise on a # v* tag push and make it possible to pin or roll back to an exact @@ -156,11 +138,11 @@ jobs: # Joins both architectures under one manifest list, so `docker pull` # resolves to the right one automatically. - name: Cria o manifesto multi-arquitetura - working-directory: /tmp/digests run: | + image='${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}' docker buildx imagetools create \ $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}') \ - $(printf '${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}@sha256:%s ' *) + "$image:build-${{ gitea.sha }}-amd64" "$image:build-${{ gitea.sha }}-arm64" - name: Confere o resultado run: | diff --git a/docker-compose.yaml b/docker-compose.yaml index 2a0e2a8..a450802 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -60,7 +60,7 @@ services: # probe - a health check on a container that is meant to stop would report # unhealthy exactly when it has done its job. migrate: - image: ${IMAGE_NAMESPACE:-ghcr.io/lucasrguerra/lambdamail}/migrations:${IMAGE_TAG:-latest} + image: ${IMAGE_NAMESPACE:-git.lucasrguerra.dev.br/lucasrguerra/lambdamail}/migrations:${IMAGE_TAG:-latest} # "latest" is a moving tag and Docker considers a tag it already holds # locally to be satisfied, so recreating this container silently reused # the cached image and the newly published one was never fetched: the @@ -80,7 +80,7 @@ services: # ------------------------------------------------------------ RSPAMD rspamd: - image: ${IMAGE_NAMESPACE:-ghcr.io/lucasrguerra/lambdamail}/rspamd:${IMAGE_TAG:-latest} + image: ${IMAGE_NAMESPACE:-git.lucasrguerra.dev.br/lucasrguerra/lambdamail}/rspamd:${IMAGE_TAG:-latest} # "latest" is a moving tag and Docker considers a tag it already holds # locally to be satisfied, so recreating this container silently reused # the cached image and the newly published one was never fetched: the @@ -140,7 +140,7 @@ services: # -------------------------------------------------------------- AUTH auth: - image: ${IMAGE_NAMESPACE:-ghcr.io/lucasrguerra/lambdamail}/auth:${IMAGE_TAG:-latest} + image: ${IMAGE_NAMESPACE:-git.lucasrguerra.dev.br/lucasrguerra/lambdamail}/auth:${IMAGE_TAG:-latest} # "latest" is a moving tag and Docker considers a tag it already holds # locally to be satisfied, so recreating this container silently reused # the cached image and the newly published one was never fetched: the @@ -178,7 +178,7 @@ services: # ----------------------------------------------------------- STORAGE storage: - image: ${IMAGE_NAMESPACE:-ghcr.io/lucasrguerra/lambdamail}/storage:${IMAGE_TAG:-latest} + image: ${IMAGE_NAMESPACE:-git.lucasrguerra.dev.br/lucasrguerra/lambdamail}/storage:${IMAGE_TAG:-latest} # "latest" is a moving tag and Docker considers a tag it already holds # locally to be satisfied, so recreating this container silently reused # the cached image and the newly published one was never fetched: the @@ -214,7 +214,7 @@ services: # --------------------------------------------------------- PROTOCOLS protocols: - image: ${IMAGE_NAMESPACE:-ghcr.io/lucasrguerra/lambdamail}/protocols:${IMAGE_TAG:-latest} + image: ${IMAGE_NAMESPACE:-git.lucasrguerra.dev.br/lucasrguerra/lambdamail}/protocols:${IMAGE_TAG:-latest} # "latest" is a moving tag and Docker considers a tag it already holds # locally to be satisfied, so recreating this container silently reused # the cached image and the newly published one was never fetched: the @@ -297,7 +297,7 @@ services: # -------------------------------------------------------------- WEB web: - image: ${IMAGE_NAMESPACE:-ghcr.io/lucasrguerra/lambdamail}/webmail:${IMAGE_TAG:-latest} + image: ${IMAGE_NAMESPACE:-git.lucasrguerra.dev.br/lucasrguerra/lambdamail}/webmail:${IMAGE_TAG:-latest} # "latest" is a moving tag and Docker considers a tag it already holds # locally to be satisfied, so recreating this container silently reused # the cached image and the newly published one was never fetched: the diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index c43357f..7b4d411 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -176,7 +176,7 @@ have propagated and the IP has some sending history. ## Upgrading -Images are published to GHCR for `linux/amd64` and `linux/arm64` on every merge +Images are published to the Gitea container registry for `linux/amd64` and `linux/arm64` on every merge to `main`. ```bash @@ -198,7 +198,7 @@ every release, which is what forces the pull. IMAGE_TAG=sha- The tags are published by `release.yml`; `docker buildx imagetools inspect -ghcr.io//lambdamail/webmail:latest` shows what `latest` currently points +git.lucasrguerra.dev.br//lambdamail/webmail:latest` shows what `latest` currently points at. **Pinning has a second edge: bump it on every release.** A pinned tag is an From 91c5df433eaf8b2366e1e8f13a6b32a81a53adfa Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 30 Oct 2026 08:56:34 -0300 Subject: [PATCH 3/8] ci: build linux/amd64 under QEMU on the arm64 runner Co-Authored-By: Claude Sonnet 5.5 --- .gitea/workflows/release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 3c2c272..995e1cd 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -66,6 +66,11 @@ jobs: steps: - uses: actions/checkout@v7 + # The only runner is arm64; linux/amd64 is built under QEMU emulation + # (label linux-amd64 points at the same runner), so it is slower. + - uses: docker/setup-qemu-action@v3 + if: matrix.arch == 'amd64' + - uses: docker/setup-buildx-action@v4 - name: Autentica no registro From 33fa05b019105c136b4cb074d35541117f3061d3 Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 30 Oct 2026 09:01:45 -0300 Subject: [PATCH 4/8] ci: run Trivy from its official image and drop the gha cache aquasecurity/trivy-action clones the Trivy repository from the workflow's own server, which on Gitea is not GitHub. The gha buildx cache has no Gitea counterpart. Co-Authored-By: Claude Sonnet 5.5 --- .gitea/workflows/ci.yml | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 3307d32..eb309b5 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -148,14 +148,15 @@ jobs: # A mail server holds credentials for every mailbox it serves, so the # secret scanner matters as much as the vulnerability one here: a DKIM # key or a Cloudflare token committed by accident is a full compromise. + # + # Trivy runs from its official image rather than aquasecurity/trivy-action: + # that action clones the Trivy repository from the workflow's own server, + # which on Gitea is not GitHub. - name: Repositório - uses: aquasecurity/trivy-action@v0.36.0 - with: - scan-type: fs - scanners: vuln,secret,misconfig - severity: HIGH,CRITICAL - exit-code: '1' - skip-dirs: services/auth/node_modules,services/storage/node_modules,services/webmail/node_modules,services/webmail/.next + run: | + docker run --rm --network host -v "$PWD:/src" -w /src aquasec/trivy:0.70.0 \ + fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 \ + --skip-dirs services/auth/node_modules,services/storage/node_modules,services/webmail/node_modules,services/webmail/.next . # MEDIUM is reported but does not fail the build. # @@ -168,13 +169,10 @@ jobs: # Reporting rather than gating, because failing on every MEDIUM makes the # scanner something people learn to skip. - name: Repositório (MEDIUM, informativo) - uses: aquasecurity/trivy-action@v0.36.0 - with: - scan-type: fs - scanners: vuln,secret - severity: MEDIUM - exit-code: '0' - skip-dirs: services/auth/node_modules,services/storage/node_modules,services/webmail/node_modules,services/webmail/.next + run: | + docker run --rm --network host -v "$PWD:/src" -w /src aquasec/trivy:0.70.0 \ + fs --scanners vuln,secret --severity MEDIUM --exit-code 0 \ + --skip-dirs services/auth/node_modules,services/storage/node_modules,services/webmail/node_modules,services/webmail/.next . image: name: Imagem (${{ matrix.service }}) @@ -198,8 +196,6 @@ jobs: push: false load: true tags: lambdamail-${{ matrix.service }}:ci - cache-from: type=gha,scope=${{ matrix.service }} - cache-to: type=gha,mode=max,scope=${{ matrix.service }} # Scanned only for the images whose contents we author. migrations and # rspamd add .sql and .conf files to an upstream image and nothing else, @@ -209,11 +205,9 @@ jobs: # that their base images have to be watched. - name: Trivy na imagem if: matrix.service != 'migrations' && matrix.service != 'rspamd' - uses: aquasecurity/trivy-action@v0.36.0 - with: - image-ref: lambdamail-${{ matrix.service }}:ci - severity: HIGH,CRITICAL - exit-code: '1' + run: | + docker run --rm --network host -e DOCKER_HOST=tcp://127.0.0.1:2375 aquasec/trivy:0.70.0 \ + image --severity HIGH,CRITICAL --exit-code 1 lambdamail-${{ matrix.service }}:ci compose-up: name: Stack completa From 248e857b85867aab4a0b977874b3b3bc7e993463 Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 30 Oct 2026 09:11:32 -0300 Subject: [PATCH 5/8] ci: rerun on the runner with a shared /workspace Co-Authored-By: Claude Sonnet 5.5 From c30d618b3ffe71fd6018fba30b9d68b1bcad032f Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 30 Oct 2026 09:22:42 -0300 Subject: [PATCH 6/8] ci: copy files into containers instead of bind-mounting, patch two advisories The job workspace is a volume the DinD daemon cannot see, so migrations and the Trivy filesystem scan now docker cp their inputs. Trivy's image gate flagged next 15.5.27 (unauthenticated RCE in image optimisation, fixed in 15.5.28) and go-jose 4.1.4 (DoS, fixed in 4.1.6); both are patch bumps. Co-Authored-By: Claude Sonnet 5.5 --- .gitea/workflows/ci.yml | 46 ++++++++++++----- services/protocols/go.mod | 2 +- services/protocols/go.sum | 4 +- services/webmail/package-lock.json | 82 +++++++++++++++--------------- services/webmail/package.json | 2 +- 5 files changed, 79 insertions(+), 57 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index eb309b5..6922d7d 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -57,12 +57,18 @@ jobs: cache-dependency-path: services/protocols/go.sum - name: Run migrations run: | - docker run --rm --network host \ - -v "$(pwd)/../../migrations:/migrations" \ - migrate/migrate:v4.17.1 \ + # The job's workspace is a volume of its own, invisible to the Docker + # daemon the job talks to, so `-v "$PWD:..."` would mount an empty + # directory. Files are copied into the container instead. + cid=$(docker create --network host migrate/migrate:v4.17.1 \ -path=/migrations \ -database "postgres://lambdamail_test:lambdamail_test@localhost:5432/lambdamail_test?sslmode=disable" \ - up + up) + docker cp "$(pwd)/../../migrations/." "$cid:/migrations" + docker start -a "$cid" + status=$(docker inspect -f '{{.State.ExitCode}}' "$cid") + docker rm "$cid" >/dev/null + exit "$status" - run: go vet ./... - run: go test ./... -v env: @@ -97,12 +103,18 @@ jobs: # the database, so a mock would prove nothing. - name: Run migrations run: | - docker run --rm --network host \ - -v "$(pwd)/../../migrations:/migrations" \ - migrate/migrate:v4.17.1 \ + # The job's workspace is a volume of its own, invisible to the Docker + # daemon the job talks to, so `-v "$PWD:..."` would mount an empty + # directory. Files are copied into the container instead. + cid=$(docker create --network host migrate/migrate:v4.17.1 \ -path=/migrations \ -database "postgres://lambdamail_test:lambdamail_test@localhost:5432/lambdamail_test?sslmode=disable" \ - up + up) + docker cp "$(pwd)/../../migrations/." "$cid:/migrations" + docker start -a "$cid" + status=$(docker inspect -f '{{.State.ExitCode}}' "$cid") + docker rm "$cid" >/dev/null + exit "$status" - run: npm install - run: npm run lint - run: npm test @@ -154,9 +166,14 @@ jobs: # which on Gitea is not GitHub. - name: Repositório run: | - docker run --rm --network host -v "$PWD:/src" -w /src aquasec/trivy:0.70.0 \ + cid=$(docker create --network host -w /src aquasec/trivy:0.70.0 \ fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 \ - --skip-dirs services/auth/node_modules,services/storage/node_modules,services/webmail/node_modules,services/webmail/.next . + --skip-dirs services/auth/node_modules,services/storage/node_modules,services/webmail/node_modules,services/webmail/.next .) + docker cp . "$cid:/src" + docker start -a "$cid" + status=$(docker inspect -f '{{.State.ExitCode}}' "$cid") + docker rm "$cid" >/dev/null + exit "$status" # MEDIUM is reported but does not fail the build. # @@ -170,9 +187,14 @@ jobs: # scanner something people learn to skip. - name: Repositório (MEDIUM, informativo) run: | - docker run --rm --network host -v "$PWD:/src" -w /src aquasec/trivy:0.70.0 \ + cid=$(docker create --network host -w /src aquasec/trivy:0.70.0 \ fs --scanners vuln,secret --severity MEDIUM --exit-code 0 \ - --skip-dirs services/auth/node_modules,services/storage/node_modules,services/webmail/node_modules,services/webmail/.next . + --skip-dirs services/auth/node_modules,services/storage/node_modules,services/webmail/node_modules,services/webmail/.next .) + docker cp . "$cid:/src" + docker start -a "$cid" + status=$(docker inspect -f '{{.State.ExitCode}}' "$cid") + docker rm "$cid" >/dev/null + exit "$status" image: name: Imagem (${{ matrix.service }}) diff --git a/services/protocols/go.mod b/services/protocols/go.mod index cce3375..8da4aad 100644 --- a/services/protocols/go.mod +++ b/services/protocols/go.mod @@ -21,7 +21,7 @@ require ( require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect - github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/go-jose/go-jose/v4 v4.1.6 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect diff --git a/services/protocols/go.sum b/services/protocols/go.sum index 20d04b2..0b30d33 100644 --- a/services/protocols/go.sum +++ b/services/protocols/go.sum @@ -23,8 +23,8 @@ github.com/fergusstrange/embedded-postgres v1.30.0 h1:ewv1e6bBlqOIYtgGgRcEnNDpfG github.com/fergusstrange/embedded-postgres v1.30.0/go.mod h1:w0YvnCgf19o6tskInrOOACtnqfVlOvluz3hlNLY7tRk= github.com/go-acme/lego/v4 v4.35.2 h1:uVQg+KC/yj9R2g7Q9W5wDqhvQvxV5SMu5eqFVoN5xZU= github.com/go-acme/lego/v4 v4.35.2/go.mod h1:pX2jN5n8OphMGY1IaMjYm5DAEzguBaKRt8AvJAgJXpc= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.6 h1:sNRkftxFJxwsILiQukqp5z83QBSmntVwmgRY7AqBDO4= +github.com/go-jose/go-jose/v4 v4.1.6/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= diff --git a/services/webmail/package-lock.json b/services/webmail/package-lock.json index 53f0856..472a32f 100644 --- a/services/webmail/package-lock.json +++ b/services/webmail/package-lock.json @@ -14,7 +14,7 @@ "framer-motion": "^11.18.2", "imagetracerjs": "1.2.6", "lucide-react": "^0.474.0", - "next": "15.5.27", + "next": "15.5.28", "qrcode-generator": "^2.0.4", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1334,15 +1334,15 @@ } }, "node_modules/@next/env": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.27.tgz", - "integrity": "sha512-WC5hvVqiuFOcRyz6gKPeJkiVvSMf3scNeAV49CdWtVqxmUlzh8UKR6YHt9yiNkh8LngtzqSlVz07/Ma3ZAzchA==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.28.tgz", + "integrity": "sha512-xxW5EBI8fMPuVTPBW7LL980IRbuDXlbkUAwp9DDhtb4Lnich3nJ13uYpvrX4VE74hWPf6AvHHAMmGCidcHjbfg==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.27.tgz", - "integrity": "sha512-fBA9QurmK4+uj4wNhmRTuO5zMVRaBHCvp6H93zTG/5i6Yi6TewbvbesXB8YQNj8T99Cwrow87hHiFV6wL/hyMA==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.28.tgz", + "integrity": "sha512-dPoQYXcfSIqx5o+TOXVXO7kONK5qbtNRDeiTfII0bT3puGIT6VF5e0QwmxNZfEZ5Zety5f3m9P/p4xqwfiLlPg==", "cpu": [ "arm64" ], @@ -1356,9 +1356,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.27.tgz", - "integrity": "sha512-FGZwJwry8qZNHxD/nfwrCwCHHrqx6epqItkkxbYcBQEQ2uw5S43bY83fQB5zbAsZE7TELMjoYkbhsQ4oKM7wwg==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.28.tgz", + "integrity": "sha512-RgCJ7buBivTH+6sddqaVm+/v8kwMHlFkobvdt3DfETutws5HrxnToMIWSN9X3MZ3aAi7BbKrGZZWUDQLEycXqw==", "cpu": [ "x64" ], @@ -1372,9 +1372,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.27.tgz", - "integrity": "sha512-GQulAaJYNF1RYuatlsw9/uzbKz4if2V7Ic9nVO+P0ljYPm4yBRTFIl6g5YkKTw40lMLbpy9edLoH44g3jSHMYA==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.28.tgz", + "integrity": "sha512-OHXQe13olE59aKe++zHavGcJPh5SLHXi6Anret3BBeq1+qhSuOJDNAli/EDDfxs6UTqA4Qddo4uyYry2slyjiQ==", "cpu": [ "arm64" ], @@ -1391,9 +1391,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.27.tgz", - "integrity": "sha512-URFFsUmu8Pi0/1qdnUI7tx7keZw1Mt5zHfrIX1RfDG9Nih8DYxK54WZsDJUX2ZTlii1jgotL7qcbwKnSAES6xQ==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.28.tgz", + "integrity": "sha512-nTB7bB6vfn4ezCJNYP43ukt2o8L3+WxKF/sTcTxepdXsJh3SM9RhlU7wrZYbH7L4HO5M87dqDTwm8587vAkCiw==", "cpu": [ "arm64" ], @@ -1410,9 +1410,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.27.tgz", - "integrity": "sha512-aJp74z7uvU5jQhyH7lyk+4qSRcTdKbKT9NQE3jiO9kWYkoHZjKi0zzHDbCoZ+v2sJDEiJ/z58R0UlWMGWNF3HA==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.28.tgz", + "integrity": "sha512-WXCfVI32rW3xCusoUX1RpZKtyChWU2Zwe27JUDmM3KrXwcDNc9vV4zEq0GKFAURnuQ/Ouo/XqPvB0yh445XZCg==", "cpu": [ "x64" ], @@ -1429,9 +1429,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.27.tgz", - "integrity": "sha512-ILl2bQ8MbpvyMpY8AXljb3BXs0MJSC8HXZ6QV5AWpVS7ZELqNPtrHvhUk+SNarvjvICXX8bCS287TCi+gUFVYg==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.28.tgz", + "integrity": "sha512-Miq+NDb/HWsXuxseYxMM6fuEOQXgTziarkKIRpVKCn8W69cEfhQaQr+90pO+u4q2CbJR2wzXmUuCFyZaJs/Nng==", "cpu": [ "x64" ], @@ -1448,9 +1448,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.27.tgz", - "integrity": "sha512-1MB9Rh5y8TOPN2x7naNTJNkCZQav9c70AI7EBYXxsEewwOffC4DjjwBwKf20NBvcA+u+5jDv7nVmHxunlP2d7Q==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.28.tgz", + "integrity": "sha512-5LR79lemgDlgxvG7MQJ+zHCkVcrUoQuO7CiIvq3I58vNW4goSOE2b+19Z6FujRWMouvni4gvudYFuFuNxolGWA==", "cpu": [ "arm64" ], @@ -1464,9 +1464,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.27.tgz", - "integrity": "sha512-XtYgpGOswgnyBWXzOEG6q9RhvnSw0CEZ0MbWqCsl5PISgt9EzvJvNW5mU5n1PsphX/lnamzxjgQXH2GUJL8nqg==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.28.tgz", + "integrity": "sha512-GNipvXUYROSTf4PXEyH95FrWImIB5zdaK6uEOg0RP1/X2drnvoMwiBkJ0VWdzX7w3iiBm3y5g2TxkKDGVTOQcg==", "cpu": [ "x64" ], @@ -3016,12 +3016,12 @@ } }, "node_modules/next": { - "version": "15.5.27", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.27.tgz", - "integrity": "sha512-F82CrlPZ8GRxBH9RIIoR3qU1t5RT3dv1K8moe8SjlDEquiFipvEI13R1Om90TaUOEwShkWCMlg8+gvTNzfMhvg==", + "version": "15.5.28", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.28.tgz", + "integrity": "sha512-JjhLBBY1PNnuwPtcZ7t8yxVeJVYvD2Jyp633J+gRCNbzSt7N0E+xqCf6uQiff3+3qmUkwcF/ksfSsK1xsZ76Tw==", "license": "MIT", "dependencies": { - "@next/env": "15.5.27", + "@next/env": "15.5.28", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -3034,15 +3034,15 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.27", - "@next/swc-darwin-x64": "15.5.27", - "@next/swc-linux-arm64-gnu": "15.5.27", - "@next/swc-linux-arm64-musl": "15.5.27", - "@next/swc-linux-x64-gnu": "15.5.27", - "@next/swc-linux-x64-musl": "15.5.27", - "@next/swc-win32-arm64-msvc": "15.5.27", - "@next/swc-win32-x64-msvc": "15.5.27", - "sharp": "^0.34.3 || ^0.35.4" + "@next/swc-darwin-arm64": "15.5.28", + "@next/swc-darwin-x64": "15.5.28", + "@next/swc-linux-arm64-gnu": "15.5.28", + "@next/swc-linux-arm64-musl": "15.5.28", + "@next/swc-linux-x64-gnu": "15.5.28", + "@next/swc-linux-x64-musl": "15.5.28", + "@next/swc-win32-arm64-msvc": "15.5.28", + "@next/swc-win32-x64-msvc": "15.5.28", + "sharp": "^0.34.3 || ^0.35.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", diff --git a/services/webmail/package.json b/services/webmail/package.json index ab4be0a..668f513 100644 --- a/services/webmail/package.json +++ b/services/webmail/package.json @@ -16,7 +16,7 @@ "framer-motion": "^11.18.2", "imagetracerjs": "1.2.6", "lucide-react": "^0.474.0", - "next": "15.5.27", + "next": "15.5.28", "qrcode-generator": "^2.0.4", "react": "^18.3.1", "react-dom": "^18.3.1", From a28a9bc9d4f0b2fdc836cebffaa5df252bf19999 Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 30 Oct 2026 09:33:43 -0300 Subject: [PATCH 7/8] ci: run Go tests as a non-root user initdb, used by the embedded Postgres in the e2e suites, refuses to run as root. Co-Authored-By: Claude Sonnet 5.5 --- .gitea/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 6922d7d..053d5b3 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -70,9 +70,17 @@ jobs: docker rm "$cid" >/dev/null exit "$status" - run: go vet ./... - - run: go test ./... -v + # The end-to-end suites start an embedded Postgres, and initdb refuses to + # run as root, which is what a job container is. Run the tests as a + # throwaway unprivileged user (GitHub's hosted runners already are one). + - name: Testes env: TEST_DATABASE_URL: postgres://lambdamail_test:lambdamail_test@localhost:5432/lambdamail_test?sslmode=disable + run: | + id tester >/dev/null 2>&1 || useradd -m -u 1001 tester + chown -R tester:tester "$GITHUB_WORKSPACE" + runuser -u tester -- env "HOME=/home/tester" "PATH=$PATH" \ + "TEST_DATABASE_URL=$TEST_DATABASE_URL" go test ./... -v ts-auth: name: TypeScript (auth) From 7baaab2fd7d78c17539992a9ee9a068a274ffafc Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 30 Oct 2026 09:47:16 -0300 Subject: [PATCH 8/8] test(auth): give real Argon2id hashing a 30s test budget The default 5s does not fit a chain of 11 hashes on a shared CI runner. The production cost parameters are untouched. Co-Authored-By: Claude Sonnet 5.5 --- services/auth/vitest.config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/auth/vitest.config.ts b/services/auth/vitest.config.ts index 2c76e30..2075bce 100644 --- a/services/auth/vitest.config.ts +++ b/services/auth/vitest.config.ts @@ -2,6 +2,10 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { + // Real Argon2id hashes cost 1-3s each on a shared CI runner, and several + // tests chain a dozen of them. The production cost parameters stay as they + // are; the default 5s budget is what does not fit. + testTimeout: 30_000, // session.ts refuses to load without a real signing secret, and ESM hoists // imports - so the value has to exist before any test file is evaluated, // which an assignment inside a test file cannot guarantee.