diff --git a/clients/cli/.github/workflows/release.yml b/clients/cli/.github/workflows/release.yml index 396fada69..9d39f3a86 100644 --- a/clients/cli/.github/workflows/release.yml +++ b/clients/cli/.github/workflows/release.yml @@ -5,6 +5,9 @@ on: tags: - '*' +permissions: + contents: write + jobs: release: runs-on: ubuntu-latest @@ -32,9 +35,66 @@ jobs: API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.version.outputs.VERSION }} + - name: Upload built binaries + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + - name: Create draft GitHub Release + uses: softprops/action-gh-release@v1 + with: + draft: true + name: ${{ github.ref_name }} + tag_name: ${{ github.ref_name }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + sign_and_notarize: + runs-on: macos-latest + needs: release + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Download built binaries + uses: actions/download-artifact@v4 + with: + name: dist + path: dist + - name: Sign CLI binaries + run: bash ./build/sign_and_notarize.sh + env: + SIGNING_CERTIFICATE: ${{ secrets.SIGNING_CERTIFICATE }} + CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }} + SIGNING_IDENTITY: ${{ secrets.SIGNING_IDENTITY }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + DIST_DIR: "dist" + NOTARIZATION_APPLE_ID: ${{ secrets.NOTARIZATION_APPLE_ID }} + NOTARIZATION_APP_PASSWORD: ${{ secrets.NOTARIZATION_APP_PASSWORD }} + NOTARIZATION_TEAM_ID: ${{ secrets.NOTARIZATION_TEAM_ID }} + - name: Upload signed binaries to Draft Release + uses: softprops/action-gh-release@v1 + with: + files: | + dist/phrase_macosx_*.zip + dist/*.tar.gz + fail_on_unmatched_files: true + overwrite: true + name: ${{ github.ref_name }} + tag_name: ${{ github.ref_name }} + draft: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish GitHub Release + uses: softprops/action-gh-release@v1 + with: + draft: false + name: ${{ github.ref_name }} + tag_name: ${{ github.ref_name }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} brew: runs-on: ubuntu-latest - needs: release + needs: sign_and_notarize steps: - name: Checkout uses: actions/checkout@v4 diff --git a/clients/cli/build/release.sh b/clients/cli/build/release.sh index e29198c83..c3de46f08 100755 --- a/clients/cli/build/release.sh +++ b/clients/cli/build/release.sh @@ -25,52 +25,4 @@ cp dist/phrase_linux_arm64 dist/linux/arm64 docker buildx build --tag "${IMAGE}" --tag ${IMAGE_LATEST} --platform linux/amd64,linux/arm64 -f ./Dockerfile --push . -# Create release -function create_release_data() -{ - cat < /dev/null - echo Hash: $(sha256sum $file) - fi -done - -echo "Publishing release" -curl \ - --silent \ - -X PATCH \ - -H "Authorization: token ${GITHUB_TOKEN}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/phrase/phrase-cli/releases/${release_id}" \ - -d '{"draft": false}' > /dev/null - -echo "Release successful" +echo "Artifacts built and ready in dist/ directory. GitHub Release creation handled in GitHub Actions workflow." diff --git a/clients/cli/build/sign_and_notarize.sh b/clients/cli/build/sign_and_notarize.sh new file mode 100644 index 000000000..8e097b378 --- /dev/null +++ b/clients/cli/build/sign_and_notarize.sh @@ -0,0 +1,97 @@ +#!/bin/bash +set -euo pipefail +umask 077 + +CERTIFICATE_BASE64="${SIGNING_CERTIFICATE}" +P12_PASSWORD="${CERTIFICATE_PASSWORD}" +SIGNING_IDENTITY="${SIGNING_IDENTITY}" +KEYCHAIN_PASSWORD="${KEYCHAIN_PASSWORD}" +DIST_DIR="${DIST_DIR:-dist}" + +CERTIFICATE_PATH="./build_certificate.p12" +KEYCHAIN_PATH="./my-signing.keychain-db" + +# Basic env validation to fail fast +require_env() { + local name="$1" value="$2" + if [[ -z "$value" ]]; then + echo "โŒ Missing required environment variable: $name" >&2 + exit 1 + fi +} + +require_env "SIGNING_CERTIFICATE" "${CERTIFICATE_BASE64}" +require_env "CERTIFICATE_PASSWORD" "${P12_PASSWORD}" +require_env "SIGNING_IDENTITY" "${SIGNING_IDENTITY}" +require_env "KEYCHAIN_PASSWORD" "${KEYCHAIN_PASSWORD}" +require_env "NOTARIZATION_APPLE_ID" "${NOTARIZATION_APPLE_ID:-}" +require_env "NOTARIZATION_APP_PASSWORD" "${NOTARIZATION_APP_PASSWORD:-}" +require_env "NOTARIZATION_TEAM_ID" "${NOTARIZATION_TEAM_ID:-}" + + +cleanup() { + echo "๐Ÿงน Cleaning up keychain and certificate..." + # Attempt to delete the temporary keychain + security delete-keychain "$KEYCHAIN_PATH" || true + # Remove certificate file + rm -f "$CERTIFICATE_PATH" || true +} +trap cleanup EXIT + +echo "๐Ÿ” Setting up certificate and keychain..." + +# Decode the certificate (macOS-only) +echo "$CERTIFICATE_BASE64" | /usr/bin/base64 -D > "$CERTIFICATE_PATH" +# Restrict permissions on sensitive certificate material +chmod 600 "$CERTIFICATE_PATH" + +# Create temporary keychain +security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" +security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" +security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + +# Import certificate into keychain +security import "$CERTIFICATE_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" +security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + +# Show available signing identities for visibility +echo "๐Ÿ”Ž Available signing identities (codesigning):" +security find-identity -v -p codesigning "$KEYCHAIN_PATH" || true + +# Find and sign all macOS binaries dynamically +echo "๐Ÿ”Ž Searching for macOS binaries in $DIST_DIR..." + +find "$DIST_DIR" -type f \( -name "phrase_macosx_*" ! -name "*.tar.gz" \) -print0 | while IFS= read -r -d '' binary; do + echo "๐Ÿ” Signing $binary..." + codesign --timestamp --options runtime --keychain "$KEYCHAIN_PATH" --sign "$SIGNING_IDENTITY" "$binary" + codesign --verify --verbose=2 --keychain "$KEYCHAIN_PATH" "$binary" +done + +echo "โœ… All macOS binaries signed successfully." + +# --- Zip artifacts for notarization --- +echo "๐Ÿ“ฆ Zipping macOS binaries for notarization..." +shopt -s nullglob +for bin in "$DIST_DIR"/phrase_macosx_*; do + [[ "$bin" == *.tar.gz ]] && continue + relbin="${bin#${DIST_DIR}/}" + echo "Creating $DIST_DIR/${relbin}.zip" + ( + cd "$DIST_DIR" && /usr/bin/zip -o "${relbin}.zip" "${relbin}" + ) +done + +# --- Notarization via Apple notarytool (Apple ID + app-specific password) --- +echo "๐Ÿ“ Notarizing zipped binaries with Apple Notary (Apple ID)..." +for zip in "$DIST_DIR"/phrase_macosx_*.zip; do + [[ -e "$zip" ]] || continue + echo "Submitting $zip to Apple Notary..." + xcrun notarytool submit "$zip" \ + --apple-id "$NOTARIZATION_APPLE_ID" \ + --password "$NOTARIZATION_APP_PASSWORD" \ + --team-id "$NOTARIZATION_TEAM_ID" \ + --wait + echo "โ„น๏ธ Notarization complete for $zip." +done + +echo "๐ŸŽ‰ Signing and notarization finished." \ No newline at end of file