name: Release # 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] tags: ['v*'] workflow_dispatch: concurrency: group: release cancel-in-progress: false env: REGISTRY: git.lucasrguerra.dev.br jobs: # Nothing is published unless the full suite passes first. ci: uses: ./.gitea/workflows/ci.yml # 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 runs-on: ubuntu-latest outputs: namespace: ${{ steps.namespace.outputs.value }} steps: - id: namespace # 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 # magnitude slower. build: name: Build ${{ matrix.service }} (${{ matrix.platform }}) needs: [ci, prepare] runs-on: ${{ matrix.runner }} permissions: contents: read packages: write strategy: fail-fast: false matrix: # Both are axes, so this is a cross product: four services on two # architectures is eight jobs. Declaring platform only under `include` # would not multiply anything - include can add keys to existing # combinations, but it cannot create the combinations themselves. service: [protocols, auth, storage, webmail, migrations, rspamd] platform: [linux/amd64, linux/arm64] include: - platform: linux/amd64 runner: linux-amd64 arch: amd64 - platform: linux/arm64 runner: ubuntu-24.04-arm arch: arm64 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 uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} username: ${{ gitea.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Metadados id: meta uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }} # 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: context: ${{ (matrix.service == 'migrations' || matrix.service == 'rspamd') && '.' || format('services/{0}', matrix.service) }} file: services/${{ matrix.service }}/Dockerfile platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} 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 }} needs: [build, prepare] runs-on: ubuntu-latest permissions: contents: read packages: write strategy: fail-fast: false matrix: service: [protocols, auth, storage, webmail, migrations, rspamd] steps: - uses: actions/checkout@v7 - uses: docker/setup-buildx-action@v4 - name: Autentica no registro uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} 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 # release; the sha tag does the same for any main build. - name: Metadados id: meta uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }} tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=raw,value=latest type=sha,format=long # Joins both architectures under one manifest list, so `docker pull` # resolves to the right one automatically. - name: Cria o manifesto multi-arquitetura run: | image='${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}' docker buildx imagetools create \ $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}') \ "$image:build-${{ gitea.sha }}-amd64" "$image:build-${{ gitea.sha }}-arm64" - name: Confere o resultado run: | docker buildx imagetools inspect \ "${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}:latest" \ --format '{{json .Manifest}}' \ | jq -r '.manifests[] | select(.platform.os == "linux") | "\(.platform.os)/\(.platform.architecture)"' - name: Resumo run: | { echo "### ${{ matrix.service }} publicado (linux/amd64 e linux/arm64)" echo echo '```' echo "${{ steps.meta.outputs.tags }}" echo '```' } >> "$GITHUB_STEP_SUMMARY" # Cria a entrada em "Releases" do repositório. Só roda quando uma tag v* é # enviada, depois que todas as imagens foram publicadas. Usa o token do próprio # Actions, então não precisa de segredo extra. gitea-release: name: Cria a release no Gitea needs: [publish, prepare] if: startsWith(gitea.ref, 'refs/tags/v') runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Release env: TAG: ${{ gitea.ref_name }} TOKEN: ${{ gitea.token }} API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }} NAMESPACE: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }} run: | prev=$(git describe --tags --abbrev=0 "$TAG^" 2>/dev/null || true) range=${prev:+$prev..}$TAG { echo "## Imagens" echo echo '```' for s in protocols auth storage webmail migrations rspamd; do echo "$NAMESPACE/$s:${TAG#v}" done echo '```' echo echo "## Mudanças${prev:+ desde $prev}" echo git log --no-merges --format='- %s (%h)' "$range" } > notes.md pre=false; case "$TAG" in *-*) pre=true ;; esac jq -n --arg tag "$TAG" --arg name "LambdaMail ${TAG#v}" --rawfile body notes.md --argjson pre "$pre" \ '{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \ -H 'Content-Type: application/json' -d @release.json