Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 5 additions & 37 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 4 additions & 2 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
93 changes: 63 additions & 30 deletions scripts/bundle-macos.sh
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ $# -lt 2 || $# -gt 3 ]]; then
echo "Usage: $0 <executable> <output-dir> [--debug]"
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <executable> <output-dir> [--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"
Expand All @@ -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" <<EOF
Expand Down Expand Up @@ -122,10 +138,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"
Expand Down Expand Up @@ -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:"
Expand Down
Loading