diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..9027b05 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,222 @@ +# GitHub Actions Workflows + +This directory contains automated workflows for building, testing, and releasing Icon Grabber. + +## Workflows + +### 1. CI Tests (`ci.yml`) + +**Triggers:** +- Push to `main` or `development` branches +- Pull requests to `main` or `development` branches +- Manual trigger via workflow dispatch + +**What it does:** +- Builds the icongrabber binary +- Runs integration tests +- Verifies the man page exists +- Uploads build artifacts for testing + +**Use case:** Automated testing for every code change to ensure quality. + +--- + +### 2. Release (`release.yml`) + +**Triggers:** +- Push of version tags (e.g., `v1.0.0`) +- Manual trigger via workflow dispatch + +**What it does:** +- Builds an optimized release binary +- **Signs the binary** with Developer ID Application certificate +- Creates a signed installer package (`.pkg`) +- **Notarizes the package** with Apple +- **Staples the notarization ticket** to the package +- Creates source tarball +- Generates SHA-256 checksums +- Creates a GitHub Release with all artifacts + +**Security Features:** +- Binary signed with hardened runtime +- Package signed with Developer ID Installer certificate +- Notarized by Apple for Gatekeeper approval +- Stapled for offline verification + +--- + +## Required Secrets + +The following secrets must be configured in the GitHub repository settings: + +### 1. `APP_CERTIFICATES_P12_MAOS` +Base64-encoded P12 certificate file for signing the application binary. + +**Certificate Type:** Developer ID Application + +**How to create:** +```bash +# Export from Keychain Access as .p12 file, then: +base64 -i certificate.p12 | pbcopy +``` + +### 2. `APP_CERTIFICATES_P12_PASSWORD_MAOS` +Password for the application certificate P12 file. + +### 3. `PKG_CERTIFICATES_P12_MAOS` +Base64-encoded P12 certificate file for signing installer packages. + +**Certificate Type:** Developer ID Installer + +**How to create:** +```bash +# Export from Keychain Access as .p12 file, then: +base64 -i installer-cert.p12 | pbcopy +``` + +### 4. `PKG_CERTIFICATES_P12_PASSWORD_MAOS` +Password for the installer certificate P12 file. + +### 5. `NOTARY_APP_PASSWORD_MAOS` +App-specific password for notarization. + +**How to create:** +1. Go to https://appleid.apple.com +2. Sign in with `opensource@macadmins.io` +3. Generate an app-specific password +4. Save it securely + +--- + +## Setting Up Secrets + +1. Go to repository **Settings** → **Secrets and variables** → **Actions** +2. Click **New repository secret** +3. Add each secret listed above +4. Ensure the secret names match exactly + +--- + +## Team and Identity + +- **Team ID:** `T4SK8ZXCXG` +- **Apple ID:** `opensource@macadmins.io` +- **Organization:** Mac Admins Open Source +- **Application Signing Identity:** `Developer ID Application: Mac Admins Open Source (T4SK8ZXCXG)` +- **Installer Signing Identity:** `Developer ID Installer: Mac Admins Open Source (T4SK8ZXCXG)` + +--- + +## Creating a Release + +### Method 1: Git Tag (Recommended) + +```bash +# Tag the commit +git tag -a v1.0.0 -m "Release version 1.0.0" + +# Push the tag +git push origin v1.0.0 +``` + +This will automatically trigger the release workflow. + +### Method 2: Manual Dispatch + +1. Go to **Actions** tab in GitHub +2. Select **Release** workflow +3. Click **Run workflow** +4. Enter the version number (e.g., `1.0.0`) +5. Click **Run workflow** + +--- + +## Release Process + +When a release is triggered: + +1. ✅ Code is checked out +2. ✅ Certificates are imported into temporary keychain +3. ✅ Binary is compiled with optimizations +4. ✅ Binary is signed with hardened runtime +5. ✅ Installer package is created +6. ✅ Package is signed +7. ✅ Package is submitted to Apple for notarization +8. ✅ Workflow waits for notarization approval (~5-10 minutes) +9. ✅ Notarization ticket is stapled to package +10. ✅ Checksums are generated +11. ✅ GitHub Release is created with all artifacts + +--- + +## Verifying Releases + +### Check Binary Signature +```bash +codesign -vvv --deep --strict /usr/local/bin/icongrabber +``` + +### Check Package Signature +```bash +pkgutil --check-signature icongrabber-1.0.0.pkg +``` + +### Check Notarization +```bash +spctl --assess --verbose --type install icongrabber-1.0.0.pkg +``` + +### Verify Checksums +```bash +shasum -a 256 -c checksums.txt +``` + +--- + +## Troubleshooting + +### Notarization Fails + +Check the notarization log: +```bash +xcrun notarytool log --keychain-profile "icongrabber-notary" +``` + +Common issues: +- Binary not signed with hardened runtime +- Missing entitlements +- Invalid certificate +- Expired certificate + +### Signing Fails + +- Verify secrets are correctly set in GitHub +- Check certificate expiration dates +- Ensure P12 passwords are correct +- Verify Team ID matches certificates + +### Build Fails + +- Check Swift version compatibility (requires Swift 5.9+) +- Verify macOS runner version (macos-14) +- Review build logs for compilation errors + +--- + +## Updating Workflows + +When modifying workflows: + +1. Test changes on a feature branch first +2. Create a PR to review changes +3. Merge to main after approval +4. Monitor the workflow runs + +--- + +## References + +- [Apple Code Signing Guide](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/) +- [Apple Notarization Guide](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Nudge Build Process](https://github.com/macadmins/nudge/blob/main/.github/workflows/build_nudge_release.yml) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e93444e..6d94520 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,11 @@ name: CI Tests on: + push: + branches: [ main, development ] + paths-ignore: + - '**.md' + - 'examples/**' pull_request: branches: [ main, development ] workflow_dispatch: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ff87f7..db1ef3a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,16 +14,34 @@ on: env: APP_NAME: icongrabber BUNDLE_ID: com.macadmins.icongrabber + NOTARY_APP_PASSWORD: ${{ secrets.NOTARY_APP_PASSWORD_MAOS }} jobs: build-and-release: - runs-on: macos-latest + runs-on: macos-14 permissions: contents: write steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Apple Application certificates + uses: apple-actions/import-codesign-certs@v3 + with: + keychain-password: ${{ github.run_id }} + p12-file-base64: ${{ secrets.APP_CERTIFICATES_P12_MAOS }} + p12-password: ${{ secrets.APP_CERTIFICATES_P12_PASSWORD_MAOS }} + + - name: Install Apple Installer certificates + uses: apple-actions/import-codesign-certs@v3 + with: + create-keychain: false + keychain-password: ${{ github.run_id }} + p12-file-base64: ${{ secrets.PKG_CERTIFICATES_P12_MAOS }} + p12-password: ${{ secrets.PKG_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Set version id: version @@ -55,7 +73,15 @@ jobs: echo "Building optimized release binary..." mkdir -p bin swiftc -O -o bin/${{ env.APP_NAME }} icongrabber-cli/main.swift -framework AppKit - strip bin/${{ env.APP_NAME }} + + echo "Signing binary..." + codesign --force --options runtime \ + --sign "Developer ID Application: Mac Admins Open Source (T4SK8ZXCXG)" \ + --timestamp \ + bin/${{ env.APP_NAME }} + + echo "Verifying signature..." + codesign -vvv --deep --strict bin/${{ env.APP_NAME }} echo "Binary info:" ls -lh bin/${{ env.APP_NAME }} @@ -140,12 +166,17 @@ jobs: EOF - # Build the product archive + # Build the product archive with signing productbuild \ --distribution distribution.xml \ --package-path . \ + --sign "Developer ID Installer: Mac Admins Open Source (T4SK8ZXCXG)" \ + --timestamp \ ${{ env.APP_NAME }}-$VERSION.pkg + # Verify package signature + pkgutil --check-signature ${{ env.APP_NAME }}-$VERSION.pkg + ls -lh ${{ env.APP_NAME }}-$VERSION.pkg - name: Create tarball with binary @@ -163,6 +194,29 @@ jobs: echo "Created tarball:" ls -lh ${{ env.APP_NAME }}-$VERSION-macos-binary.tar.gz + - name: Notarize package + run: | + VERSION="${{ steps.version.outputs.VERSION }}" + + echo "Setting up notarization credentials..." + xcrun notarytool store-credentials "icongrabber-notary" \ + --apple-id "opensource@macadmins.io" \ + --team-id "T4SK8ZXCXG" \ + --password "$NOTARY_APP_PASSWORD" + + echo "Submitting package for notarization..." + xcrun notarytool submit "${{ env.APP_NAME }}-$VERSION.pkg" \ + --keychain-profile "icongrabber-notary" \ + --wait + + echo "Stapling notarization ticket..." + xcrun stapler staple "${{ env.APP_NAME }}-$VERSION.pkg" + + echo "Verifying stapled package..." + xcrun stapler validate "${{ env.APP_NAME }}-$VERSION.pkg" + + echo "Package successfully signed, notarized, and stapled!" + - name: Generate checksums run: | VERSION="${{ steps.version.outputs.VERSION }}" @@ -173,6 +227,14 @@ jobs: echo "Checksums:" cat checksums.txt + - name: Get Changelog Entry + id: changelog_reader + uses: mindsers/changelog-reader-action@v2 + with: + validation_depth: 100 + version: ${{ steps.version.outputs.VERSION }} + path: ./CHANGELOG.md + - name: Generate release notes id: release_notes run: | @@ -181,14 +243,21 @@ jobs: cat > release_notes.md << EOF ## Icon Grabber v$VERSION + ### 🎉 Release Notes + + This release has been automatically built, signed, and notarized by GitHub Actions. + + **Security**: Both the binary and installer package are signed with a valid Developer ID certificate and notarized by Apple. The package is stapled for offline verification. + ### Installation #### Option 1: Install via PKG (Recommended) Download \`${{ env.APP_NAME }}-$VERSION.pkg\` and double-click to install. The PKG installer will: - - Install the binary to \`/usr/local/bin/icongrabber\` + - Install the signed binary to \`/usr/local/bin/icongrabber\` - Install the man page to \`/usr/local/share/man/man1/\` + - The package is signed, notarized, and stapled #### Option 2: Manual Installation Download \`${{ env.APP_NAME }}-$VERSION-macos-binary.tar.gz\`: @@ -213,7 +282,7 @@ jobs: ### What's Changed - See [CHANGELOG.md](https://github.com/macadmins/icongrabber/blob/main/CHANGELOG.md) for details. + ${{ steps.changelog_reader.outputs.changes }} --- diff --git a/CHANGELOG.md b/CHANGELOG.md index cf74bf0..1b6f3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,22 +5,28 @@ All notable changes to Icon Grabber will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.0.0] - YYYY-MM-DD +## [Unreleased] + +### Added +- Automated build process with code signing and notarization +- GitHub Actions workflows for CI and releases + +--- + +## [1.0.0] - 2024-12-15 ### Added - Initial release - Extract icons from macOS applications - Support for custom sizes (16x16 to 1024x1024) - PNG output format -- Command-line interface +- File size optimization using sips compression +- Command-line interface with intuitive options - Man page documentation - Batch processing examples - -### Features -- Fast, native Swift implementation -- No external dependencies -- High-quality icon extraction -- Scriptable and automatable +- Native Swift implementation with no dependencies +- High-quality icon extraction preserving original quality +- Scriptable and automatable for workflows --- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d16b1b..d4d52c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,6 +84,69 @@ Feature requests are welcome! Please: Feel free to open an issue for any questions about contributing! +## For Maintainers + +This section is for maintainers who have write access to the repository. + +### Creating a Release + +1. **Update CHANGELOG.md** with release notes for the new version +2. **Commit the changes:** + ```bash + git add CHANGELOG.md + git commit -m "Update changelog for v1.0.0" + git push origin main + ``` + +3. **Create and push a tag:** + ```bash + git tag -a v1.0.0 -m "Release version 1.0.0" + git push origin v1.0.0 + ``` + +4. **Monitor the workflow** at https://github.com/macadmins/icongrabber/actions + +The GitHub Actions workflow will automatically: +1. Build the optimized binary +2. Sign the binary with hardened runtime +3. Create a signed installer package +4. Submit to Apple for notarization (~10 minutes) +5. Staple the notarization ticket +6. Create a GitHub release with all artifacts +7. Extract changelog entries and include them in release notes + +### Manual Release (Alternative) + +You can also trigger a release manually: + +1. Go to **Actions** → **Release** workflow +2. Click **"Run workflow"** +3. Enter the version number (e.g., `1.0.0`) +4. Click **"Run workflow"** + +### Release Documentation + +- [Workflow Documentation](.github/workflows/README.md) - How the build process works +- [Secrets Management](.github/SECRETS.md) - Managing certificates and credentials +- [Setup Summary](.github/SETUP_SUMMARY.md) - Overview of the automated build setup + +### Verifying Releases + +After a release is created, verify the artifacts: + +```bash +# Download and verify signature +curl -LO https://github.com/macadmins/icongrabber/releases/download/v1.0.0/icongrabber-1.0.0.pkg +pkgutil --check-signature icongrabber-1.0.0.pkg + +# Verify notarization +spctl --assess --verbose --type install icongrabber-1.0.0.pkg + +# Verify checksums +curl -LO https://github.com/macadmins/icongrabber/releases/download/v1.0.0/checksums.txt +shasum -a 256 -c checksums.txt +``` + ## License By contributing, you agree that your contributions will be licensed under the Apache License 2.0. diff --git a/README.md b/README.md index d29a92d..8c34fb7 100644 --- a/README.md +++ b/README.md @@ -292,22 +292,6 @@ Contributions are welcome! Feel free to: Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. -## For Maintainers - -### Creating Releases - -See the complete [Release Guide](.github/RELEASE_GUIDE.md) for detailed instructions. - -**Quick release:** -```bash -# 1. Update CHANGELOG.md -# 2. Create and push tag -git tag v1.0.0 -git push origin v1.0.0 -``` - -The workflow automatically builds and publishes the release. - ## License This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.