Skip to content

Commit b716554

Browse files
committed
chore: use slim binary over dylib
1 parent c450bd4 commit b716554

File tree

15 files changed

+530
-246
lines changed

15 files changed

+530
-246
lines changed

Coder-Desktop/.swiftlint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ type_name:
1010
identifier_name:
1111
allowed_symbols: "_"
1212
min_length: 1
13+
line_length:
14+
ignores_urls: true

Coder-Desktop/Coder-Desktop/Views/LoginForm.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ enum LoginError: Error {
222222
case .outdatedCoderVersion:
223223
"""
224224
The Coder deployment must be version \(Validator.minimumCoderVersion)
225-
or higher to use Coder Desktop.
225+
or higher to use this version of Coder Desktop.
226226
"""
227227
case let .failedAuth(err):
228228
"Could not authenticate with Coder deployment:\n\(err.localizedDescription)"

Coder-Desktop/Coder-DesktopHelper/Manager.swift

Lines changed: 90 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,56 @@ actor Manager {
77
let cfg: ManagerConfig
88
let telemetryEnricher: TelemetryEnricher
99

10-
let tunnelHandle: TunnelHandle
10+
let tunnelDaemon: TunnelDaemon
1111
let speaker: Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>
1212
var readLoop: Task<Void, any Error>!
1313

14-
// /var/root/Downloads
15-
private let dest = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)
16-
.first!.appending(path: "coder-vpn.dylib")
14+
#if arch(arm64)
15+
private static let binaryName = "coder-darwin-arm64"
16+
#else
17+
private static let binaryName = "coder-darwin-amd64"
18+
#endif
19+
20+
// /var/root/Library/Application Support/com.coder.Coder-Desktop/coder-darwin-{arm64,amd64}
21+
private let dest = try? FileManager.default
22+
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
23+
.appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.coder.Coder-Desktop", isDirectory: true)
24+
.appendingPathComponent(binaryName)
25+
1726
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "manager")
1827

