diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f7829a8..9e82e77 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -45,16 +45,6 @@ jobs: restore-keys: | zig-${{ runner.os }}-${{ matrix.arch }}- - - name: Import code-signing certificate - env: - MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} - MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} - if: ${{ env.MACOS_CERTIFICATE != '' && env.MACOS_CERTIFICATE_PWD != '' }} - uses: Apple-Actions/import-codesign-certs@v6 - with: - p12-file-base64: ${{ env.MACOS_CERTIFICATE }} - p12-password: ${{ env.MACOS_CERTIFICATE_PWD }} - - name: Cache Nix store uses: cachix/cachix-action@v16 with: @@ -82,44 +72,22 @@ jobs: - name: Bundle libraries and package run: | chmod +x scripts/bundle-macos.sh - ./scripts/bundle-macos.sh zig-out/bin/architect release - if security find-identity -v -p codesigning >/tmp/codesign-identities.txt 2>/dev/null; then - IDENTITY=$(awk 'NR==1 {print $2}' /tmp/codesign-identities.txt) - echo "Signing with identity ${IDENTITY}" - for lib in release/Architect.app/Contents/MacOS/lib/*.dylib; do - codesign --force --options runtime --timestamp --sign "$IDENTITY" "$lib" - done - codesign --force --options runtime --timestamp --sign "$IDENTITY" \ - --entitlements macos/Architect.entitlements \ - release/Architect.app/Contents/MacOS/architect - codesign --force --options runtime --timestamp --sign "$IDENTITY" \ - --entitlements macos/Architect.entitlements \ - release/Architect.app - else - echo "No Developer ID cert available; applying ad-hoc signature for Gatekeeper" - for lib in release/Architect.app/Contents/MacOS/lib/*.dylib; do - codesign --force --sign - "$lib" - done - codesign --force --sign - --entitlements macos/Architect.entitlements \ - release/Architect.app/Contents/MacOS/architect - codesign --force --sign - --entitlements macos/Architect.entitlements \ - release/Architect.app - fi + ./scripts/bundle-macos.sh zig-out/bin/architect release --unsigned cd release - tar -czf architect-macos-${{ matrix.arch }}.tar.gz Architect.app + tar -czf architect-macos-${{ matrix.arch }}-unsigned.tar.gz Architect.app working-directory: architect - name: Upload artifact uses: actions/upload-artifact@v7 with: - name: architect-macos-${{ matrix.arch }} - path: architect/release/architect-macos-${{ matrix.arch }}.tar.gz + name: architect-macos-${{ matrix.arch }}-unsigned + path: architect/release/architect-macos-${{ matrix.arch }}-unsigned.tar.gz - name: Create Release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: - files: architect/release/architect-macos-${{ matrix.arch }}.tar.gz + files: architect/release/architect-macos-${{ matrix.arch }}-unsigned.tar.gz draft: false prerelease: false generate_release_notes: true diff --git a/README.md b/README.md index 4203329..2f197ab 100644 --- a/README.md +++ b/README.md @@ -50,28 +50,29 @@ Architect solves this with a grid view that keeps all your agents visible, with ## Installation -### Download Pre-built Binary (macOS) +### Download Pre-built Binary (macOS, unsigned) -Download the latest release from the [releases page](https://github.com/forketyfork/architect/releases). +Download the latest unsigned release from the [releases page](https://github.com/forketyfork/architect/releases). **For Apple Silicon (M1/M2/M3/M4):** ```bash -curl -LO https://github.com/forketyfork/architect/releases/latest/download/architect-macos-arm64.tar.gz -tar -xzf architect-macos-arm64.tar.gz +curl -LO https://github.com/forketyfork/architect/releases/latest/download/architect-macos-arm64-unsigned.tar.gz +tar -xzf architect-macos-arm64-unsigned.tar.gz xattr -dr com.apple.quarantine Architect.app open Architect.app ``` **For Intel Macs:** ```bash -curl -LO https://github.com/forketyfork/architect/releases/latest/download/architect-macos-x86_64.tar.gz -tar -xzf architect-macos-x86_64.tar.gz +curl -LO https://github.com/forketyfork/architect/releases/latest/download/architect-macos-x86_64-unsigned.tar.gz +tar -xzf architect-macos-x86_64-unsigned.tar.gz xattr -dr com.apple.quarantine Architect.app open Architect.app ``` **Note**: +* These GitHub release archives are unsigned. Clear the quarantine attribute before first launch, or macOS may block the app. * The archive contains `Architect.app`. You can launch it with `open Architect.app` or run `./Architect.app/Contents/MacOS/architect` from the terminal. Keep the bundle contents intact. * Not sure which architecture? Run `uname -m` - if it shows `arm64`, use the ARM64 version; if it shows `x86_64`, use the Intel version. diff --git a/docs/development.md b/docs/development.md index 4991e5b..5f8cf20 100644 --- a/docs/development.md +++ b/docs/development.md @@ -89,6 +89,8 @@ git tag v0.1.0 git push origin v0.1.0 ``` +The release workflow packages unsigned app bundles. It does not import macOS signing certificates and does not notarize the app. Release downloads therefore require clearing the quarantine attribute after extraction, as described in the README installation instructions. You can also run the Release workflow manually with `workflow_dispatch` to validate the packaging flow before pushing a real release tag. + Each release includes: -- `architect-macos-arm64.tar.gz` - Apple Silicon -- `architect-macos-x86_64.tar.gz` - Intel +- `architect-macos-arm64-unsigned.tar.gz` - Apple Silicon +- `architect-macos-x86_64-unsigned.tar.gz` - Intel diff --git a/scripts/bundle-macos.sh b/scripts/bundle-macos.sh index ca176ff..af9e9f9 100755 --- a/scripts/bundle-macos.sh +++ b/scripts/bundle-macos.sh @@ -1,17 +1,30 @@ #!/usr/bin/env bash set -euo pipefail -if [[ $# -lt 2 || $# -gt 3 ]]; then - echo "Usage: $0 [--debug]" +if [[ $# -lt 2 ]]; then + echo "Usage: $0 [--debug] [--unsigned]" exit 1 fi EXECUTABLE="$1" OUTPUT_DIR="$2" DEBUG_MODE=false -if [[ "${3:-}" == "--debug" ]]; then - DEBUG_MODE=true -fi +SIGN_APP=true + +for arg in "${@:3}"; do + case "$arg" in + --debug) + DEBUG_MODE=true + ;; + --unsigned) + SIGN_APP=false + ;; + *) + echo "Unknown flag: $arg" + exit 1 + ;; + esac +done APP_NAME="Architect" APP_DIR="$OUTPUT_DIR/${APP_NAME}.app" @@ -24,14 +37,17 @@ ICON_SOURCE="assets/macos/${APP_NAME}.icns" SCRIPT_DIR="$(cd -- "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd -- "$SCRIPT_DIR/.." && pwd)" -if [[ "$DEBUG_MODE" == true ]]; then - ENTITLEMENTS="$REPO_ROOT/macos/ArchitectDebug.entitlements" -else - ENTITLEMENTS="$REPO_ROOT/macos/Architect.entitlements" +if [[ "$SIGN_APP" == true ]]; then + if [[ "$DEBUG_MODE" == true ]]; then + ENTITLEMENTS="$REPO_ROOT/macos/ArchitectDebug.entitlements" + else + ENTITLEMENTS="$REPO_ROOT/macos/Architect.entitlements" + fi fi echo "Bundling macOS application: $EXECUTABLE -> $APP_DIR" +rm -rf "$APP_DIR" mkdir -p "$LIB_DIR" "$RESOURCES_DIR" "$SHARE_DIR" cat > "$CONTENTS_DIR/Info.plist" </dev/null 2>&1; then + echo "Removing embedded signature from $(basename "$file")..." + codesign --remove-signature "$file" + fi +} + echo "Analyzing dynamic library dependencies..." initial_deps=$(otool -L "$EXECUTABLE" | awk '/^[[:space:]]/ {print $1}' | grep '^/nix/store' || true) -# Use a flag instead of early return: signing must always happen even without Nix deps +# Use a flag instead of early return: bundling must still finish even without Nix deps skip_lib_patching=false if [[ -z "$initial_deps" ]]; then echo "No Nix store dependencies found" @@ -202,30 +226,39 @@ if [[ "$skip_lib_patching" != true ]]; then fi fi -echo "" -echo "Signing application bundle with entitlements..." -if [[ ! -f "$ENTITLEMENTS" ]]; then - echo "Error: Entitlements file not found: $ENTITLEMENTS" - exit 1 -fi +if [[ "$SIGN_APP" == true ]]; then + echo "" + echo "Signing application bundle with entitlements..." + if [[ ! -f "$ENTITLEMENTS" ]]; then + echo "Error: Entitlements file not found: $ENTITLEMENTS" + exit 1 + fi -# Sign all bundled libraries first -shopt -s nullglob -for lib in "$LIB_DIR"/*.dylib; do - echo "Signing $(basename "$lib")..." - codesign --force --sign - "$lib" -done -shopt -u nullglob + shopt -s nullglob + for lib in "$LIB_DIR"/*.dylib; do + echo "Signing $(basename "$lib")..." + codesign --force --sign - "$lib" + done + shopt -u nullglob -# Sign the main binary -echo "Signing architect..." -codesign --force --sign - --entitlements "$ENTITLEMENTS" "$MACOS_DIR/architect" + echo "Signing architect..." + codesign --force --sign - --entitlements "$ENTITLEMENTS" "$MACOS_DIR/architect" -# Sign the entire app bundle -echo "Signing ${APP_NAME}.app..." -codesign --force --sign - --entitlements "$ENTITLEMENTS" "$APP_DIR" + echo "Signing ${APP_NAME}.app..." + codesign --force --sign - --entitlements "$ENTITLEMENTS" "$APP_DIR" -echo "Code signing complete." + echo "Code signing complete." +else + echo "" + echo "Removing embedded signatures (--unsigned)..." + remove_signature_if_present "$MACOS_DIR/architect" + shopt -s nullglob + for lib in "$LIB_DIR"/*.dylib; do + remove_signature_if_present "$lib" + done + shopt -u nullglob + echo "Skipping code signing (--unsigned)." +fi echo "" echo "Bundle complete! Structure:"