Skip to content

Commit 739649b

Browse files
committed
chore(ci): build for distribution
1 parent 1413f5a commit 739649b

File tree

10 files changed

+209
-35
lines changed

10 files changed

+209
-35
lines changed

.github/workflows/ci.yml

+3-15
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,8 @@ jobs:
1717
test:
1818
name: test
1919
runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest'}}
20+
if: false
2021
steps:
21-
- name: Harden Runner
22-
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
23-
with:
24-
egress-policy: audit
25-
2622
- name: Checkout
2723
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
2824
with:
@@ -43,12 +39,8 @@ jobs:
4339
format:
4440
name: fmt
4541
runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest'}}
42+
if: false
4643
steps:
47-
- name: Harden Runner
48-
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
49-
with:
50-
egress-policy: audit
51-
5244
- name: Checkout
5345
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
5446
with:
@@ -69,12 +61,8 @@ jobs:
6961
lint:
7062
name: lint
7163
runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest'}}
64+
if: false
7265
steps:
73-
- name: Harden Runner
74-
uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
75-
with:
76-
egress-policy: audit
77-
7866
- name: Checkout
7967
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
8068
with:

.github/workflows/release.yml

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: release
2+
3+
on:
4+
# TODO: Switch to on `v*` tag push
5+
pull_request:
6+
7+
# permissions:
8+
# # To upload assets to the release
9+
# contents: write
10+
11+
jobs:
12+
build:
13+
runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest'}}
14+
if: ${{ github.repository_owner == 'coder' }}
15+
env:
16+
CERT_PATH: /tmp/apple_cert.p12
17+
APP_PROF_PATH: /tmp/app.provisionprofile
18+
EXT_PROF_PATH: /tmp/ext.provisionprofile
19+
KEYCHAIN_PATH: /tmp/app-signing.keychain-db
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
23+
with:
24+
fetch-depth: 1
25+
26+
- name: Switch XCode Version
27+
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
28+
with:
29+
xcode-version: "16.0.0"
30+
31+
- name: Install Cert & Retrieve Provisioning Profiles
32+
env:
33+
APPLE_CERT: ${{ secrets.APPLE_DEVELOPER_ID_PKCS12_B64 }}
34+
CERT_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_PKCS12_PASSWORD }}
35+
APP_PROF: ${{ secrets.CODER_DESKTOP_APP_PROVISIONPROFILE_B64 }}
36+
EXT_PROF: ${{ secrets.CODER_DESKTOP_EXTENSION_PROVISIONPROFILE_B64 }}
37+
run: |
38+
set -euo pipefail
39+
touch "$CERT_PATH" "$APP_PROF_PATH" "$EXT_PROF_PATH"
40+
echo "$APPLE_CERT" | base64 -d > "$CERT_PATH"
41+
echo "$APP_PROF" | base64 -d > "$APP_PROF_PATH"
42+
echo "$EXT_PROF" | base64 -d > "$EXT_PROF_PATH"
43+
set -x
44+
security create-keychain -p "" "$KEYCHAIN_PATH"
45+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
46+
security unlock-keychain -p "" "$KEYCHAIN_PATH"
47+
security import "$CERT_PATH" -P "$CERT_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
48+
security list-keychain -d user -s "$KEYCHAIN_PATH"
49+
50+
- name: Setup Deps
51+
run: |
52+
brew install xcodegen
53+
npm install --global create-dmg
54+
55+
- name: Build
56+
env:
57+
APPLE_ID: ${{ secrets.APPLE_NOTARYTOOL_USERNAME }}
58+
APPLE_ID_PASSWORD: ${{ secrets.APPLE_NOTARYTOOL_PASSWORD }}
59+
run: |
60+
./scripts/build.sh
61+
62+
- name: Upload Build Artifacts
63+
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
64+
with:
65+
name: app
66+
path: |
67+
./build
68+
retention-days: 7
69+
70+
- name: Clean Up
71+
if: always()
72+
run: |
73+
security delete-keychain "$KEYCHAIN_PATH"
74+
rm -f /tmp/{apple_cert.p12,app.provisionprofile,ext.provisionprofile,app-signing.keychain-db}

