Skip to content

Commit 42b8f0b

Browse files
committed
chore: use slim binary over dylib
1 parent a5d5337 commit 42b8f0b

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy