#!/bin/bash # release.sh — build and publish a PhotoPhetch release to Gitea # # Usage: # ./release.sh 1.0.0 "First release with Android and iPhone support" # ./release.sh 1.1.0 # prompts for release notes # # Requires: # - curl (built into macOS) # - git # - ./gradlew (Gradle wrapper in this directory) # - GITEA_TOKEN environment variable, or it will prompt # Get a token: https://git.bolenpad.com/user/settings/applications set -euo pipefail # ── Config ────────────────────────────────────────────────────────────────── GITEA_URL="https://git.bolenpad.com" GITEA_USER="kbolen" GITEA_REPO="photophetch" # ── Arguments ─────────────────────────────────────────────────────────────── if [ $# -lt 1 ]; then echo "Usage: $0 [release-notes]" echo " Example: $0 1.0.0 'First stable release'" exit 1 fi VERSION="$1" TAG="v${VERSION}" # Release notes: from argument, or auto-generate from git log if [ $# -ge 2 ]; then NOTES="$2" else # Auto-generate from git log since last tag (or all commits if no tags yet) LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") if [ -n "$LAST_TAG" ]; then RAW_LOG=$(git log "${LAST_TAG}..HEAD" --oneline --no-merges) LOG_RANGE="since $LAST_TAG" else RAW_LOG=$(git log --oneline --no-merges | head -30) LOG_RANGE="all commits" fi echo "" echo "Auto-generated release notes ($LOG_RANGE):" echo "──────────────────────────────────────────" echo "$RAW_LOG" echo "──────────────────────────────────────────" echo "" echo "Accept these as release notes? [y=yes, e=edit, q=quit]" read -r CHOICE case "$CHOICE" in y|Y) NOTES="$RAW_LOG" ;; e|E) TMPFILE=$(mktemp /tmp/photophetch-release-notes.XXXXXX.md) echo "$RAW_LOG" > "$TMPFILE" ${EDITOR:-nano} "$TMPFILE" NOTES=$(cat "$TMPFILE") rm -f "$TMPFILE" ;; *) echo "Aborted." exit 1 ;; esac fi # Gitea token if [ -z "${GITEA_TOKEN:-}" ]; then echo "GITEA_TOKEN not set." read -rsp "Enter Gitea API token: " GITEA_TOKEN echo fi # ── Pre-flight checks ──────────────────────────────────────────────────────── echo "→ Checking for uncommitted changes..." if ! git diff --quiet || ! git diff --cached --quiet; then echo "ERROR: Uncommitted changes exist. Commit or stash them first." exit 1 fi echo "→ Checking we're on main..." BRANCH=$(git rev-parse --abbrev-ref HEAD) if [ "$BRANCH" != "main" ]; then echo "ERROR: Not on main branch (currently on '$BRANCH'). Switch to main first." exit 1 fi echo "→ Checking tag $TAG doesn't already exist..." if git tag | grep -q "^${TAG}$"; then echo "ERROR: Tag $TAG already exists." exit 1 fi # ── Update version in build.gradle.kts ────────────────────────────────────── echo "→ Updating version to $VERSION in build.gradle.kts..." sed -i.bak "s/packageVersion = \"[^\"]*\"/packageVersion = \"${VERSION}\"/" build.gradle.kts rm -f build.gradle.kts.bak # ── Commit version bump ────────────────────────────────────────────────────── echo "→ Committing version bump..." git add build.gradle.kts git commit -m "Bump version to $VERSION" # ── Build DMG ─────────────────────────────────────────────────────────────── echo "→ Building DMG (this takes a few minutes)..." ./gradlew packageDmg DMG_PATH=$(find build/compose/binaries/main/dmg -name "*.dmg" | head -1) if [ -z "$DMG_PATH" ]; then echo "ERROR: No DMG found in build/compose/binaries/main/dmg/" exit 1 fi DMG_NAME=$(basename "$DMG_PATH") echo "→ Built: $DMG_PATH" # ── Tag and push ───────────────────────────────────────────────────────────── echo "→ Creating tag $TAG..." git tag -a "$TAG" -m "Release $TAG" echo "→ Pushing commits and tag..." git push origin main git push origin "$TAG" # ── Create Gitea release ───────────────────────────────────────────────────── echo "→ Creating Gitea release $TAG..." RELEASE_RESPONSE=$(curl -s -X POST \ "${GITEA_URL}/api/v1/repos/${GITEA_USER}/${GITEA_REPO}/releases" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ \"tag_name\": \"${TAG}\", \"name\": \"PhotoPhetch ${TAG}\", \"body\": \"${NOTES}\", \"draft\": false, \"prerelease\": false }") RELEASE_ID=$(echo "$RELEASE_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])" 2>/dev/null) if [ -z "$RELEASE_ID" ]; then echo "ERROR: Failed to create release. Response:" echo "$RELEASE_RESPONSE" exit 1 fi echo "→ Created release ID $RELEASE_ID" # ── Upload DMG ─────────────────────────────────────────────────────────────── echo "→ Uploading $DMG_NAME..." UPLOAD_RESPONSE=$(curl -s -X POST \ "${GITEA_URL}/api/v1/repos/${GITEA_USER}/${GITEA_REPO}/releases/${RELEASE_ID}/assets?name=${DMG_NAME}" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/octet-stream" \ --data-binary "@${DMG_PATH}") DOWNLOAD_URL=$(echo "$UPLOAD_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('browser_download_url',''))" 2>/dev/null) if [ -z "$DOWNLOAD_URL" ]; then echo "WARNING: Upload may have failed. Response:" echo "$UPLOAD_RESPONSE" else echo "✓ Released: $DOWNLOAD_URL" fi echo "" echo "✓ Done! PhotoPhetch $TAG released." echo " Release page: ${GITEA_URL}/${GITEA_USER}/${GITEA_REPO}/releases/tag/${TAG}"