Coder Desktop/Coder Desktop/Views/LoginForm.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,9 @@ enum LoginField: Hashable {
194194
case sessionToken
195195
}
196196

197-
#Preview {
198-
LoginForm<PreviewSession>()
199-
.environmentObject(PreviewSession())
200-
}
197+
#if DEBUG
198+
#Preview {
199+
LoginForm<PreviewSession>()
200+
.environmentObject(PreviewSession())
201+
}
202+
#endif

Coder Desktop/Coder Desktop/Views/Settings/NetworkTab.swift

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ struct NetworkTab<VPN: VPNService>: View {
99
}
1010
}
1111

12-
#Preview {
13-
NetworkTab<PreviewVPN>()
14-
}
12+
#if DEBUG
13+
#Preview {
14+
NetworkTab<PreviewVPN>()
15+
}
16+
#endif

Coder Desktop/Coder Desktop/Views/VPNMenu.swift

+7-5
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ struct VPNMenu<VPN: VPNService, S: Session>: View {
8888
}
8989
}
9090

91-
#Preview {
92-
VPNMenu<PreviewVPN, PreviewSession>().frame(width: 256)
93-
.environmentObject(PreviewVPN())
94-
.environmentObject(PreviewSession())
95-
}
91+
#if DEBUG
92+
#Preview {
93+
VPNMenu<PreviewVPN, PreviewSession>().frame(width: 256)
94+
.environmentObject(PreviewVPN())
95+
.environmentObject(PreviewSession())
96+
}
97+
#endif

Coder Desktop/VPN/Manager.swift

+15-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ actor Manager {
1111
let speaker: Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>
1212
var readLoop: Task<Void, any Error>!
1313

14-
private let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
15-
.first!.appending(path: "coder-vpn.dylib")
14+
private let frameworksDir = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)
15+
.first!.appendingPathComponent("Frameworks")
16+
private var dest = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)
17+
.first!.appendingPathComponent("Frameworks").appending(path: "coder-vpn.dylib")
1618
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "manager")
1719

