Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 1 addition & 39 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,42 +86,4 @@ jobs:
with:
name: compiled-templates
path: templates-parser/out

docker:
name: docker build & push
runs-on: ubuntu-latest
needs: [fmt, clippy, test, templates-build]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/mailify
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# docker image build + push lives in .github/workflows/docker.yml (tag-triggered)
98 changes: 98 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: docker

on:
push:
tags: ['v*']
workflow_dispatch:

jobs:
build:
name: build (${{ matrix.platform }})
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-24.04
arch: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/mailify
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/mailify,push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digest-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

manifest:
name: manifest & push
needs: build
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digest-*
merge-multiple: true
- uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/mailify
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ secrets.DOCKERHUB_USERNAME }}/mailify@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ secrets.DOCKERHUB_USERNAME }}/mailify:${{ steps.meta.outputs.version }}
200 changes: 200 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
name: release

on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
tag:
description: 'Tag to release (e.g. v0.2.0). Tag must already exist.'
required: true

env:
CARGO_TERM_COLOR: always
BIN_NAME: mailify

jobs:
# ─────────────────────────────────────────────────────────────
# 1. Create a draft GitHub Release up-front so matrix jobs upload into it
# ─────────────────────────────────────────────────────────────
create-release:
name: create draft release
runs-on: ubuntu-24.04
permissions:
contents: write
outputs:
tag: ${{ steps.resolve.outputs.tag }}
version: ${{ steps.resolve.outputs.version }}
steps:
- name: Resolve tag
id: resolve
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ inputs.tag }}"
else
TAG="${GITHUB_REF#refs/tags/}"
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
ref: ${{ steps.resolve.outputs.tag }}
- name: Create draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "${{ steps.resolve.outputs.tag }}" >/dev/null 2>&1; then
echo "Release already exists — skipping creation."
else
gh release create "${{ steps.resolve.outputs.tag }}" \
--draft \
--title "${{ steps.resolve.outputs.tag }}" \
--generate-notes
fi

# ─────────────────────────────────────────────────────────────
# 2. Build binaries on native runners, upload signed archive + SHA256
# ─────────────────────────────────────────────────────────────
build-binaries:
name: build ${{ matrix.target }}
needs: create-release
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
runner: ubuntu-24.04
archive: tar.gz
- target: x86_64-unknown-linux-musl
runner: ubuntu-24.04
archive: tar.gz
musl: true
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
archive: tar.gz
- target: x86_64-apple-darwin
runner: macos-13
archive: tar.gz
- target: aarch64-apple-darwin
runner: macos-14
archive: tar.gz
- target: x86_64-pc-windows-msvc
runner: windows-2022
archive: zip
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.create-release.outputs.tag }}

- name: Install Bun (templates build)
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3"

- name: Build templates
working-directory: templates-parser
run: |
bun install --frozen-lockfile
bun run build

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- name: Install musl tools
if: matrix.musl == true
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends musl-tools

- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}

- name: Build release binary
run: cargo build --release --bin ${{ env.BIN_NAME }} --target ${{ matrix.target }}

- name: Stage archive (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
STAGE="${BIN_NAME}-${{ needs.create-release.outputs.version }}-${{ matrix.target }}"
mkdir -p "dist/$STAGE"
cp "target/${{ matrix.target }}/release/${BIN_NAME}" "dist/$STAGE/"
cp -r templates-parser/out "dist/$STAGE/templates"
cp README.md LICENSE* "dist/$STAGE/" 2>/dev/null || true
cd dist
tar -czf "$STAGE.tar.gz" "$STAGE"
shasum -a 256 "$STAGE.tar.gz" > "$STAGE.tar.gz.sha256"
echo "ASSET=dist/$STAGE.tar.gz" >> "$GITHUB_ENV"
echo "ASSET_SHA=dist/$STAGE.tar.gz.sha256" >> "$GITHUB_ENV"

- name: Stage archive (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$stage = "$env:BIN_NAME-${{ needs.create-release.outputs.version }}-${{ matrix.target }}"
New-Item -ItemType Directory -Force -Path "dist/$stage" | Out-Null
Copy-Item "target/${{ matrix.target }}/release/$env:BIN_NAME.exe" "dist/$stage/"
Copy-Item -Recurse "templates-parser/out" "dist/$stage/templates"
Copy-Item README.md,LICENSE* "dist/$stage/" -ErrorAction SilentlyContinue
Push-Location dist
Compress-Archive -Path $stage -DestinationPath "$stage.zip"
(Get-FileHash "$stage.zip" -Algorithm SHA256).Hash.ToLower() + " $stage.zip" | Out-File "$stage.zip.sha256" -Encoding ascii
Pop-Location
"ASSET=dist/$stage.zip" | Out-File -FilePath $env:GITHUB_ENV -Append
"ASSET_SHA=dist/$stage.zip.sha256" | Out-File -FilePath $env:GITHUB_ENV -Append

- name: Upload to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ needs.create-release.outputs.tag }}" "${{ env.ASSET }}" "${{ env.ASSET_SHA }}" --clobber

# ─────────────────────────────────────────────────────────────
# 3. Publish crates to crates.io
# Disabled by default — enable once libs are deemed API-stable and
# CARGO_REGISTRY_TOKEN is set in repo secrets.
# ─────────────────────────────────────────────────────────────
publish-crates:
name: cargo publish
needs: build-binaries
runs-on: ubuntu-24.04
if: false # flip to: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.create-release.outputs.tag }}
- uses: dtolnay/rust-toolchain@stable
- name: Publish in dep order
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -e
for crate in mailify-core mailify-config mailify-templates mailify-smtp mailify-auth mailify-queue mailify-api; do
echo "::group::publish $crate"
cargo publish -p "$crate" --no-verify
# Wait for crates.io index propagation before next dependent publish
sleep 30
echo "::endgroup::"
done

# ─────────────────────────────────────────────────────────────
# 4. Finalize: flip draft → published
# ─────────────────────────────────────────────────────────────
finalize:
name: publish release
needs: [create-release, build-binaries]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.create-release.outputs.tag }}
- name: Flip draft to published
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release edit "${{ needs.create-release.outputs.tag }}" --draft=false
Loading
Loading