@@ -3,6 +3,7 @@ import Foundation
33enum GatewayLaunchAgentManager {
44 private static let logger = Logger ( subsystem: " ai.openclaw " , category: " gateway.launchd " )
55 private static let disableLaunchAgentMarker = " .openclaw/disable-launchagent "
6+ private nonisolated ( unsafe) static var externalHomeVolumeMemo : ( path: String , external: Bool ) ?
67
78 private static var disableLaunchAgentMarkerURL : URL {
89 #if DEBUG
@@ -15,10 +16,76 @@ enum GatewayLaunchAgentManager {
1516 }
1617
1718 private static var plistURL : URL {
18- FileManager ( ) . homeDirectoryForCurrentUser
19+ self . launchAgentPlistURL ( )
20+ }
21+
22+ static func launchAgentPlistURL(
23+ homeURL: URL = FileManager ( ) . homeDirectoryForCurrentUser,
24+ environment: [ String : String ] = ProcessInfo . processInfo. environment,
25+ homeIsExternalVolume: Bool ? = nil ) -> URL
26+ {
27+ self . launchAgentHomeURL (
28+ homeURL: homeURL,
29+ environment: environment,
30+ homeIsExternalVolume: homeIsExternalVolume)
1931 . appendingPathComponent ( " Library/LaunchAgents/ \( gatewayLaunchdLabel) .plist " )
2032 }
2133
34+ private static func launchAgentHomeURL(
35+ homeURL: URL ,
36+ environment: [ String : String ] ,
37+ homeIsExternalVolume: Bool ? ) -> URL
38+ {
39+ let external = homeIsExternalVolume ?? self . isExternalVolumeHome ( homeURL)
40+ guard external, let user = self . loginUsername ( environment: environment) else {
41+ return homeURL
42+ }
43+ return URL ( fileURLWithPath: " /Users " , isDirectory: true )
44+ . appendingPathComponent ( user, isDirectory: true )
45+ }
46+
47+ private static func loginUsername( environment: [ String : String ] ) -> String ? {
48+ if let user = environment [ " USER " ] ? . trimmingCharacters ( in: . whitespacesAndNewlines) ,
49+ !user. isEmpty
50+ {
51+ return user
52+ }
53+ if let logname = environment [ " LOGNAME " ] ? . trimmingCharacters ( in: . whitespacesAndNewlines) ,
54+ !logname. isEmpty
55+ {
56+ return logname
57+ }
58+ let fallback = NSUserName ( ) . trimmingCharacters ( in: . whitespacesAndNewlines)
59+ return fallback. isEmpty ? nil : fallback
60+ }
61+
62+ private static func isExternalVolumeHome( _ homeURL: URL ) -> Bool {
63+ let path = homeURL. path
64+ if let memo = self . externalHomeVolumeMemo, memo. path == path {
65+ return memo. external
66+ }
67+
68+ let external = self . fileSystemNumber ( forPath: " / " )
69+ . flatMap { rootDevice in
70+ self . fileSystemNumber ( forPath: path) . map { homeDevice in
71+ rootDevice != homeDevice
72+ }
73+ } ?? false
74+ self . externalHomeVolumeMemo = ( path, external)
75+ return external
76+ }
77+
78+ private static func fileSystemNumber( forPath path: String ) -> NSNumber ? {
79+ guard
80+ let value = try ? FileManager . default. attributesOfFileSystem ( forPath: path) [
81+ . systemNumber,
82+ ] as? NSNumber
83+ else {
84+ return nil
85+ }
86+ return value
87+ }
88+
2289 static func isLaunchAgentWriteDisabled( ) -> Bool {
2390 if FileManager ( ) . fileExists ( atPath: self . disableLaunchAgentMarkerURL. path) { return true }
2491 return false
0 commit comments