Skip to content

Commit e9e15db

Browse files
committed
chore: use slim binary over dylib
1 parent ef370db commit e9e15db

File tree

10 files changed

+497
-219
lines changed

10 files changed

+497
-219
lines changed

Coder-Desktop/Coder-DesktopHelper/Manager.swift

Lines changed: 86 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,54 @@ 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("Failed to create directories for binary destination: \(error.localizedDescription)")
41+
}
42+
let client = Client(url: cfg.serverUrl)
43+
let buildInfo: BuildInfoResponse
44+
do {
45+
buildInfo = try await client.buildInfo()
46+
} catch {
47+
throw .serverInfo(error.description)
48+
}
49+
guard let serverSemver = buildInfo.semver else {
50+
throw .serverInfo("invalid version: \(buildInfo.version)")
51+
}
52+
guard Validator.minimumCoderVersion
53+
.compare(serverSemver, options: .numeric) != .orderedDescending
54+
else {
55+
throw .belowMinimumCoderVersion(actualVersion: serverSemver)
56+
}
57+
let binaryPath = cfg.serverUrl.appending(path: "bin").appending(path: Manager.binaryName)
3058
do {
3159
let sessionConfig = URLSessionConfiguration.default
3260
// The tunnel might be asked to start before the network interfaces have woken up from sleep
@@ -35,7 +63,7 @@ actor Manager {
3563
sessionConfig.timeoutIntervalForRequest = 60
3664
sessionConfig.timeoutIntervalForResource = 300
3765
try await download(
38-
src: dylibPath,
66+
src: binaryPath,
3967
dest: dest,
4068
urlSession: URLSession(configuration: sessionConfig)
4169
) { progress in
@@ -45,48 +73,44 @@ actor Manager {
4573
throw .download(error)
4674
}
4775
pushProgress(stage: .validating)
48-
let client = Client(url: cfg.serverUrl)
49-
let buildInfo: BuildInfoResponse
5076
do {
51-
buildInfo = try await client.buildInfo()
77+
try Validator.validate(path: dest)
5278
} catch {
53-
throw .serverInfo(error.description)
54-
}
55-
guard let semver = buildInfo.semver else {
56-
throw .serverInfo("invalid version: \(buildInfo.version)")
79+
throw .validation(error)
5780
}
81+
82+
// Without this, the TUN fd isn't recognised as a socket in the
83+
// spawned process, and the tunnel fails to start.
5884
do {
59-
try Validator.validate(path: dest, expectedVersion: semver)
85+
try unsetCloseOnExec(fd: cfg.tunFd)
6086
} catch {
61-
throw .validation(error)
87+
throw .cloexec(error)
6288
}
6389

6490
do {
65-
try tunnelHandle = TunnelHandle(dylibPath: dest)
91+
try tunnelDaemon = await TunnelDaemon(binaryPath: dest) { err in
92+
Task { try? await NEXPCServerDelegate.cancelProvider(error:
93+
makeNSError(suffix: "TunnelDaemon", desc: "Tunnel daemon: \(err.description)")
94+
) }
95+
}
6696
} catch {
6797
throw .tunnelSetup(error)
6898
}
6999
speaker = await Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>(
70-
writeFD: tunnelHandle.writeHandle,
71-
readFD: tunnelHandle.readHandle
100+
writeFD: tunnelDaemon.writeHandle,
101+
readFD: tunnelDaemon.readHandle
72102
)
73103
do {
74104
try await speaker.handshake()
75105
} catch {
76106
throw .handshake(error)
77107
}
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-
}
86108

87109
readLoop = Task { try await run() }
88110
}
89111

112+
deinit { logger.debug("manager deinit") }
113+
90114
func run() async throws {
91115
do {
92116
for try await m in speaker {
@@ -99,14 +123,14 @@ actor Manager {
99123
}
100124
} catch {
101125
logger.error("tunnel read loop failed: \(error.localizedDescription, privacy: .public)")
102-
try await tunnelHandle.close()
126+
try await tunnelDaemon.close()
103127
try await NEXPCServerDelegate.cancelProvider(error:
104128
makeNSError(suffix: "Manager", desc: "Tunnel read loop failed: \(error.localizedDescription)")
105129
)
106130
return
107131
}
108132
logger.info("tunnel read loop exited")
109-
try await tunnelHandle.close()
133+
try await tunnelDaemon.close()
110134
try await NEXPCServerDelegate.cancelProvider(error: nil)
111135
}
112136

@@ -204,6 +228,12 @@ actor Manager {
204228
if !stopResp.success {
205229
throw .errorResponse(msg: stopResp.errorMessage)
206230
}
231+
do {
232+
try await tunnelDaemon.close()
233+
} catch {
234+
throw .tunnelFail(error)
235+
}
236+
readLoop.cancel()
207237
}
208238

209239
// Retrieves the current state of all peers,
@@ -239,28 +269,32 @@ struct ManagerConfig {
239269

240270
enum ManagerError: Error {
241271
case download(DownloadError)
242-
case tunnelSetup(TunnelHandleError)
272+
case fileError(String)
273+
case tunnelSetup(TunnelDaemonError)
243274
case handshake(HandshakeError)
244275
case validation(ValidationError)
245276
case incorrectResponse(Vpn_TunnelMessage)
277+
case cloexec(POSIXError)
246278
case failedRPC(any Error)
247279
case serverInfo(String)
248280
case errorResponse(msg: String)
249-
case noTunnelFileDescriptor
250-
case noApp
251-
case permissionDenied
252281
case tunnelFail(any Error)
282+
case belowMinimumCoderVersion(actualVersion: String)
253283

254284
var description: String {
255285
switch self {
256286
case let .download(err):
257287
"Download error: \(err.localizedDescription)"
288+
case let .fileError(msg):
289+
msg
258290
case let .tunnelSetup(err):
259291
"Tunnel setup error: \(err.localizedDescription)"
260292
case let .handshake(err):
261293
"Handshake error: \(err.localizedDescription)"
262294
case let .validation(err):
263295
"Validation error: \(err.localizedDescription)"
296+
case let .cloexec(err):
297+
"Failed to mark TUN fd as non-cloexec: \(err.localizedDescription)"
264298
case .incorrectResponse:
265299
"Received unexpected response over tunnel"
266300
case let .failedRPC(err):
@@ -269,14 +303,13 @@ enum ManagerError: Error {
269303
msg
270304
case let .errorResponse(msg):
271305
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"
278306
case let .tunnelFail(err):
279-
"Failed to communicate with dylib over tunnel: \(err.localizedDescription)"
307+
"Failed to communicate with daemon over tunnel: \(err.localizedDescription)"
308+
case let .belowMinimumCoderVersion(actualVersion):
309+
"""
310+
The Coder deployment must be version \(Validator.minimumCoderVersion)
311+
or higher to use Coder Desktop. Current version: \(actualVersion)
312+
"""
280313
}
281314
}
282315

@@ -297,9 +330,16 @@ func writeVpnLog(_ log: Vpn_Log) {
297330
case .UNRECOGNIZED: .info
298331
}
299332
let logger = Logger(
300-
subsystem: "\(Bundle.main.bundleIdentifier!).dylib",
333+
subsystem: "\(Bundle.main.bundleIdentifier!).daemon",
301334
category: log.loggerNames.joined(separator: ".")
302335
)
303336
let fields = log.fields.map { "\($0.name): \($0.value)" }.joined(separator: ", ")
304337
logger.log(level: level, "\(log.message, privacy: .public)\(fields.isEmpty ? "" : ": \(fields)", privacy: .public)")
305338
}
339+
340+
extension FileManager {
341+
func ensureDirectories(for url: URL) throws {
342+
let dir = url.hasDirectoryPath ? url : url.deletingLastPathComponent()
343+
try createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil)
344+
}
345+
}

Coder-Desktop/Coder-DesktopHelper/TunnelHandle.swift

Lines changed: 0 additions & 116 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(

Coder-Desktop/VPNLib/Download.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ extension DownloadManager: URLSessionDownloadDelegate {
102102
return
103103
}
104104
guard httpResponse.statusCode != 304 else {
105-
// We already have the latest dylib downloaded in dest
105+
// We already have the latest binary downloaded in dest
106106
continuation.resume()
107107
return
108108
}

Coder-Desktop/VPNLib/Receiver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ actor Receiver<RecvMsg: Message> {
6969
},
7070
onCancel: {
7171
self.logger.debug("async stream canceled")
72-
self.dispatch.close()
72+
self.dispatch.close(flags: [.stop])
7373
}
7474
)
7575
}

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