Skip to content

Refresh rolling release metadata and versioning #30

Refresh rolling release metadata and versioning

Refresh rolling release metadata and versioning #30

name: Publish Main Build
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
build-and-release-mac-downloads:
runs-on: macos-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Capture previous rolling release commit
run: |
set -euo pipefail
git fetch --force --tags origin
previous_sha="$(git rev-list -n 1 refs/tags/main-build 2>/dev/null || true)"
echo "PREVIOUS_MAIN_BUILD_SHA=$previous_sha" >> "$GITHUB_ENV"
- name: Validate release signing secrets
env:
MACOS_DEVELOPER_ID_CERT_P12_BASE64: ${{ secrets.MACOS_DEVELOPER_ID_CERT_P12_BASE64 }}
MACOS_DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.MACOS_DEVELOPER_ID_CERT_PASSWORD }}
MACOS_DEVELOPER_ID_IDENTITY: ${{ secrets.MACOS_DEVELOPER_ID_IDENTITY }}
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
APPLE_NOTARY_API_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_API_KEY_P8_BASE64 }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
run: |
set -euo pipefail
missing=0
for name in \
MACOS_DEVELOPER_ID_CERT_P12_BASE64 \
MACOS_DEVELOPER_ID_CERT_PASSWORD \
MACOS_DEVELOPER_ID_IDENTITY \
MACOS_KEYCHAIN_PASSWORD \
APPLE_NOTARY_API_KEY_P8_BASE64 \
APPLE_NOTARY_ISSUER_ID \
APPLE_NOTARY_KEY_ID
do
if [ -z "${!name:-}" ]; then
echo "Missing required secret: $name" >&2
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
echo "Refusing to publish unsigned or unnotarized release artifacts." >&2
exit 1
fi
- name: Extract release metadata
run: |
set -euo pipefail
version="$(sed -n 's/^DMG_VERSION=\"\([^\"]*\)\"$/\1/p' MacBackend/build_dmg.sh | head -n 1)"
if [ -z "$version" ]; then
echo "Could not determine DMG_VERSION from MacBackend/build_dmg.sh" >&2
exit 1
fi
{
echo "RELEASE_VERSION=$version"
echo "RELEASE_MIN_MACOS=12+"
} >> "$GITHUB_ENV"
- name: Generate rolling release summary
run: |
set -euo pipefail
collect_subjects() {
local range="$1"
if [ -n "$range" ]; then
git log --no-merges --pretty=format:'%s' "$range"
else
git log --no-merges --pretty=format:'%s' -n 12
fi
}
build_notes() {
local range="$1"
local -a notes=()
local seen=$'\n'
local subject=""
while IFS= read -r subject; do
[ -n "$subject" ] || continue
case "$seen" in
*$'\n'"$subject"$'\n'*) continue ;;
esac
notes+=("- $subject")
seen+="$subject"$'\n'
[ "${#notes[@]}" -ge 4 ] && break
done < <(collect_subjects "$range")
if [ "${#notes[@]}" -lt 3 ]; then
while IFS= read -r subject; do
[ -n "$subject" ] || continue
case "$seen" in
*$'\n'"$subject"$'\n'*) continue ;;
esac
notes+=("- $subject")
seen+="$subject"$'\n'
[ "${#notes[@]}" -ge 4 ] && break
done < <(collect_subjects "")
fi
if [ "${#notes[@]}" -eq 0 ]; then
notes=(
"- Refreshed the rolling main build"
"- Updated signed and notarized Mac downloads"
"- Synced the website with the newest release metadata"
)
fi
printf '%s\n' "${notes[@]}"
}
range=""
if [ -n "${PREVIOUS_MAIN_BUILD_SHA:-}" ] && git cat-file -e "${PREVIOUS_MAIN_BUILD_SHA}^{commit}" 2>/dev/null; then
range="${PREVIOUS_MAIN_BUILD_SHA}..${GITHUB_SHA}"
fi
{
echo "RELEASE_NOTES<<EOF"
build_notes "$range"
echo "EOF"
} >> "$GITHUB_ENV"
- name: Decode release signing assets
env:
MACOS_DEVELOPER_ID_CERT_P12_BASE64: ${{ secrets.MACOS_DEVELOPER_ID_CERT_P12_BASE64 }}
APPLE_NOTARY_API_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_API_KEY_P8_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
run: |
set -euo pipefail
CERT_PATH="$RUNNER_TEMP/developer-id-application.p12"
API_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"
decode_base64() {
if base64 --help 2>&1 | grep -q -- '--decode'; then
base64 --decode
else
base64 -D
fi
}
echo "$MACOS_DEVELOPER_ID_CERT_P12_BASE64" | decode_base64 > "$CERT_PATH"
echo "$APPLE_NOTARY_API_KEY_P8_BASE64" | decode_base64 > "$API_KEY_PATH"
{
echo "MACOS_CERT_PATH=$CERT_PATH"
echo "MACOS_NOTARY_API_KEY_PATH=$API_KEY_PATH"
} >> "$GITHUB_ENV"
- name: Build macOS app and DMG
working-directory: MacBackend
env:
SIGN_APP: "0"
NOTARIZE_DMG: "0"
REQUIRE_SIGNED_RELEASE: "0"
run: bash build_dmg.sh
- name: Install rcodesign
run: cargo install apple-codesign --locked
- name: Sign and notarize macOS app and DMG
working-directory: MacBackend
env:
MACOS_CERT_PATH: ${{ env.MACOS_CERT_PATH }}
MACOS_DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.MACOS_DEVELOPER_ID_CERT_PASSWORD }}
APPLE_NOTARY_API_KEY_PATH: ${{ env.MACOS_NOTARY_API_KEY_PATH }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
run: |
set -euo pipefail
APP_NAME="iCodex-Connect"
BUILD_DIR="build"
STAGING_APP="$BUILD_DIR/dmg-staging/${APP_NAME}.app"
STANDALONE_APP="$BUILD_DIR/${APP_NAME}.app"
API_JSON="$RUNNER_TEMP/appstoreconnect-api-key.json"
DMG_PATH="$(find "$BUILD_DIR" -maxdepth 1 -name "${APP_NAME}-*.dmg" | head -n 1)"
if [ ! -d "$STAGING_APP" ] || [ ! -d "$STANDALONE_APP" ] || [ -z "$DMG_PATH" ]; then
echo "Expected unsigned app bundle outputs were not created." >&2
exit 1
fi
rcodesign encode-app-store-connect-api-key \
--output-path "$API_JSON" \
"$APPLE_NOTARY_ISSUER_ID" \
"$APPLE_NOTARY_KEY_ID" \
"$APPLE_NOTARY_API_KEY_PATH"
rcodesign sign \
--p12-file "$MACOS_CERT_PATH" \
--p12-password "$MACOS_DEVELOPER_ID_CERT_PASSWORD" \
--for-notarization \
"$STAGING_APP"
rcodesign sign \
--p12-file "$MACOS_CERT_PATH" \
--p12-password "$MACOS_DEVELOPER_ID_CERT_PASSWORD" \
--for-notarization \
"$STANDALONE_APP"
rm -f "$DMG_PATH"
hdiutil create \
-volname "$APP_NAME" \
-srcfolder "$BUILD_DIR/dmg-staging" \
-ov \
-format UDZO \
-imagekey zlib-level=9 \
"$DMG_PATH"
rcodesign sign \
--p12-file "$MACOS_CERT_PATH" \
--p12-password "$MACOS_DEVELOPER_ID_CERT_PASSWORD" \
--for-notarization \
"$DMG_PATH"
rcodesign notary-submit \
--api-key-file "$API_JSON" \
--wait \
--staple \
"$DMG_PATH"
xcrun stapler validate "$DMG_PATH"
MOUNT_DIR="$RUNNER_TEMP/icodex-release-check"
rm -rf "$MOUNT_DIR"
mkdir -p "$MOUNT_DIR"
hdiutil attach "$DMG_PATH" -nobrowse -readonly -mountpoint "$MOUNT_DIR" >/dev/null
spctl --assess -vv "$MOUNT_DIR/${APP_NAME}.app"
hdiutil detach "$MOUNT_DIR" >/dev/null
- name: Prepare stable release assets
working-directory: MacBackend
run: |
set -euo pipefail
APP_NAME="iCodex-Connect"
BUILD_DIR="build"
RELEASE_DIR="release"
mkdir -p "$RELEASE_DIR"
DMG_PATH="$(find "$BUILD_DIR" -maxdepth 1 -name "${APP_NAME}-*.dmg" | head -n 1)"
if [ -z "$DMG_PATH" ]; then
echo "Could not find DMG output." >&2
exit 1
fi
if [ ! -d "$BUILD_DIR/${APP_NAME}.app" ]; then
echo "Could not find app bundle output." >&2
exit 1
fi
cp "$DMG_PATH" "$RELEASE_DIR/${APP_NAME}.dmg"
ditto -c -k --sequesterRsrc --keepParent \
"$BUILD_DIR/${APP_NAME}.app" \
"$RELEASE_DIR/${APP_NAME}.app.zip"
shasum -a 256 \
"$RELEASE_DIR/${APP_NAME}.dmg" \
"$RELEASE_DIR/${APP_NAME}.app.zip" \
> "$RELEASE_DIR/SHA256SUMS.txt"
- name: Move rolling release tag to this commit
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -f main-build "${GITHUB_SHA}"
git push origin refs/tags/main-build --force
- name: Recreate rolling release so timestamps stay fresh
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if gh release view main-build >/dev/null 2>&1; then
gh release delete main-build --yes
fi
- name: Publish rolling release
uses: ncipollo/release-action@v1
with:
tag: main-build
name: Main Build · v${{ env.RELEASE_VERSION }}
allowUpdates: true
removeArtifacts: true
replacesArtifacts: true
prerelease: false
makeLatest: true
artifacts: |
MacBackend/release/iCodex-Connect.dmg
MacBackend/release/iCodex-Connect.app.zip
MacBackend/release/SHA256SUMS.txt
body: |
What's new:
${{ env.RELEASE_NOTES }}
Version: `${{ env.RELEASE_VERSION }}`
Minimum macOS: `${{ env.RELEASE_MIN_MACOS }}`
Commit: `${{ github.sha }}`
This release is Developer ID signed and notarized before publication.
Stable asset URLs:
- `https://github.com/${{ github.repository }}/releases/download/main-build/iCodex-Connect.dmg`
- `https://github.com/${{ github.repository }}/releases/download/main-build/iCodex-Connect.app.zip`
- name: Clean up decoded signing assets
if: always()
run: |
set -euo pipefail
rm -f "${MACOS_CERT_PATH:-}" "${MACOS_NOTARY_API_KEY_PATH:-}" || true