name: Release
on:
push:
tags:
- "v*"
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
MACOSX_DEPLOYMENT_TARGET: "13.0"
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
jobs:
dmg:
name: macOS DMG (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
runner: macos-latest
- arch: x86_64
runner: macos-15-intel
permissions:
contents: read
env:
OPENLOGI_BUNDLE_ASSETS: ${{ vars.OPENLOGI_BUNDLE_ASSETS }}
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Verify runner architecture
run: test "$(uname -m)" = "${{ matrix.arch }}"
- name: Load release secrets from 1Password
id: load_secrets
uses: 1password/load-secrets-action@v4
with:
export-env: false
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
APPLE_SIGNING_IDENTITY: ${{ secrets.OP_APPLE_SECRET_ITEM }}/APPLE_SIGNING_IDENTITY
APPLE_CERTIFICATE: ${{ secrets.OP_APPLE_SECRET_ITEM }}/APPLE_CERTIFICATE
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.OP_APPLE_SECRET_ITEM }}/APPLE_CERTIFICATE_PASSWORD
APPLE_ID: ${{ secrets.OP_APPLE_SECRET_ITEM }}/APPLE_ID
APPLE_PASSWORD: ${{ secrets.OP_APPLE_SECRET_ITEM }}/APPLE_PASSWORD
APPLE_TEAM_ID: ${{ secrets.OP_APPLE_SECRET_ITEM }}/APPLE_TEAM_ID
OPENLOGI_UPDATE_BASE_URL: ${{ secrets.OP_R2_SECRET_ITEM }}/OPENLOGI_UPDATE_BASE_URL
OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY: ${{ secrets.OP_R2_SECRET_ITEM }}/OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY
- name: Install packaging tools
run: |
brew install librsvg
brew install create-dmg
- name: Validate release secrets
env:
APPLE_SIGNING_IDENTITY: ${{ steps.load_secrets.outputs.APPLE_SIGNING_IDENTITY }}
APPLE_CERTIFICATE: ${{ steps.load_secrets.outputs.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ steps.load_secrets.outputs.APPLE_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ steps.load_secrets.outputs.APPLE_ID }}
APPLE_PASSWORD: ${{ steps.load_secrets.outputs.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ steps.load_secrets.outputs.APPLE_TEAM_ID }}
OPENLOGI_UPDATE_BASE_URL: ${{ steps.load_secrets.outputs.OPENLOGI_UPDATE_BASE_URL }}
OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY: ${{ steps.load_secrets.outputs.OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY }}
run: |
set -euo pipefail
: "${APPLE_SIGNING_IDENTITY:?Configure APPLE_SIGNING_IDENTITY in 1Password}"
: "${APPLE_CERTIFICATE:?Configure APPLE_CERTIFICATE in 1Password}"
: "${APPLE_CERTIFICATE_PASSWORD:?Configure APPLE_CERTIFICATE_PASSWORD in 1Password}"
: "${APPLE_ID:?Configure APPLE_ID in 1Password}"
: "${APPLE_PASSWORD:?Configure APPLE_PASSWORD in 1Password}"
: "${APPLE_TEAM_ID:?Configure APPLE_TEAM_ID in 1Password}"
# : "${GITHUB_APP_ID:?Configure GITHUB_APP_ID in 1Password}"
# : "${GITHUB_APP_PRIVATE_KEY:?Configure GITHUB_APP_PRIVATE_KEY in 1Password}"
: "${OPENLOGI_UPDATE_BASE_URL:?Configure OPENLOGI_UPDATE_BASE_URL in 1Password}"
: "${OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY:?Configure OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY in 1Password}"
- name: Import Apple signing certificate
uses: apple-actions/import-codesign-certs@v7
with:
p12-file-base64: ${{ steps.load_secrets.outputs.APPLE_CERTIFICATE }}
p12-password: ${{ steps.load_secrets.outputs.APPLE_CERTIFICATE_PASSWORD }}
- name: Build OpenLogi.app with cargo-bundle
env:
OPENLOGI_UPDATE_BASE_URL: ${{ steps.load_secrets.outputs.OPENLOGI_UPDATE_BASE_URL }}
OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY: ${{ steps.load_secrets.outputs.OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY }}
run: |
set -euo pipefail
export OPENLOGI_UPDATE_MANIFEST_URL="${OPENLOGI_UPDATE_BASE_URL%/}/channels/stable/latest.json"
cargo run -p xtask -- bundle-macos
- name: Sign app bundle
env:
APPLE_SIGNING_IDENTITY: ${{ steps.load_secrets.outputs.APPLE_SIGNING_IDENTITY }}
run: |
set -euo pipefail
app="target/release/bundle/osx/OpenLogi.app"
codesign --force --deep --options runtime --timestamp \
--sign "$APPLE_SIGNING_IDENTITY" "$app"
codesign --verify --deep --strict --verbose=2 "$app"
- name: Create and sign DMG
env:
APPLE_SIGNING_IDENTITY: ${{ steps.load_secrets.outputs.APPLE_SIGNING_IDENTITY }}
run: |
set -euo pipefail
cargo run -p xtask -- dmg-macos --sign-identity "$APPLE_SIGNING_IDENTITY"
- name: Notarize DMG
env:
APPLE_ID: ${{ steps.load_secrets.outputs.APPLE_ID }}
APPLE_PASSWORD: ${{ steps.load_secrets.outputs.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ steps.load_secrets.outputs.APPLE_TEAM_ID }}
run: |
set -euo pipefail
xcrun notarytool submit target/release/OpenLogi.dmg \
--apple-id "$APPLE_ID" \
--password "$APPLE_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
- name: Staple DMG
run: |
set -euo pipefail
xcrun stapler staple target/release/OpenLogi.dmg
xcrun stapler validate target/release/OpenLogi.dmg
- name: Collect DMG artifact
run: |
mkdir -p dist
ref_name="${GITHUB_REF_NAME:-dev}"
ref_name="${ref_name//\//-}"
cp target/release/OpenLogi.dmg "dist/OpenLogi-${ref_name}-macos-${{ matrix.arch }}.dmg"
- uses: actions/upload-artifact@v7
with:
name: OpenLogi-macos-dmg-${{ matrix.arch }}
path: dist/*.dmg
publish:
name: Publish release
runs-on: ubuntu-latest
needs: dmg
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/download-artifact@v8
with:
pattern: OpenLogi-macos-dmg-*
path: dist
merge-multiple: true
- name: Generate checksums
run: |
cd dist
sha256sum *.dmg > SHA256SUMS
- name: Load static updater publishing config from 1Password
id: r2_config
uses: 1password/load-secrets-action@v4
with:
export-env: false
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
OPENLOGI_UPDATE_BASE_URL: ${{ secrets.OP_R2_SECRET_ITEM }}/OPENLOGI_UPDATE_BASE_URL
OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY: ${{ secrets.OP_R2_SECRET_ITEM }}/OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY
OPENLOGI_UPDATE_MINISIGN_SECRET_KEY: ${{ secrets.OP_R2_SECRET_ITEM }}/OPENLOGI_UPDATE_MINISIGN_SECRET_KEY
R2_ACCOUNT_ID: ${{ secrets.OP_R2_SECRET_ITEM }}/CLOUDFLARE_R2_ACCOUNT_ID
R2_BUCKET: ${{ secrets.OP_R2_SECRET_ITEM }}/CLOUDFLARE_R2_BUCKET
R2_ACCESS_KEY_ID: ${{ secrets.OP_R2_SECRET_ITEM }}/CLOUDFLARE_R2_ACCESS_KEY_ID
R2_SECRET_ACCESS_KEY: ${{ secrets.OP_R2_SECRET_ITEM }}/CLOUDFLARE_R2_SECRET_ACCESS_KEY
- name: Validate R2 publishing configuration
env:
OPENLOGI_UPDATE_BASE_URL: ${{ steps.r2_config.outputs.OPENLOGI_UPDATE_BASE_URL }}
OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY: ${{ steps.r2_config.outputs.OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY }}
OPENLOGI_UPDATE_MINISIGN_SECRET_KEY: ${{ steps.r2_config.outputs.OPENLOGI_UPDATE_MINISIGN_SECRET_KEY }}
R2_ACCOUNT_ID: ${{ steps.r2_config.outputs.R2_ACCOUNT_ID }}
R2_BUCKET: ${{ steps.r2_config.outputs.R2_BUCKET }}
R2_ACCESS_KEY_ID: ${{ steps.r2_config.outputs.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ steps.r2_config.outputs.R2_SECRET_ACCESS_KEY }}
run: |
set -euo pipefail
: "${OPENLOGI_UPDATE_BASE_URL:?Configure OPENLOGI_UPDATE_BASE_URL in the R2 1Password item}"
: "${OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY:?Configure OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY in the R2 1Password item}"
: "${OPENLOGI_UPDATE_MINISIGN_SECRET_KEY:?Configure OPENLOGI_UPDATE_MINISIGN_SECRET_KEY in the R2 1Password item}"
: "${R2_ACCOUNT_ID:?Configure CLOUDFLARE_R2_ACCOUNT_ID in the R2 1Password item}"
: "${R2_BUCKET:?Configure CLOUDFLARE_R2_BUCKET in the R2 1Password item}"
: "${R2_ACCESS_KEY_ID:?Configure CLOUDFLARE_R2_ACCESS_KEY_ID in the R2 1Password item}"
: "${R2_SECRET_ACCESS_KEY:?Configure CLOUDFLARE_R2_SECRET_ACCESS_KEY in the R2 1Password item}"
- name: Install minisign
run: |
sudo apt-get update
sudo apt-get install -y minisign
- name: Sign updater artifacts
env:
OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY: ${{ steps.r2_config.outputs.OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY }}
OPENLOGI_UPDATE_MINISIGN_SECRET_KEY: ${{ steps.r2_config.outputs.OPENLOGI_UPDATE_MINISIGN_SECRET_KEY }}
run: |
set -euo pipefail
key_file="${RUNNER_TEMP}/openlogi-minisign.key"
umask 077
trap 'rm -f "$key_file"' EXIT
# The secret key file is stored base64-encoded in 1Password so its two
# lines survive paste/round-trip (a raw multi-line value can have its
# newline mangled to a space — see the GitHub App key note above).
# `tr -d` drops any wrapping whitespace before decoding.
printf '%s' "$OPENLOGI_UPDATE_MINISIGN_SECRET_KEY" | tr -d '[:space:]' | base64 -d > "$key_file"
for artifact in dist/*.dmg; do
minisign -S -m "$artifact" -s "$key_file" -x "$artifact.minisig" -W
minisign -V -m "$artifact" -P "$OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY" -x "$artifact.minisig"
done
- name: Generate static updater manifest
env:
OPENLOGI_UPDATE_BASE_URL: ${{ steps.r2_config.outputs.OPENLOGI_UPDATE_BASE_URL }}
run: |
set -euo pipefail
cargo run -p xtask -- generate-updater-manifest \
--dist dist \
--tag "$GITHUB_REF_NAME" \
--base-url "$OPENLOGI_UPDATE_BASE_URL" \
--output dist/latest.json
- name: Upload static updater assets to R2
env:
AWS_ACCESS_KEY_ID: ${{ steps.r2_config.outputs.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ steps.r2_config.outputs.R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_REGION: auto
R2_ACCOUNT_ID: ${{ steps.r2_config.outputs.R2_ACCOUNT_ID }}
R2_BUCKET: ${{ steps.r2_config.outputs.R2_BUCKET }}
run: |
set -euo pipefail
endpoint="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
release_prefix="s3://${R2_BUCKET}/releases/${GITHUB_REF_NAME}"
channel_manifest="s3://${R2_BUCKET}/channels/stable/latest.json"
aws s3 cp dist/ "$release_prefix/" \
--recursive \
--exclude latest.json \
--cache-control "public, max-age=31536000, immutable" \
--endpoint-url "$endpoint"
aws s3 cp dist/latest.json "$channel_manifest" \
--content-type "application/json" \
--cache-control "no-cache" \
--endpoint-url "$endpoint"
- name: Publish GitHub Release with assets
uses: softprops/action-gh-release@v3
with:
files: |
dist/*.dmg
dist/*.minisig
dist/SHA256SUMS
generate_release_notes: true
fail_on_unmatched_files: true