mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
0dbdf994b3
* feat(macos): isolate named app profiles * refactor(macos): isolate profile launch ownership * fix(macos): avoid overlapping approvals socket access * fix(macos): declare profile defaults concurrency ownership * fix(macos): return profiled node launch arguments * chore(i18n): refresh macOS profile source inventory * fix(macos): gate profile startup before services * test(macos): evaluate profile state before assertions * fix(daemon): skip absent launchd deactivation * fix(macos): fail closed on profile port conflicts * chore(i18n): refresh profile conflict inventory * fix(macos): ignore non-gateway launch agent claims * test(macos): stabilize profile lifecycle timing * fix(macos): remove stale dashboard URL * chore(macos): refresh native source baseline
59 lines
2.2 KiB
Swift
59 lines
2.2 KiB
Swift
import Darwin
|
|
import Foundation
|
|
|
|
final class ProfileGatewayPortReservation: @unchecked Sendable {
|
|
let port: Int
|
|
let conflict: String?
|
|
private let lock: AppInstanceLock?
|
|
|
|
private init(port: Int, conflict: String?, lock: AppInstanceLock?) {
|
|
self.port = port
|
|
self.conflict = conflict
|
|
self.lock = lock
|
|
}
|
|
|
|
static func acquire(
|
|
profile: AppProfile,
|
|
port: Int,
|
|
homeDirectory: URL = FileManager.default.homeDirectoryForCurrentUser,
|
|
temporaryDirectory: URL = URL(fileURLWithPath: "/tmp", isDirectory: true)) -> Self
|
|
{
|
|
guard let profileName = profile.name else { return Self(port: port, conflict: nil, lock: nil) }
|
|
let lockURL = temporaryDirectory
|
|
.appendingPathComponent("openclaw-\(geteuid())-app-profile-ports", isDirectory: true)
|
|
.appendingPathComponent("\(port).lock")
|
|
let lock: AppInstanceLock
|
|
switch AppInstanceLock.acquire(url: lockURL) {
|
|
case let .acquired(acquired): lock = acquired
|
|
case .busy:
|
|
return self.conflict(
|
|
profile: profileName,
|
|
port: port,
|
|
reason: "another running OpenClaw profile already reserves it")
|
|
case let .failed(reason):
|
|
return self.conflict(
|
|
profile: profileName,
|
|
port: port,
|
|
reason: "the profile reservation could not be verified (\(reason))")
|
|
}
|
|
|
|
if let reason = GatewayLaunchAgentManager.conflictingProfileClaimOwner(
|
|
port: port,
|
|
excludingLabel: profile.gatewayLaunchAgentLabel,
|
|
homeDirectory: homeDirectory)
|
|
{
|
|
return self.conflict(profile: profileName, port: port, reason: reason)
|
|
}
|
|
return Self(port: port, conflict: nil, lock: lock)
|
|
}
|
|
|
|
private static func conflict(profile: String, port: Int, reason: String) -> Self {
|
|
let message = "Profile \"\(profile)\" cannot use Gateway port \(port) because \(reason). " +
|
|
"Set gateway.port to a free port for this profile, or stop/uninstall the other Gateway."
|
|
return Self(
|
|
port: port,
|
|
conflict: message,
|
|
lock: nil)
|
|
}
|
|
}
|