native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add local app notarization flow to fork release script

onevcat a66c4b20 29adde24

+199 -17
+29 -6
doc-onevcat/fork-sync-and-release.md
··· 59 59 60 60 ## Personal Release Strategy (Fork Release Page) 61 61 62 - For personal usage, the easiest path is: 62 + The release helper now supports automatic notarization for personal fork releases. 63 63 64 - 1) Build unsigned Debug app locally (`make build-app`). 65 - 2) Zip app bundle. 66 - 3) Create a tag. 67 - 4) Upload zip to your fork GitHub Release page. 64 + Default flow: 68 65 69 - This avoids Apple signing/notarization setup and keeps the workflow simple. 66 + 1) Build app locally (`make build-app`). 67 + 2) Sign app with your `Developer ID Application` identity. 68 + 3) Notarize via `notarytool` and staple ticket to app. 69 + 4) Zip app bundle. 70 + 5) Create tag and upload zip to your fork GitHub Release page. 71 + 72 + If you want the old behavior (no notarization), set `ENABLE_NOTARIZATION=0`. 70 73 71 74 ## Helper Scripts 72 75 ··· 75 78 - Default target repo: auto-detected from `origin` 76 79 - Override target repo: `GH_REPO=owner/repo` 77 80 - Release create fallback: if `gh release create` fails (for example token scope mismatch), script falls back to `gh api` and then uploads assets 81 + - Notarization: enabled by default (`ENABLE_NOTARIZATION=1`) 82 + - Default keychain profile name: `supacode-notary` (override with `APPLE_NOTARY_KEYCHAIN_PROFILE`) 78 83 79 84 ### Example 80 85 ··· 88 93 # Or specify tag explicitly 89 94 ./doc-onevcat/scripts/release-to-fork.sh onevcat-v2026.02.26-01 90 95 ``` 96 + 97 + ## Notarization Credentials 98 + 99 + The script first tries `xcrun notarytool submit --keychain-profile <profile>`. 100 + If profile is missing, it will create one using either: 101 + 102 + - App Store Connect API key: 103 + - `APPLE_NOTARIZATION_KEY_PATH` 104 + - `APPLE_NOTARIZATION_KEY_ID` 105 + - `APPLE_NOTARIZATION_ISSUER` 106 + - Or Apple ID credentials: 107 + - `APPLE_ID` 108 + - `APPLE_PASSWORD` (app-specific password) 109 + - `APPLE_TEAM_ID` (optional if inferable from signing identity) 110 + 111 + Signing identity: 112 + 113 + - `APPLE_SIGNING_IDENTITY` (optional). If omitted, script auto-detects the first available `Developer ID Application` identity from keychain. 91 114 92 115 ## Optional: Full Signed Release on Fork 93 116
+170 -11
doc-onevcat/scripts/release-to-fork.sh
··· 1 1 #!/usr/bin/env bash 2 2 set -euo pipefail 3 3 4 - if ! command -v gh >/dev/null 2>&1; then 5 - echo "error: gh CLI is required" 6 - exit 1 7 - fi 8 - 9 - if ! command -v jq >/dev/null 2>&1; then 10 - echo "error: jq is required" 11 - exit 1 12 - fi 13 - 14 4 origin_repo_from_remote() { 15 5 local remote_url 16 6 remote_url="$(git remote get-url origin 2>/dev/null || true)" ··· 31 21 return 1 32 22 } 33 23 24 + default_signing_identity() { 25 + security find-identity -v -p codesigning 2>/dev/null \ 26 + | awk -F'"' '/Developer ID Application/ {print $2; exit}' 27 + } 28 + 29 + team_id_from_identity() { 30 + local identity="$1" 31 + if [[ "$identity" =~ \(([A-Z0-9]{10})\)$ ]]; then 32 + echo "${BASH_REMATCH[1]}" 33 + fi 34 + } 35 + 36 + submit_with_keychain_profile() { 37 + local artifact_path="$1" 38 + local output 39 + 40 + set +e 41 + output="$(xcrun notarytool submit "$artifact_path" --keychain-profile "$KEYCHAIN_PROFILE" --wait 2>&1)" 42 + local status=$? 43 + set -e 44 + 45 + if [[ $status -eq 0 ]]; then 46 + echo "$output" 47 + return 0 48 + fi 49 + 50 + echo "$output" >&2 51 + if [[ "$output" == *"No Keychain password item found for profile"* ]] \ 52 + || [[ "$output" == *"profile"* && "$output" == *"not found"* ]] 53 + then 54 + return 2 55 + fi 56 + 57 + return $status 58 + } 59 + 60 + store_notary_credentials() { 61 + local key_path="${APPLE_NOTARIZATION_KEY_PATH:-}" 62 + local key_id="${APPLE_NOTARIZATION_KEY_ID:-}" 63 + local issuer="${APPLE_NOTARIZATION_ISSUER:-}" 64 + 65 + if [[ -n "$key_path" || -n "$key_id" || -n "$issuer" ]]; then 66 + if [[ -z "$key_path" || -z "$key_id" || -z "$issuer" ]]; then 67 + echo "error: APPLE_NOTARIZATION_KEY_PATH/KEY_ID/ISSUER must all be set" 68 + exit 1 69 + fi 70 + if [[ ! -f "$key_path" ]]; then 71 + echo "error: APPLE_NOTARIZATION_KEY_PATH does not exist: $key_path" 72 + exit 1 73 + fi 74 + xcrun notarytool store-credentials "$KEYCHAIN_PROFILE" \ 75 + --key "$key_path" \ 76 + --key-id "$key_id" \ 77 + --issuer "$issuer" 78 + return 79 + fi 80 + 81 + if [[ -z "$APPLE_ID_INPUT" ]]; then 82 + if [[ -t 0 ]]; then 83 + read -r -p "Apple ID email for notarization: " APPLE_ID_INPUT 84 + else 85 + echo "error: APPLE_ID is required when no key-based notarization credentials are provided" 86 + exit 1 87 + fi 88 + fi 89 + 90 + if [[ -z "$APPLE_PASSWORD_INPUT" ]]; then 91 + if [[ -t 0 ]]; then 92 + read -r -s -p "App-specific password (input hidden): " APPLE_PASSWORD_INPUT 93 + echo 94 + else 95 + echo "error: APPLE_PASSWORD is required when no key-based notarization credentials are provided" 96 + exit 1 97 + fi 98 + fi 99 + 100 + if [[ -z "$TEAM_ID_INPUT" ]]; then 101 + TEAM_ID_INPUT="$(team_id_from_identity "$SIGNING_IDENTITY" || true)" 102 + fi 103 + if [[ -z "$TEAM_ID_INPUT" ]]; then 104 + if [[ -t 0 ]]; then 105 + read -r -p "Apple Team ID: " TEAM_ID_INPUT 106 + else 107 + echo "error: APPLE_TEAM_ID is required when it cannot be inferred from signing identity" 108 + exit 1 109 + fi 110 + fi 111 + 112 + xcrun notarytool store-credentials "$KEYCHAIN_PROFILE" \ 113 + --apple-id "$APPLE_ID_INPUT" \ 114 + --password "$APPLE_PASSWORD_INPUT" \ 115 + --team-id "$TEAM_ID_INPUT" 116 + } 117 + 118 + sign_and_notarize_app() { 119 + local app_path="$1" 120 + local submission_zip="$2" 121 + 122 + echo "[release] codesigning app with identity: $SIGNING_IDENTITY" 123 + codesign --force --deep --options runtime --timestamp --sign "$SIGNING_IDENTITY" "$app_path" 124 + codesign --verify --deep --strict --verbose=2 "$app_path" 125 + 126 + echo "[release] create notarization artifact: $submission_zip" 127 + ditto -c -k --sequesterRsrc --keepParent "$app_path" "$submission_zip" 128 + 129 + echo "[release] notarizing artifact..." 130 + if submit_with_keychain_profile "$submission_zip"; then 131 + echo "[release] used keychain profile: $KEYCHAIN_PROFILE" 132 + else 133 + local notary_status=$? 134 + if [[ $notary_status -ne 2 ]]; then 135 + exit "$notary_status" 136 + fi 137 + 138 + echo "[release] keychain profile not found: $KEYCHAIN_PROFILE" 139 + echo "[release] storing notarization credentials..." 140 + store_notary_credentials 141 + xcrun notarytool submit "$submission_zip" --keychain-profile "$KEYCHAIN_PROFILE" --wait 142 + fi 143 + 144 + echo "[release] staple notarization ticket to app" 145 + xcrun stapler staple "$app_path" 146 + xcrun stapler validate "$app_path" 147 + } 148 + 149 + if ! command -v gh >/dev/null 2>&1; then 150 + echo "error: gh CLI is required" 151 + exit 1 152 + fi 153 + 154 + if ! command -v jq >/dev/null 2>&1; then 155 + echo "error: jq is required" 156 + exit 1 157 + fi 158 + 159 + if [[ "$(uname -s)" != "Darwin" ]]; then 160 + echo "error: this script only supports macOS" 161 + exit 1 162 + fi 163 + 34 164 REPO="${GH_REPO:-$(origin_repo_from_remote || true)}" 35 165 if [[ -z "${REPO}" ]]; then 36 166 REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner)" ··· 39 169 SHORT_SHA="$(git rev-parse --short HEAD)" 40 170 DEFAULT_TAG="onevcat-v$(date +%Y.%m.%d)-${SHORT_SHA}" 41 171 TAG="${1:-$DEFAULT_TAG}" 172 + ENABLE_NOTARIZATION="${ENABLE_NOTARIZATION:-1}" 173 + KEYCHAIN_PROFILE="${APPLE_NOTARY_KEYCHAIN_PROFILE:-supacode-notary}" 174 + SIGNING_IDENTITY="${APPLE_SIGNING_IDENTITY:-}" 175 + TEAM_ID_INPUT="${APPLE_TEAM_ID:-}" 176 + APPLE_ID_INPUT="${APPLE_ID:-}" 177 + APPLE_PASSWORD_INPUT="${APPLE_PASSWORD:-}" 42 178 43 179 echo "[release] repository: ${REPO}" 44 180 echo "[release] tag: ${TAG}" 181 + echo "[release] notarization: ${ENABLE_NOTARIZATION}" 45 182 46 183 if git rev-parse "${TAG}" >/dev/null 2>&1; then 47 184 echo "error: local tag ${TAG} already exists" ··· 65 202 mkdir -p build 66 203 ZIP_PATH="build/${PRODUCT_NAME%.app}-${TAG}.app.zip" 67 204 NOTES_PATH="build/release-notes-${TAG}.md" 205 + SUBMISSION_ZIP="build/notary-submit-${TAG}.app.zip" 206 + BUILD_TYPE="Debug (unsigned)" 207 + 208 + if [[ "${ENABLE_NOTARIZATION}" == "1" ]]; then 209 + if ! command -v xcrun >/dev/null 2>&1; then 210 + echo "error: xcrun is required for notarization" 211 + exit 1 212 + fi 213 + if ! command -v codesign >/dev/null 2>&1; then 214 + echo "error: codesign is required for notarization" 215 + exit 1 216 + fi 217 + if [[ -z "$SIGNING_IDENTITY" ]]; then 218 + SIGNING_IDENTITY="$(default_signing_identity || true)" 219 + fi 220 + if [[ -z "$SIGNING_IDENTITY" ]]; then 221 + echo "error: APPLE_SIGNING_IDENTITY is not set and no Developer ID Application identity was found" 222 + exit 1 223 + fi 224 + sign_and_notarize_app "${APP_PATH}" "${SUBMISSION_ZIP}" 225 + BUILD_TYPE="Debug (Developer ID signed + notarized)" 226 + fi 68 227 69 228 echo "[release] package ${APP_PATH} -> ${ZIP_PATH}" 70 229 ditto -c -k --sequesterRsrc --keepParent "${APP_PATH}" "${ZIP_PATH}" ··· 75 234 76 235 - Commit: ${SHORT_SHA} 77 236 - Upstream main (local): ${UPSTREAM_MAIN_SHA} 78 - - Build type: Debug (unsigned) 237 + - Build type: ${BUILD_TYPE} 79 238 - Branch: $(git branch --show-current) 80 239 EOF 81 240