Skip to content
Merged
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
170 changes: 156 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,65 @@
.PHONY: build test lint check clean install release-mac
SHELL := /bin/bash

.PHONY: \
build \
test \
lint \
check \
clean \
install \
release-mac \
notarize-mac \
verify-mac \
release-mac-notarized \
release-macos-notarized

BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
GIT_HEIGHT := $(shell git rev-list --count HEAD 2>/dev/null || echo 0)

GIT_DESC := $(shell git describe --always)
ifneq ($(shell git status --porcelain),)
GIT_DESC := $(GIT_DESC)-dirty
GIT_DESC := $(GIT_DESC)-dirty
endif

LDFLAGS := -X main.buildVersion=B$(GIT_HEIGHT) -X main.buildCommit=$(GIT_DESC) -X main.buildDate=$(BUILD_DATE)
# Metadata for ordinary development builds.
LDFLAGS := \
-X main.buildVersion=B$(GIT_HEIGHT) \
-X main.buildCommit=$(GIT_DESC) \
-X main.buildDate=$(BUILD_DATE)

# Pass VERSION explicitly for a release, e.g.:
#
# make release-mac-notarized VERSION=v0.0.13
#
VERSION ?= $(shell git describe --tags --exact-match 2>/dev/null || git describe --tags --always)

# Metadata embedded in release builds.
RELEASE_LDFLAGS := \
-s -w \
-X main.buildVersion=$(VERSION) \
-X main.buildCommit=$(GIT_DESC) \
-X main.buildDate=$(BUILD_DATE)

# First usable Developer ID Application identity in the current keychain.
# Override this if there is more than one, e.g.:
#
# make release-mac-notarized VERSION=v0.0.13 \
# SIGN_IDENTITY='Developer ID Application: Tailscale Inc. (W5364U7YZB)'
#
SIGN_IDENTITY ?= $(shell security find-identity -v -p codesigning 2>/dev/null | sed -n 's/.*"\(Developer ID Application[^"]*\)".*/\1/p' | head -1)

# Name passed to `xcrun notarytool store-credentials` on this Mac.
NOTARY_PROFILE ?= tailscale-notary

RELEASE_DIR := .build/release
ARM64_BIN := $(RELEASE_DIR)/aperture_darwin_arm64
AMD64_BIN := $(RELEASE_DIR)/aperture_darwin_amd64
ARM64_ARCHIVE := $(RELEASE_DIR)/aperture_$(VERSION)_darwin_arm64.zip
AMD64_ARCHIVE := $(RELEASE_DIR)/aperture_$(VERSION)_darwin_amd64.zip
CHECKSUMS := $(RELEASE_DIR)/aperture_$(VERSION)_darwin_checksums.txt

build:
mkdir -p .build
go build -ldflags "$(LDFLAGS)" -o .build/aperture ./cmd/aperture

test:
Expand All @@ -36,15 +85,108 @@ install:
clean:
rm -rf .build/

# First Developer ID Application identity in the keychain; override when there
# is more than one: make release-mac SIGN_IDENTITY="Developer ID Application: Name (TEAMID)"
SIGN_IDENTITY ?= $(shell security find-identity -v -p codesigning 2>/dev/null | sed -n 's/.*"\(Developer ID Application[^"]*\)".*/\1/p' | head -1)

# Signed macOS release binaries in .build/release/. Runs on the Mac that holds
# the cert; codesign and the identity do not exist elsewhere.
# Build and sign the final architecture-specific macOS executables. Nothing
# must modify these files after this target finishes.
release-mac:
@if [ -z "$(SIGN_IDENTITY)" ]; then echo "no Developer ID Application identity in the keychain"; exit 1; fi
GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w $(LDFLAGS)" -o .build/release/aperture_darwin_arm64 ./cmd/aperture
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w $(LDFLAGS)" -o .build/release/aperture_darwin_amd64 ./cmd/aperture
codesign --options runtime --timestamp --sign "$(SIGN_IDENTITY)" .build/release/aperture_darwin_arm64 .build/release/aperture_darwin_amd64
codesign --verify --verbose=2 .build/release/aperture_darwin_arm64 .build/release/aperture_darwin_amd64
@set -euo pipefail; \
if [ -z "$(SIGN_IDENTITY)" ]; then \
echo "error: no Developer ID Application identity found in the keychain"; \
echo "run: security find-identity -v -p codesigning"; \
exit 1; \
fi; \
mkdir -p "$(RELEASE_DIR)"; \
echo "==> Building arm64 macOS release binary"; \
GOOS=darwin GOARCH=arm64 go build \
-ldflags "$(RELEASE_LDFLAGS)" \
-o "$(ARM64_BIN)" \
./cmd/aperture; \
echo "==> Building amd64 macOS release binary"; \
GOOS=darwin GOARCH=amd64 go build \
-ldflags "$(RELEASE_LDFLAGS)" \
-o "$(AMD64_BIN)" \
./cmd/aperture; \
echo "==> Checking binary architectures"; \
lipo -archs "$(ARM64_BIN)"; \
lipo -archs "$(AMD64_BIN)"; \
echo "==> Signing as: $(SIGN_IDENTITY)"; \
codesign --force --options runtime --timestamp \
--sign "$(SIGN_IDENTITY)" "$(ARM64_BIN)"; \
codesign --force --options runtime --timestamp \
--sign "$(SIGN_IDENTITY)" "$(AMD64_BIN)"; \
echo "==> Verifying binary signatures"; \
codesign --verify --strict --verbose=4 "$(ARM64_BIN)"; \
codesign --verify --strict --verbose=4 "$(AMD64_BIN)"

