Files
LambdaMail/.gitea/workflows/ci.yml
T
Lucas RayanandClaude Sonnet 5.5 a28a9bc9d4
CI / Lint de idioma (R9) (push) Successful in 22s
CI / TypeScript (auth) (push) Failing after 3m33s
CI / TypeScript (storage) (push) Successful in 17s
CI / Go (protocols) (push) Successful in 5m6s
CI / Trivy (dependências, segredos e Dockerfiles) (push) Successful in 1m9s
CI / Build (webmail) (push) Successful in 2m6s
CI / Stack completa (push) Skipped
CI / Imagem (migrations) (push) Successful in 2m24s
CI / Imagem (auth) (push) Successful in 2m58s
CI / Imagem (rspamd) (push) Successful in 29s
CI / Imagem (protocols) (push) Successful in 1m47s
CI / Imagem (storage) (push) Successful in 1m19s
CI / Imagem (webmail) (push) Successful in 2m35s
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 <noreply@anthropic.com>
2026-10-30 09:33:43 -03:00

259 lines
10 KiB
YAML

name: CI
# Runs on every branch and pull request. Publishing lives in release.yml and
# only runs once this workflow has passed.
#
# main is excluded from the push trigger on purpose: release.yml already calls
# this workflow there. Running both would put two jobs in the same concurrency
# group, and one would cancel the other - which is why every merge used to
# leave a cancelled CI run behind.
on:
push:
branches-ignore: [main]
pull_request:
branches: [main]
workflow_call:
concurrency:
group: ci-${{ gitea.ref }}
cancel-in-progress: true
jobs:
lang-lint:
name: Lint de idioma (R9)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: node scripts/lang-lint.mjs
go-protocols:
name: Go (protocols)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: lambdamail_test
POSTGRES_USER: lambdamail_test
POSTGRES_PASSWORD: lambdamail_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
defaults:
run:
working-directory: services/protocols
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: services/protocols/go.mod
# defaults.run.working-directory applies to run steps, not to
# actions, so setup-go looks for go.sum at the repository root and
# silently skips its cache without this.
cache-dependency-path: services/protocols/go.sum
- name: Run migrations
run: |
# 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)
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 ./...
# 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)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: lambdamail_test
POSTGRES_USER: lambdamail_test
POSTGRES_PASSWORD: lambdamail_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
defaults:
run:
working-directory: services/auth
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with: { node-version: "20" }
# The auth suite asserts against real rows: password verification,
# lockout, TOTP replay and recovery-code consumption are all decided in
# the database, so a mock would prove nothing.
- name: Run migrations
run: |
# 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)
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
env:
TEST_DATABASE_URL: postgres://lambdamail_test:lambdamail_test@localhost:5432/lambdamail_test?sslmode=disable
LAMBDAMAIL_MASTER_KEY: ci-master-key-at-least-16-chars
ts-storage:
name: TypeScript (storage)
runs-on: ubuntu-latest
defaults:
run:
working-directory: services/storage
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with: { node-version: "20" }
- run: npm install
- run: npm run lint
- run: npm test
webmail-build:
name: Build (webmail)
runs-on: ubuntu-latest
defaults:
run:
working-directory: services/webmail
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with: { node-version: "20" }
- run: npm install
- run: npm run lint
- run: npm test
- run: npm run build
security:
name: Trivy (dependências, segredos e Dockerfiles)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# 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
run: |
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 .)
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.
#
# The gate above stops at HIGH, which is why a MEDIUM XSS advisory in
# DOMPurify - the library that sanitises every message body the webmail
# renders - sat unnoticed through green builds. Severity is the
# advisory's opinion, not this project's: a MEDIUM in the HTML sanitiser
# matters more here than a HIGH in a build-time tool.
#
# Reporting rather than gating, because failing on every MEDIUM makes the
# scanner something people learn to skip.
- name: Repositório (MEDIUM, informativo)
run: |
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 .)
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 }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
service: [protocols, auth, storage, webmail, migrations, rspamd]
steps:
- uses: actions/checkout@v7
- uses: docker/setup-buildx-action@v4
# Built but not pushed: this proves each Dockerfile still works on every
# branch, while publishing stays in release.yml.
- name: Build
uses: docker/build-push-action@v7
with:
context: ${{ (matrix.service == 'migrations' || matrix.service == 'rspamd') && '.' || format('services/{0}', matrix.service) }}
file: services/${{ matrix.service }}/Dockerfile
push: false
load: true
tags: lambdamail-${{ matrix.service }}:ci
# Scanned only for the images whose contents we author. migrations and
# rspamd add .sql and .conf files to an upstream image and nothing else,
# so every finding in them belongs to that upstream project and cannot be
# fixed here - gating on it would gate this pipeline on somebody else's
# release cadence. They are still built above, and OPERATIONS.md records
# that their base images have to be watched.
- name: Trivy na imagem
if: matrix.service != 'migrations' && matrix.service != 'rspamd'
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
runs-on: ubuntu-latest
needs: [go-protocols, ts-auth, ts-storage, webmail-build]
steps:
- uses: actions/checkout@v7
- run: cp .env.example .env
# The proxy network is declared external because Coolify owns it on the
# deploy host. Nothing creates it here, and without it compose refuses to
# start the stack at all.
- name: Cria a rede do proxy
run: docker network create "${COOLIFY_PROXY_NETWORK:-coolify}"
# The build overlay is explicit here: CI has to exercise the code in the
# branch, not the images already published from main.
- run: docker compose -f docker-compose.yaml -f docker-compose.build.yaml --env-file .env up --build --wait
- run: docker compose -f docker-compose.yaml -f docker-compose.build.yaml ps
- run: docker compose -f docker-compose.yaml -f docker-compose.build.yaml down -v