1820
// swiftlint:disable:next function_body_length
@@ -26,6 +28,16 @@ actor Manager {
2628
#else
2729
fatalError("unknown architecture")
2830
#endif
31+
do {
32+
if !FileManager.default.fileExists(atPath: frameworksDir.path) {
33+
try FileManager.default.createDirectory(
34+
atPath: frameworksDir.path,
35+
withIntermediateDirectories: true,
36+
attributes: [:])
37+
}
38+
} catch {
39+
throw .download(.fileOpError(error))
40+
}
2941
do {
3042
try await download(src: dylibPath, dest: dest)
3143
} catch {
@@ -49,6 +61,7 @@ actor Manager {
4961
do {
5062
try tunnelHandle = TunnelHandle(dylibPath: dest)
5163
} catch {
64+
logger.error("couldn't open dylib \(error, privacy: .public)")
5265
throw .tunnelSetup(error)
5366
}
5467
speaker = await Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>(

Coder Desktop/VPN/VPN.entitlements

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<array>
1313
<string>$(TeamIdentifierPrefix)com.coder.Coder-Desktop</string>
1414
</array>
15+
<key>com.apple.security.cs.disable-library-validation</key>
16+
<true/>
1517
<key>com.apple.security.network.client</key>
1618
<true/>
1719
<key>com.apple.security.network.server</key>

Coder Desktop/project.yml

+14-5
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ targets:
114114
path: Coder Desktop/Coder_Desktop.entitlements
115115
properties:
116116
com.apple.developer.networking.networkextension:
117-
- packet-tunnel-provider
117+
- packet-tunnel-provider${PTP_SUFFIX}
118118
com.apple.developer.system-extension.install: true
119119
com.apple.security.app-sandbox: true
120120
com.apple.security.files.user-selected.read-only: true
@@ -125,6 +125,7 @@ targets:
125125
base:
126126
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon # Sets the app icon to "AppIcon".
127127
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
128+
# `CODE_SIGN_*` options are overriden during a release build
128129
CODE_SIGN_IDENTITY: "Apple Development"
129130
CODE_SIGN_STYLE: Automatic
130131
COMBINE_HIDPI_IMAGES: YES
@@ -135,6 +136,8 @@ targets:
135136
INFOPLIST_KEY_NSHumanReadableCopyright: ""
136137
SWIFT_EMIT_LOC_STRINGS: YES
137138
PRODUCT_BUNDLE_IDENTIFIER: "com.coder.Coder-Desktop"
139+
# Empty outside of release builds
140+
PROVISIONING_PROFILE_SPECIFIER: ${APP_PROVISIONING_PROFILE_ID}
138141

139142
# (ThomasK33): Install the application into the /Applications folder
140143
# so that macOS stops complaining about the app being run from an
@@ -200,12 +203,15 @@ targets:
200203
path: VPN/VPN.entitlements
201204
properties:
202205
com.apple.developer.networking.networkextension:
203-
- packet-tunnel-provider
206+
# PTP_SUFFIX is populated at `xcodegen` time.
207+
- packet-tunnel-provider${PTP_SUFFIX}
204208
com.apple.security.app-sandbox: true
205209
com.apple.security.application-groups:
206210
- $(TeamIdentifierPrefix)com.coder.Coder-Desktop
207211
com.apple.security.network.client: true
208212
com.apple.security.network.server: true
213+
# TODO: Remove this
214+
com.apple.security.cs.disable-library-validation: true
209215
settings:
210216
base:
211217
ENABLE_HARDENED_RUNTIME: YES
@@ -215,6 +221,11 @@ targets:
215221
PRODUCT_NAME: "$(PRODUCT_BUNDLE_IDENTIFIER)"
216222
SWIFT_EMIT_LOC_STRINGS: YES
217223
SWIFT_OBJC_BRIDGING_HEADER: "VPN/com_coder_Coder_Desktop_VPN-Bridging-Header.h"
224+
# `CODE_SIGN_*` are overriden during a release build
225+
CODE_SIGN_IDENTITY: "Apple Development"
226+
CODE_SIGN_STYLE: Automatic
227+
# Empty outside of release builds
228+
PROVISIONING_PROFILE_SPECIFIER: ${EXT_PROVISIONING_PROFILE_ID}
218229
dependencies:
219230
- target: VPNLib
220231
embed: true
@@ -235,8 +246,6 @@ targets:
235246
DYLIB_COMPATIBILITY_VERSION: 1
236247
DYLIB_CURRENT_VERSION: 1
237248
DYLIB_INSTALL_NAME_BASE: "@rpath"
238-
CODE_SIGN_IDENTITY: "Apple Development"
239-
CODE_SIGN_STYLE: Automatic
240249
LD_RUNPATH_SEARCH_PATHS:
241250
- "@executable_path/../Frameworks"
242251
- "@loader_path/Frameworks"
@@ -297,4 +306,4 @@ targets:
297306
settings:
298307
base:
299308
TEST_HOST: "$(BUILT_PRODUCTS_DIR)/Coder Desktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Coder Desktop"
300-
PRODUCT_BUNDLE_IDENTIFIER: com.coder.Coder-Desktop.CoderSDKTests
309+
PRODUCT_BUNDLE_IDENTIFIER: com.coder.Coder-Desktop.CoderSDKTests

Makefile

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ setup: \
1818

1919
$(XCPROJECT): $(PROJECT)/project.yml
2020
cd $(PROJECT); \
21-
SWIFT_VERSION=$(SWIFT_VERSION) xcodegen
21+
SWIFT_VERSION=$(SWIFT_VERSION) \
22+
PTP_SUFFIX=${PTP_SUFFIX} \
23+
APP_PROVISIONING_PROFILE_ID=${APP_PROVISIONING_PROFILE_ID} \
24+
EXT_PROVISIONING_PROFILE_ID=${EXT_PROVISIONING_PROFILE_ID} \
25+
xcodegen
2226

2327
$(PROJECT)/VPNLib/vpn.pb.swift: $(PROJECT)/VPNLib/vpn.proto
2428
protoc --swift_opt=Visibility=public --swift_out=. 'Coder Desktop/VPNLib/vpn.proto'

scripts/build.sh

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env bash
2+
3+
set -euxo pipefail
4+
5+
get_uuid() {
6+
strings "$1" | grep -E -o '[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}'
7+
}
8+
9+
# Build Documentation @ https://developer.apple.com/forums/thread/737894
10+
11+
XCODE_PROVISIONING_PROFILES_DIR="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
12+
ALT_PROVISIONING_PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
13+
mkdir -p "$XCODE_PROVISIONING_PROFILES_DIR"
14+
mkdir -p "$ALT_PROVISIONING_PROFILES_DIR"
15+
APPLE_TEAM_ID="4399GN35BJ"
16+
CODE_SIGN_IDENTITY="Developer ID Application: Coder Technologies Inc (${APPLE_TEAM_ID})"
17+
18+
# Extract the ID of each provisioning profile
19+
APP_PROVISIONING_PROFILE_ID=$(get_uuid "$APP_PROF_PATH")
20+
EXT_PROVISIONING_PROFILE_ID=$(get_uuid "$EXT_PROF_PATH")
21+
PTP_SUFFIX="-systemextension"
22+
23+
# Install Provisioning Profiles
24+
cp "$APP_PROF_PATH" "${XCODE_PROVISIONING_PROFILES_DIR}/${APP_PROVISIONING_PROFILE_ID}.provisionprofile"
25+
cp "$APP_PROF_PATH" "${ALT_PROVISIONING_PROFILES_DIR}/${APP_PROVISIONING_PROFILE_ID}.provisionprofile"
26+
cp "$EXT_PROF_PATH" "${XCODE_PROVISIONING_PROFILES_DIR}/${EXT_PROVISIONING_PROFILE_ID}.provisionprofile"
27+
cp "$EXT_PROF_PATH" "${ALT_PROVISIONING_PROFILES_DIR}/${EXT_PROVISIONING_PROFILE_ID}.provisionprofile"
28+
29+
export APP_PROVISIONING_PROFILE_ID
30+
export EXT_PROVISIONING_PROFILE_ID
31+
export PTP_SUFFIX
32+
make
33+
xcodebuild \
34+
-project "Coder Desktop/Coder Desktop.xcodeproj" \
35+
-scheme "Coder Desktop" \
36+
-configuration "Release" \
37+
-skipPackagePluginValidation \
38+
CODE_SIGN_STYLE=Manual \
39+
CODE_SIGN_IDENTITY="$CODE_SIGN_IDENTITY" \
40+
CODE_SIGN_INJECT_BASE_ENTITLEMENTS=NO \
41+
OTHER_CODE_SIGN_FLAGS='--timestamp' | LC_ALL="en_US.UTF-8" xcpretty
42+
43+
mkdir build
44+
built_app_path="./Coder Desktop.app"
45+
ditto "$(find "$HOME/Library/Developer/Xcode/DerivedData" -name "Coder Desktop.app")" "$built_app_path"
46+
47+
create-dmg \
48+
--identity="$CODE_SIGN_IDENTITY" \
49+
"$built_app_path" \
50+
./
51+
52+
# Add dmg to build artifacts
53+
dmg_path="./build/Coder Desktop.dmg"
54+
mv ./Coder\ Desktop*.dmg "$dmg_path"
55+
56+
# Notarize
57+
xcrun notarytool store-credentials "notarytool-credentials" \
58+
--apple-id "$APPLE_ID" \
59+
--team-id "$APPLE_TEAM_ID" \
60+
--password "$APPLE_ID_PASSWORD" \
61+
--keychain "$KEYCHAIN_PATH"
62+
63+
xcrun notarytool submit "$dmg_path" \
64+
--keychain-profile "notarytool-credentials" \
65+
--keychain "$KEYCHAIN_PATH" \
66+
--wait
67+
68+
# Staple the notarization to the app and dmg, so they work without internet
69+
xcrun stapler staple "$dmg_path"
70+
xcrun stapler staple "$built_app_path"
71+
72+
# Add dsym to build artifacts
73+
dsym_zipped_path="./build/coder-desktop-universal-dsym.zip"
74+
zip -9 -r --symlinks "$dsym_zipped_path" "$(find "$HOME/Library/Developer/Xcode/DerivedData" -name "Coder Desktop.app.dSYM")"
75+
76+
# Add zipped app to build artifacts
77+
app_zipped_path="./build/coder-desktop-universal.zip"
78+
zip -9 -r --symlinks "$app_zipped_path" "$built_app_path"

0 commit comments

Comments
 (0)