import Darwin import Dispatch import Foundation @main private struct MeetingAssistantLauncher { private static let serviceDirectoryName = "MeetingAssistant" static func main() { let runtimeDirectory = Bundle.main.bundleURL .appendingPathComponent("Contents/Resources/app", isDirectory: true) let assemblyPath = runtimeDirectory.appendingPathComponent("MeetingAssistant.dll").path let environmentFile = FileManager.default.homeDirectoryForCurrentUser .appendingPathComponent("Library/Application Support") .appendingPathComponent(serviceDirectoryName) .appendingPathComponent("config/meeting-assistant.env") guard FileManager.default.fileExists(atPath: assemblyPath) else { fail("Meeting Assistant runtime not found at \(assemblyPath)") } guard FileManager.default.fileExists(atPath: environmentFile.path) else { fail("Meeting Assistant environment file not found at \(environmentFile.path)") } let process = Process() process.executableURL = URL(fileURLWithPath: "/bin/zsh") process.arguments = [ "-c", "set -a; source \"$1\"; set +a; exec /usr/local/share/dotnet/dotnet \"$2\" --contentRoot \"$3\"", "meeting-assistant-launcher", environmentFile.path, assemblyPath, runtimeDirectory.path ] process.currentDirectoryURL = runtimeDirectory signal(SIGTERM, SIG_IGN) signal(SIGINT, SIG_IGN) let terminationSignal = DispatchSource.makeSignalSource(signal: SIGTERM, queue: .global()) terminationSignal.setEventHandler { if process.isRunning { process.terminate() } } terminationSignal.resume() let interruptSignal = DispatchSource.makeSignalSource(signal: SIGINT, queue: .global()) interruptSignal.setEventHandler { if process.isRunning { kill(process.processIdentifier, SIGINT) } } interruptSignal.resume() do { try process.run() process.waitUntilExit() Darwin.exit(process.terminationStatus) } catch { fail("Meeting Assistant launcher failed: \(error)") } } private static func fail(_ message: String) -> Never { FileHandle.standardError.write(Data("\(message)\n".utf8)) Darwin.exit(1) } }