1928
// swiftlint:disable:next function_body_length
2029
init(cfg: ManagerConfig) async throws(ManagerError) {
2130
self.cfg = cfg
2231
telemetryEnricher = TelemetryEnricher()
23-
#if arch(arm64)
24-
let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-darwin-arm64.dylib")
25-
#elseif arch(x86_64)
26-
let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-darwin-amd64.dylib")
27-
#else
28-
fatalError("unknown architecture")
29-
#endif
32+
guard let dest else {
33+
// This should never happen
34+
throw .fileError("Failed to create path for binary destination" +
35+
"(/var/root/Library/Application Support/com.coder.Coder-Desktop)")
36+
}
37+
do {
38+
try FileManager.default.ensureDirectories(for: dest)
39+
} catch {
40+
throw .fileError(
41+
"Failed to create directories for binary destination (\(dest)): \(error.localizedDescription)"
42+
)
43+
}
44+
let client = Client(url: cfg.serverUrl)
45+
let buildInfo: BuildInfoResponse
46+
do {
47+
buildInfo = try await client.buildInfo()
48+
} catch {
49+
throw .serverInfo(error.description)
50+
}
51+
guard let serverSemver = buildInfo.semver else {
52+
throw .serverInfo("invalid version: \(buildInfo.version)")
53+
}
54+
guard Validator.minimumCoderVersion
55+
.compare(serverSemver, options: .numeric) != .orderedDescending
56+
else {
57+
throw .belowMinimumCoderVersion(actualVersion: serverSemver)
58+
}
59+
let binaryPath = cfg.serverUrl.appending(path: "bin").appending(path: Manager.binaryName)
3060
do {
3161
let sessionConfig = URLSessionConfiguration.default
3262
// The tunnel might be asked to start before the network interfaces have woken up from sleep
@@ -35,7 +65,7 @@ actor Manager {
3565
sessionConfig.timeoutIntervalForRequest = 60
3666
sessionConfig.timeoutIntervalForResource = 300
3767
try await download(
38-
src: dylibPath,
68+
src: binaryPath,
3969
dest: dest,
4070
urlSession: URLSession(configuration: sessionConfig)
4171
) { progress in
@@ -45,48 +75,46 @@ actor Manager {
4575
throw .download(error)
4676
}
4777
pushProgress(stage: .validating)
48-
let client = Client(url: cfg.serverUrl)
49-
let buildInfo: BuildInfoResponse
5078
do {
51-
buildInfo = try await client.buildInfo()
79+
try Validator.validate(path: dest)
5280
} catch {
53-
throw .serverInfo(error.description)
54-
}
55-
guard let semver = buildInfo.semver else {
56-
throw .serverInfo("invalid version: \(buildInfo.version)")
81+
// Cleanup unvalid binary
82+
try? FileManager.default.removeItem(at: dest)
83+
throw .validation(error)
5784
}
85+
86+
// Without this, the TUN fd isn't recognised as a socket in the
87+
// spawned process, and the tunnel fails to start.
5888
do {
59-
try Validator.validate(path: dest, expectedVersion: semver)
89+
try unsetCloseOnExec(fd: cfg.tunFd)
6090
} catch {
61-
throw .validation(error)
91+
throw .cloexec(error)
6292
}
6393

6494
do {
65-
try tunnelHandle = TunnelHandle(dylibPath: dest)
95+
try tunnelDaemon = await TunnelDaemon(binaryPath: dest) { err in
96+
Task { try? await NEXPCServerDelegate.cancelProvider(error:
97+
makeNSError(suffix: "TunnelDaemon", desc: "Tunnel daemon: \(err.description)")
98+
) }
99+
}
66100
} catch {
67101
throw .tunnelSetup(error)
68102
}
69103
speaker = await Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>(
70-
writeFD: tunnelHandle.writeHandle,
71-
readFD: tunnelHandle.readHandle
104+
writeFD: tunnelDaemon.writeHandle,
105+
readFD: tunnelDaemon.readHandle
72106
)
73107
do {
74108
try await speaker.handshake()
75109
} catch {
76110
throw .handshake(error)
77111
}
78-
do {
79-
try await tunnelHandle.openTunnelTask?.value
80-
} catch let error as TunnelHandleError {
81-
logger.error("failed to wait for dylib to open tunnel: \(error, privacy: .public) ")
82-
throw .tunnelSetup(error)
83-
} catch {
84-
fatalError("openTunnelTask must only throw TunnelHandleError")
85-
}
86112

87113
readLoop = Task { try await run() }
88114
}
89115

116+
deinit { logger.debug("manager deinit") }
117+
90118
func run() async throws {
91119
do {
92120
for try await m in speaker {
@@ -99,14 +127,14 @@ actor Manager {
99127
}
100128
} catch {
101129
logger.error("tunnel read loop failed: \(error.localizedDescription, privacy: .public)")
102-
try await tunnelHandle.close()
130+
try await tunnelDaemon.close()
103131
try await NEXPCServerDelegate.cancelProvider(error:
104132
makeNSError(suffix: "Manager", desc: "Tunnel read loop failed: \(error.localizedDescription)")
105133
)
106134
return
107135
}
108136
logger.info("tunnel read loop exited")
109-
try await tunnelHandle.close()
137+
try await tunnelDaemon.close()
110138
try await NEXPCServerDelegate.cancelProvider(error: nil)
111139
}
112140

@@ -204,6 +232,12 @@ actor Manager {
204232
if !stopResp.success {
205233
throw .errorResponse(msg: stopResp.errorMessage)
206234
}
235+
do {
236+
try await tunnelDaemon.close()
237+
} catch {
238+
throw .tunnelFail(error)
239+
}
240+
readLoop.cancel()
207241
}
208242

209243
// Retrieves the current state of all peers,
@@ -239,28 +273,32 @@ struct ManagerConfig {
239273

240274
enum ManagerError: Error {
241275
case download(DownloadError)
242-
case tunnelSetup(TunnelHandleError)
276+
case fileError(String)
277+
case tunnelSetup(TunnelDaemonError)
243278
case handshake(HandshakeError)
244279
case validation(ValidationError)
245280
case incorrectResponse(Vpn_TunnelMessage)
281+
case cloexec(POSIXError)
246282
case failedRPC(any Error)
247283
case serverInfo(String)
248284
case errorResponse(msg: String)
249-
case noTunnelFileDescriptor
250-
case noApp
251-
case permissionDenied
252285
case tunnelFail(any Error)
286+
case belowMinimumCoderVersion(actualVersion: String)
253287

254288
var description: String {
255289
switch self {
256290
case let .download(err):
257291
"Download error: \(err.localizedDescription)"
292+
case let .fileError(msg):
293+
msg
258294
case let .tunnelSetup(err):
259295
"Tunnel setup error: \(err.localizedDescription)"
260296
case let .handshake(err):
261297
"Handshake error: \(err.localizedDescription)"
262298
case let .validation(err):
263299
"Validation error: \(err.localizedDescription)"
300+
case let .cloexec(err):
301+
"Failed to mark TUN fd as non-cloexec: \(err.localizedDescription)"
264302
case .incorrectResponse:
265303
"Received unexpected response over tunnel"
266304
case let .failedRPC(err):
@@ -269,14 +307,13 @@ enum ManagerError: Error {
269307
msg
270308
case let .errorResponse(msg):
271309
msg
272-
case .noTunnelFileDescriptor:
273-
"Could not find a tunnel file descriptor"
274-
case .noApp:
275-
"The VPN must be started with the app open during first-time setup."
276-
case .permissionDenied:
277-
"Permission was not granted to execute the CoderVPN dylib"
278310
case let .tunnelFail(err):
279-
"Failed to communicate with dylib over tunnel: \(err.localizedDescription)"
311+
"Failed to communicate with daemon over tunnel: \(err.localizedDescription)"
312+
case let .belowMinimumCoderVersion(actualVersion):
313+
"""
314+
The Coder deployment must be version \(Validator.minimumCoderVersion)
315+
or higher to use Coder Desktop. Current version: \(actualVersion)
316+
"""
280317
}
281318
}
282319

@@ -297,9 +334,16 @@ func writeVpnLog(_ log: Vpn_Log) {
297334
case .UNRECOGNIZED: .info
298335
}
299336
let logger = Logger(
300-
subsystem: "\(Bundle.main.bundleIdentifier!).dylib",
337+
subsystem: "\(Bundle.main.bundleIdentifier!).daemon",
301338
category: log.loggerNames.joined(separator: ".")
302339
)
303340
let fields = log.fields.map { "\($0.name): \($0.value)" }.joined(separator: ", ")
304341
logger.log(level: level, "\(log.message, privacy: .public)\(fields.isEmpty ? "" : ": \(fields)", privacy: .public)")
305342
}
343+
344+
extension FileManager {
345+
func ensureDirectories(for url: URL) throws {
346+
let dir = url.hasDirectoryPath ? url : url.deletingLastPathComponent()
347+
try createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil)
348+
}
349+
}

Coder-Desktop/Coder-DesktopHelper/TunnelHandle.swift

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

Coder-Desktop/Coder-DesktopHelper/com_coder_Coder_Desktop_VPN-Bridging-Header.h

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

Coder-Desktop/Coder-DesktopTests/LoginFormTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ struct LoginTests {
134134
username: "admin"
135135
)
136136
let buildInfo = BuildInfoResponse(
137-
version: "v2.20.0"
137+
version: "v2.24.2"
138138
)
139139

140140
try Mock(

0 commit comments

Comments
 (0)