# Package the signed binaries and wait for Apple to notarize each archive.
# A ZIP is not itself code-signed: it contains the signed executable.
notarize-mac:
@set -euo pipefail; \
test -x "$(ARM64_BIN)"; \
test -x "$(AMD64_BIN)"; \
echo "==> Checking notarization credentials"; \
xcrun notarytool history --keychain-profile "$(NOTARY_PROFILE)" >/dev/null; \
echo "==> Rechecking signed binaries before packaging"; \
codesign --verify --strict --verbose=4 "$(ARM64_BIN)"; \
codesign --verify --strict --verbose=4 "$(AMD64_BIN)"; \
echo "==> Creating release archives"; \
rm -f "$(ARM64_ARCHIVE)" "$(AMD64_ARCHIVE)" "$(CHECKSUMS)"; \
ditto -c -k --keepParent "$(ARM64_BIN)" "$(ARM64_ARCHIVE)"; \
ditto -c -k --keepParent "$(AMD64_BIN)" "$(AMD64_ARCHIVE)"; \
echo "==> arm64 archive contents"; \
unzip -l "$(ARM64_ARCHIVE)"; \
echo "==> amd64 archive contents"; \
unzip -l "$(AMD64_ARCHIVE)"; \
echo "==> Submitting arm64 archive for notarization"; \
xcrun notarytool submit "$(ARM64_ARCHIVE)" \
--keychain-profile "$(NOTARY_PROFILE)" \
--wait; \
echo "==> Submitting amd64 archive for notarization"; \
xcrun notarytool submit "$(AMD64_ARCHIVE)" \
--keychain-profile "$(NOTARY_PROFILE)" \
--wait; \
shasum -a 256 "$(ARM64_ARCHIVE)" "$(AMD64_ARCHIVE)" > "$(CHECKSUMS)"

# Extract and assess the exact executables that users receive. The find
# commands intentionally locate the binary by its known filename rather than
# assuming a particular ditto/unzip archive layout.
verify-mac:
@set -euo pipefail; \
test -f "$(ARM64_ARCHIVE)"; \
test -f "$(AMD64_ARCHIVE)"; \
ARM64_TEST_DIR="$$(mktemp -d /tmp/aperture-arm64.XXXXXX)"; \
AMD64_TEST_DIR="$$(mktemp -d /tmp/aperture-amd64.XXXXXX)"; \
trap 'rm -rf "$$ARM64_TEST_DIR" "$$AMD64_TEST_DIR"' EXIT; \
echo "==> Extracting and verifying arm64 artifact"; \
ditto -x -k "$(ARM64_ARCHIVE)" "$$ARM64_TEST_DIR"; \
ARM64_EXTRACTED="$$(find "$$ARM64_TEST_DIR" -type f -name "$(notdir $(ARM64_BIN))" -print -quit)"; \
if [ -z "$$ARM64_EXTRACTED" ]; then \
echo "error: $(notdir $(ARM64_BIN)) was not found in $(ARM64_ARCHIVE)"; \
find "$$ARM64_TEST_DIR" -maxdepth 4 -print; \
exit 1; \
fi; \
echo " found: $$ARM64_EXTRACTED"; \
test -x "$$ARM64_EXTRACTED"; \
codesign --verify --strict --verbose=4 "$$ARM64_EXTRACTED"; \
echo "==> Extracting and verifying amd64 artifact"; \
ditto -x -k "$(AMD64_ARCHIVE)" "$$AMD64_TEST_DIR"; \
AMD64_EXTRACTED="$$(find "$$AMD64_TEST_DIR" -type f -name "$(notdir $(AMD64_BIN))" -print -quit)"; \
if [ -z "$$AMD64_EXTRACTED" ]; then \
echo "error: $(notdir $(AMD64_BIN)) was not found in $(AMD64_ARCHIVE)"; \
find "$$AMD64_TEST_DIR" -maxdepth 4 -print; \
exit 1; \
fi; \
echo " found: $$AMD64_EXTRACTED"; \
test -x "$$AMD64_EXTRACTED"; \
codesign --verify --strict --verbose=4 "$$AMD64_EXTRACTED"; \
echo "==> Verification passed"; \
echo "==> Checksums: $(CHECKSUMS)"

# Full local macOS release flow: build, sign, archive, notarize, then verify.
release-mac-notarized: release-mac notarize-mac verify-mac
@echo "==> Complete"; \
echo " $(ARM64_ARCHIVE)"; \
echo " $(AMD64_ARCHIVE)"; \
echo " $(CHECKSUMS)"

# Compatibility alias for the earlier target spelling.
release-macos-notarized: release-mac-notarized
Loading