Skip to content

Commit 99c912b

Browse files
chore: make helper launchdaemon approval mandatory (#205)
First step in addressing #201. This PR installs and kickstarts the LaunchDaemon as part of the `.pkg` installer, instead of requiring it be approved via the UI. As such, we no longer distribute the app contained within a `.zip`. This PR adds a build script to install the LaunchDaemon when developing locally, to ensure it stays up to date when changes are made. Installing the LaunchDaemon requires administrator privileges, so to minimise password prompts, we only restart it when the binary itself, or any of it's frameworks have changed. There's an Apple Developer Forum thread where I replied, enquiring about this installer approach vs the existing SMAppService approach, esp. w.r.t deploying the app via MDM: https://developer.apple.com/forums/thread/766351?answerId=850675022&page=1#851913022 (This PR previously had UI changes, and I did some refactoring. That refactoring is still in the diff.)
1 parent ab44e4a commit 99c912b

File tree

13 files changed

+121
-248
lines changed

13 files changed

+121
-248
lines changed

Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ struct DesktopApp: App {
2626
SettingsView<CoderVPNService>()
2727
.environmentObject(appDelegate.vpn)
2828
.environmentObject(appDelegate.state)
29-
.environmentObject(appDelegate.helper)
3029
.environmentObject(appDelegate.autoUpdater)
3130
}
3231
.windowResizability(.contentSize)
@@ -48,13 +47,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
4847
let fileSyncDaemon: MutagenDaemon
4948
let urlHandler: URLHandler
5049
let notifDelegate: NotifDelegate
51-
let helper: HelperService
5250
let autoUpdater: UpdaterService
5351

5452
override init() {
5553
notifDelegate = NotifDelegate()
5654
vpn = CoderVPNService()
57-
helper = HelperService()
5855
autoUpdater = UpdaterService()
5956
let state = AppState(onChange: vpn.configureTunnelProviderProtocol)
6057
vpn.onStart = {

Coder-Desktop/Coder-Desktop/HelperService.swift

Lines changed: 0 additions & 117 deletions
This file was deleted.

Coder-Desktop/Coder-Desktop/Views/Settings/ExperimentalTab.swift

Lines changed: 0 additions & 10 deletions
This file was deleted.

Coder-Desktop/Coder-Desktop/Views/Settings/HelperSection.swift

Lines changed: 0 additions & 82 deletions
This file was deleted.

Coder-Desktop/Coder-Desktop/Views/Settings/Settings.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ struct SettingsView<VPN: VPNService>: View {
1313
.tabItem {
1414
Label("Network", systemImage: "dot.radiowaves.left.and.right")
1515
}.tag(SettingsTab.network)
16-
ExperimentalTab()
17-
.tabItem {
18-
Label("Experimental", systemImage: "gearshape.2")
19-
}.tag(SettingsTab.experimental)
20-
2116
}.frame(width: 600)
2217
.frame(maxHeight: 500)
2318
.scrollContentBackground(.hidden)
@@ -28,5 +23,4 @@ struct SettingsView<VPN: VPNService>: View {
2823
enum SettingsTab: Int {
2924
case general
3025
case network
31-
case experimental
3226
}

Coder-Desktop/Coder-Desktop/Views/VPN/VPNState.swift

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,10 @@ struct VPNState<VPN: VPNService>: View {
1010
Group {
1111
switch (vpn.state, state.hasSession) {
1212
case (.failed(.systemExtensionError(.needsUserApproval)), _):
13-
VStack {
14-
Text("Awaiting System Extension approval")
15-
.foregroundColor(.secondary)
16-
.multilineTextAlignment(.center)
17-
.fixedSize(horizontal: false, vertical: true)
18-
.padding(.horizontal, Theme.Size.trayInset)
19-
.padding(.vertical, Theme.Size.trayPadding)
20-
.frame(maxWidth: .infinity)
21-
Button {
22-
openSystemExtensionSettings()
23-
} label: {
24-
Text("Approve in System Settings")
25-
}
26-
}
13+
ApprovalRequiredView(
14+
message: "Awaiting System Extension approval",
15+
action: openSystemExtensionSettings
16+
)
2717
case (_, false):
2818
Text("Sign in to use Coder Desktop")
2919
.font(.body)
@@ -32,11 +22,7 @@ struct VPNState<VPN: VPNService>: View {
3222
VStack {
3323
Text("The system VPN requires reconfiguration")
3424
.foregroundColor(.secondary)
35-
.multilineTextAlignment(.center)
36-
.fixedSize(horizontal: false, vertical: true)
37-
.padding(.horizontal, Theme.Size.trayInset)
38-
.padding(.vertical, Theme.Size.trayPadding)
39-
.frame(maxWidth: .infinity)
25+
.vpnStateMessage()
4026
Button {
4127
state.reconfigure()
4228
} label: {
@@ -61,15 +47,46 @@ struct VPNState<VPN: VPNService>: View {
6147
Text("\(vpnErr.description)")
6248
.font(.headline)
6349
.foregroundColor(.red)
64-
.multilineTextAlignment(.center)
65-
.fixedSize(horizontal: false, vertical: true)
66-
.padding(.horizontal, Theme.Size.trayInset)
67-
.padding(.vertical, Theme.Size.trayPadding)
68-
.frame(maxWidth: .infinity)
50+
.vpnStateMessage()
6951
case (.connected, true):
7052
EmptyView()
7153
}
7254
}
7355
.onReceive(inspection.notice) { inspection.visit(self, $0) } // viewInspector
7456
}
7557
}
58+
59+
struct ApprovalRequiredView: View {
60+
let message: String
61+
let action: () -> Void
62+
63+
var body: some View {
64+
VStack {
65+
Text(message)
66+
.foregroundColor(.secondary)
67+
.vpnStateMessage()
68+
Button {
69+
action()
70+
} label: {
71+
Text("Approve in System Settings")
72+
}
73+
}
74+
}
75+
}
76+
77+
struct VPNStateMessageTextModifier: ViewModifier {
78+
func body(content: Content) -> some View {
79+
content
80+
.multilineTextAlignment(.center)
81+
.fixedSize(horizontal: false, vertical: true)
82+
.padding(.horizontal, Theme.Size.trayInset)
83+
.padding(.vertical, Theme.Size.trayPadding)
84+
.frame(maxWidth: .infinity)
85+
}
86+
}
87+
88+
extension View {
89+
func vpnStateMessage() -> some View {
90+
modifier(VPNStateMessageTextModifier())
91+
}
92+
}

Coder-Desktop/Coder-DesktopHelper/com.coder.Coder-Desktop.Helper.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<dict>
55
<key>Label</key>
66
<string>com.coder.Coder-Desktop.Helper</string>
7-
<key>BundleProgram</key>
8-
<string>Contents/MacOS/com.coder.Coder-Desktop.Helper</string>
7+
<key>Program</key>
8+
<string>/Applications/Coder Desktop.app/Contents/MacOS/com.coder.Coder-Desktop.Helper</string>
99
<key>MachServices</key>
1010
<dict>
1111
<!-- $(TeamIdentifierPrefix) isn't populated here, so this value is hardcoded -->

Coder-Desktop/project.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,29 @@ targets:
216216
buildToolPlugins:
217217
- plugin: SwiftLintBuildToolPlugin
218218
package: SwiftLintPlugins
219+
postBuildScripts:
220+
# This is a dependency of the app, not the helper, as it copies the
221+
# helper plist from the app bundle to the system store.
222+
- name: "Upsert Helper for Local Development"
223+
# Only run this script (and prompt for admin) when the helper or any of
224+
# it's frameworks have changed.
225+
inputFiles:
226+
- "$(BUILT_PRODUCTS_DIR)/com.coder.Coder-Desktop.Helper"
227+
- "$(BUILT_PRODUCTS_DIR)/CoderSDK.framework/Versions/A/CoderSDK"
228+
- "$(BUILT_PRODUCTS_DIR)/VPNLib.framework/Versions/A/VPNLib"
229+
outputFiles:
230+
- "$(DERIVED_FILE_DIR)/upsert-helper.stamp"
231+
script: |
232+
if [ -n "${CI}" ]; then
233+
# Skip in CI
234+
exit 0
235+
fi
236+
/usr/bin/osascript <<'APPLESCRIPT'
237+
do shell script "/bin/bash -c " & quoted form of ((system attribute "SRCROOT") & "/../scripts/upsert-dev-helper.sh") with administrator privileges
238+
APPLESCRIPT
239+
/usr/bin/touch "${DERIVED_FILE_DIR}/upsert-helper.stamp"
240+
basedOnDependencyAnalysis: true
241+
runOnlyWhenInstalling: false
219242

220243
Coder-DesktopTests:
221244
type: bundle.unit-test
@@ -376,4 +399,4 @@ targets:
376399
PRODUCT_BUNDLE_IDENTIFIER: "com.coder.Coder-Desktop.Helper"
377400
PRODUCT_MODULE_NAME: "$(PRODUCT_NAME:c99extidentifier)"
378401
PRODUCT_NAME: "$(PRODUCT_BUNDLE_IDENTIFIER)"
379-
SKIP_INSTALL: YES
402+
SKIP_INSTALL: YES

0 commit comments

Comments
 (0)