Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
241 lines (219 loc) · 9.07 KB
/
Copy pathMakefile
File metadata and controls
241 lines (219 loc) · 9.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
SHELL := /bin/bash
.PHONY: \
build \
test \
lint \
check \
clean \
install \
release-mac \
notarize-mac \
verify-mac \
release-mac-notarized \
release-macos-notarized \
upload-mac
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
GIT_VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
GIT_DESC := $(shell git describe --always)
ifneq ($(shell git status --porcelain),)
GIT_DESC := $(GIT_DESC)-dirty
endif
# Metadata for ordinary development builds.
LDFLAGS := \
-X main.buildVersion=$(GIT_VERSION) \
-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:
go test ./...
# Nothing here needs installing: gofmt and vet ship with the toolchain, so a
# clean checkout can run this.
lint:
@out=$$(gofmt -l .); if [ -n "$$out" ]; then echo "gofmt:"; echo "$$out"; exit 1; fi
go vet ./...
go mod tidy -diff
# The gate. Differs from test in the two ways that matter here: it runs the
# suite under the race detector, and it builds. A bridge is several goroutines
# racing a control plane, so a data race is the failure this project actually
# has, and `go test` will not find one.
check: lint build
go test -race ./...
# The black-box suites: cross-version compatibility against the latest
# release, and golden output for the scripted surface. Tagged out of check
# because they download the release binary and, with TS_AUTHKEY set, log a
# bridge into the local tailnet. Run them on a dev box, not in CI.
test-tailnet:
go test -race -tags tailnet ./e2e/
install:
go install -ldflags "$(LDFLAGS)" ./cmd/aperture
clean:
rm -rf .build/
# Build and sign the final architecture-specific macOS executables. Nothing
# must modify these files after this target finishes.
release-mac:
@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 --norsrc --noextattr "$(ARM64_BIN)" "$(ARM64_ARCHIVE)"; \
ditto -c -k --keepParent --norsrc --noextattr "$(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; \
echo "==> Writing checksums"; \
(cd "$(RELEASE_DIR)" && shasum -a 256 \
"$(notdir $(ARM64_ARCHIVE))" \
"$(notdir $(AMD64_ARCHIVE))" \
> "$(notdir $(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)"; \
echo " next, once the tag's goreleaser run has published:"; \
echo " make upload-mac VERSION=$(VERSION)"
# Replace the unsigned darwin assets on the GitHub release for VERSION with
# the signed, notarized archives, and rewrite checksums.txt to match: the
# goreleaser assets are named aperture_darwin_<arch>.tar.gz while the signed
# ones are zips, so an upload alone would leave both on the release and the
# checksums pointing at the unsigned tarballs. Run only after the tag's
# goreleaser workflow has published. -R is explicit so this works from a
# checkout whose origin is not github.com.
upload-mac:
@set -euo pipefail; \
test -f "$(ARM64_ARCHIVE)"; \
test -f "$(AMD64_ARCHIVE)"; \
test -f "$(CHECKSUMS)"; \
REPO=tailscale/aperture-cli; \
echo "==> Deleting unsigned darwin assets from $(VERSION)"; \
while read -r asset; do \
case "$$asset" in \
*darwin*) \
echo " delete: $$asset"; \
gh release delete-asset "$(VERSION)" "$$asset" -R "$$REPO" --yes ;; \
esac; \
done < <(gh release view "$(VERSION)" -R "$$REPO" --json assets -q '.assets[].name'); \
echo "==> Uploading signed archives"; \
gh release upload "$(VERSION)" -R "$$REPO" \
"$(ARM64_ARCHIVE)" "$(AMD64_ARCHIVE)"; \
echo "==> Rewriting checksums.txt"; \
WORK="$$(mktemp -d /tmp/aperture-checksums.XXXXXX)"; \
trap 'rm -rf "$$WORK"' EXIT; \
gh release download "$(VERSION)" -R "$$REPO" \
--pattern checksums.txt --dir "$$WORK" --clobber; \
grep -v darwin "$$WORK/checksums.txt" > "$$WORK/merged" || [ $$? -eq 1 ]; \
cat "$(CHECKSUMS)" >> "$$WORK/merged"; \
mv "$$WORK/merged" "$$WORK/checksums.txt"; \
gh release upload "$(VERSION)" -R "$$REPO" "$$WORK/checksums.txt" --clobber; \
echo "==> Release $(VERSION) now carries the signed darwin builds"
# Compatibility alias for the earlier target spelling.
release-macos-notarized: release-mac-notarized