From ac105ae208054b48dcb18301e0041b3da02a883e Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Wed, 11 Mar 2026 15:10:33 +0100 Subject: [PATCH 1/2] ci(release): publish unsigned macOS artifacts Issue: Architect's GitHub release workflow still produced signed macOS artifacts even though the project should stop publishing signed downloads and document an unsigned distribution path. Solution: Removed certificate import and release signing from the GitHub Actions release job, and renamed the published tarballs so the unsigned status is obvious. Added an unsigned bundling mode that strips inherited signatures from the packaged app and bundled dylibs, then updated the installation and release docs to match the new workflow and quarantine requirements. --- .github/workflows/release.yaml | 42 ++-------------- README.md | 13 ++--- docs/development.md | 6 ++- scripts/bundle-macos.sh | 92 +++++++++++++++++++++++----------- 4 files changed, 78 insertions(+), 75 deletions(-) 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..a3cc245 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,10 +37,12 @@ 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" @@ -122,10 +137,18 @@ $dep" fi } +remove_signature_if_present() { + local file="$1" + if codesign -dv "$file" >/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 +225,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:" From fa2235f40c651276f8fe3c3655f590525ba39106 Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Wed, 11 Mar 2026 15:43:17 +0100 Subject: [PATCH 2/2] fix(bundle): reset app bundle before packaging Issue: A PR review comment pointed out that bundle-macos.sh could leave stale code-signing metadata or leftover files behind when rebuilding into the same output directory, especially in --unsigned mode. Solution: Recreate the Architect.app bundle from a clean path before copying files into it. That removes leftover _CodeSignature and CodeResources metadata along with any stale dylibs or resources, keeping repeated bundle runs deterministic and aligned with the unsigned release workflow. --- scripts/bundle-macos.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/bundle-macos.sh b/scripts/bundle-macos.sh index a3cc245..af9e9f9 100755 --- a/scripts/bundle-macos.sh +++ b/scripts/bundle-macos.sh @@ -47,6 +47,7 @@ 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" <