diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 80aec6651..bc4676f83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -464,7 +464,7 @@ jobs: CORE_MANIFEST: ${{ steps.core-paths.outputs.core_manifest }} CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }} OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }} - OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }} + OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_CORE_SENTRY_DSN }} # Sentry release tracking (#405): `option_env!("OPENHUMAN_BUILD_SHA")` # in src/main.rs bakes the short SHA into the release tag # (`openhuman@+`) so core events match the frontend. @@ -511,7 +511,7 @@ jobs: # (`vite build`) bakes Sentry + debug flags into the final bundle. # Vite is invoked via tauri.conf.json's beforeBuildCommand during # `cargo tauri build`, so these env vars must be present here. - VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }} + VITE_SENTRY_DSN: ${{ vars.OPENHUMAN_REACT_SENTRY_DSN }} VITE_DEBUG: ${{ vars.VITE_DEBUG }} # Sentry release tracking (#405) — must match the Vite build triggered # by Tauri so source maps uploaded there resolve correctly against the @@ -804,24 +804,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # TODO: Uncomment this when we have a staging tag - # - name: Build and push staging tag - # uses: docker/build-push-action@v6 - # with: - # context: . - # file: Dockerfile - # push: true - # tags: | - # ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:staging-${{ needs.prepare-release.outputs.tag }} - # cache-from: type=gha - # cache-to: type=gha,mode=max - # labels: | - # org.opencontainers.image.title=openhuman-core - # org.opencontainers.image.description=OpenHuman core JSON-RPC server - # org.opencontainers.image.version=${{ needs.prepare-release.outputs.version }} - # org.opencontainers.image.source=https://github.com/tinyhumansai/openhuman - # org.opencontainers.image.revision=${{ needs.prepare-release.outputs.sha }} - # ========================================================================= # Phase 3c: Generate and upload latest.json for the Tauri auto-updater. # Runs after every platform has uploaded its updater artifact (.sig files diff --git a/.gitignore b/.gitignore index 69683d52d..9933adf6a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,12 @@ prompt-dumps/ scripts/ci-secrets.json scripts/ci-secrets.local.json +# act (local GitHub Actions runner) +.secrets +.vars +.actrc +.github/act-event.json + # Editor directories and files .idea .DS_Store diff --git a/scripts/release/repackage-dmg.sh b/scripts/release/repackage-dmg.sh index 02c9465d9..8edd9f3f2 100755 --- a/scripts/release/repackage-dmg.sh +++ b/scripts/release/repackage-dmg.sh @@ -80,11 +80,34 @@ rm -f "$DMG_RW" DMG_RW="" echo "[dmg] Notarizing DMG..." +DMG_SUBMIT_OUT="$(mktemp /tmp/notarize-dmg-XXXXXX.json)" +set +e xcrun notarytool submit "$DMG_PATH" \ --apple-id "$APPLE_ID" \ --password "$APPLE_PASSWORD" \ --team-id "$APPLE_TEAM_ID" \ - --wait + --output-format json \ + --wait > "$DMG_SUBMIT_OUT" +DMG_SUBMIT_RC=$? +set -e +cat "$DMG_SUBMIT_OUT" + +DMG_SUBMISSION_ID="$(/usr/bin/python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("id",""))' "$DMG_SUBMIT_OUT" 2>/dev/null || true)" +DMG_SUBMISSION_STATUS="$(/usr/bin/python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("status",""))' "$DMG_SUBMIT_OUT" 2>/dev/null || true)" +rm -f "$DMG_SUBMIT_OUT" + +if [ -n "$DMG_SUBMISSION_ID" ]; then + echo "[dmg] Fetching notarytool developer log for $DMG_SUBMISSION_ID:" + xcrun notarytool log "$DMG_SUBMISSION_ID" \ + --apple-id "$APPLE_ID" \ + --password "$APPLE_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" || true +fi + +if [ "$DMG_SUBMISSION_STATUS" != "Accepted" ] || [ "$DMG_SUBMIT_RC" -ne 0 ]; then + echo "[dmg] ERROR: DMG notarization did not succeed (status=$DMG_SUBMISSION_STATUS, rc=$DMG_SUBMIT_RC)" >&2 + exit 1 +fi xcrun stapler staple "$DMG_PATH" echo "[dmg] DMG notarization complete: $DMG_PATH" diff --git a/scripts/release/sign-and-notarize-macos.sh b/scripts/release/sign-and-notarize-macos.sh index bdaa91d6d..c5b9d5999 100755 --- a/scripts/release/sign-and-notarize-macos.sh +++ b/scripts/release/sign-and-notarize-macos.sh @@ -78,8 +78,23 @@ codesign_framework() { # ── Nested Frameworks/ (CEF + Helper apps) ────────────────────────────────── # Must be signed from the inside out, before the outer .app bundle. if [ -d "$APP_PATH/Contents/Frameworks" ]; then - # 1. Sign each *.framework as a single bundle. + # 1. For each *.framework: pre-sign loose dylibs/.so files inside it + # (CEF puts libEGL, libGLESv2, libvk_swiftshader, libcef_sandbox in + # `Libraries/` next to the main binary, NOT under Versions/A/, so the + # bundle signature doesn't reach them and notarization rejects them as + # ad-hoc signed without a secure timestamp). Then seal the framework + # bundle so its CodeResources covers the freshly-signed dylibs. while IFS= read -r -d '' fw; do + FW_NAME="$(basename "$fw" .framework)" + echo "[sign] Pre-signing inner Mach-O files in: $(basename "$fw")" + while IFS= read -r -d '' inner; do + # Skip the framework's main binary (sealed by the bundle pass below). + case "$inner" in + "$fw/$FW_NAME"|"$fw/Versions/"*"/$FW_NAME") continue ;; + esac + echo "[sign] $(basename "$inner")" + codesign_framework "$inner" + done < <(find "$fw" \( -name '*.dylib' -o -name '*.so' \) -type f -print0) echo "[sign] Signing framework bundle: $(basename "$fw")" codesign_framework "$fw" done < <(find "$APP_PATH/Contents/Frameworks" -maxdepth 1 -type d -name '*.framework' -print0) @@ -121,16 +136,44 @@ echo "[sign] Notarizing..." NOTARIZE_ZIP="$(mktemp /tmp/OpenHuman-notarize-XXXXXX.zip)" ditto -c -k --keepParent "$APP_PATH" "$NOTARIZE_ZIP" +SUBMIT_OUT="$(mktemp /tmp/notarize-submit-XXXXXX.json)" +set +e xcrun notarytool submit "$NOTARIZE_ZIP" \ --apple-id "$APPLE_ID" \ --password "$APPLE_PASSWORD" \ --team-id "$APPLE_TEAM_ID" \ - --wait + --output-format json \ + --wait > "$SUBMIT_OUT" +SUBMIT_RC=$? +set -e +cat "$SUBMIT_OUT" rm -f "$NOTARIZE_ZIP" +SUBMISSION_ID="$(/usr/bin/plutil -convert json -o - "$SUBMIT_OUT" 2>/dev/null \ + | /usr/bin/python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' 2>/dev/null || true)" +SUBMISSION_STATUS="$(/usr/bin/plutil -convert json -o - "$SUBMIT_OUT" 2>/dev/null \ + | /usr/bin/python3 -c 'import json,sys; print(json.load(sys.stdin).get("status",""))' 2>/dev/null || true)" +rm -f "$SUBMIT_OUT" + +echo "[sign] notarytool exit=$SUBMIT_RC id=$SUBMISSION_ID status=$SUBMISSION_STATUS" + +if [ -n "$SUBMISSION_ID" ]; then + echo "[sign] Fetching notarytool developer log for $SUBMISSION_ID:" + xcrun notarytool log "$SUBMISSION_ID" \ + --apple-id "$APPLE_ID" \ + --password "$APPLE_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" || true +fi + +if [ "$SUBMISSION_STATUS" != "Accepted" ] || [ "$SUBMIT_RC" -ne 0 ]; then + echo "[sign] ERROR: notarization did not succeed (status=$SUBMISSION_STATUS, rc=$SUBMIT_RC)" >&2 + exit 1 +fi + # ── Staple ─────────────────────────────────────────────────────────────────── echo "[sign] Stapling..." xcrun stapler staple "$APP_PATH" echo "[sign] Notarization complete" +