64 lines
3.1 KiB
Swift
64 lines
3.1 KiB
Swift
import AppKit
|
|
|
|
struct InstallError: LocalizedError {
|
|
let errorDescription: String?
|
|
}
|
|
|
|
do {
|
|
guard CommandLine.arguments.count == 3, !CommandLine.arguments[2].isEmpty else {
|
|
throw InstallError(errorDescription: "Usage: swift Scripts/install.swift <Conjure.app> <installation directory>")
|
|
}
|
|
let manager = FileManager.default
|
|
let identifier = "dev.conjure.launcher"
|
|
let source = URL(fileURLWithPath: CommandLine.arguments[1], isDirectory: true)
|
|
.standardizedFileURL.resolvingSymlinksInPath()
|
|
let directory = URL(fileURLWithPath: (CommandLine.arguments[2] as NSString).expandingTildeInPath, isDirectory: true)
|
|
.standardizedFileURL.resolvingSymlinksInPath()
|
|
let destination = directory.appendingPathComponent("Conjure.app", isDirectory: true)
|
|
guard source.path != destination.path else {
|
|
throw InstallError(errorDescription: "The build and installation paths must be different.")
|
|
}
|
|
guard Bundle(url: source)?.bundleIdentifier == identifier else {
|
|
throw InstallError(errorDescription: "The source is not a Conjure application bundle.")
|
|
}
|
|
|
|
func validateDestination() throws -> Bool {
|
|
let exists = manager.fileExists(atPath: destination.path)
|
|
if exists {
|
|
guard try destination.resourceValues(forKeys: [.isSymbolicLinkKey]).isSymbolicLink != true,
|
|
Bundle(url: destination)?.bundleIdentifier == identifier else {
|
|
throw InstallError(errorDescription: "Refusing to replace an unrelated app or symbolic link at \(destination.path).")
|
|
}
|
|
}
|
|
let running = NSRunningApplication.runningApplications(withBundleIdentifier: identifier)
|
|
.contains { $0.bundleURL?.resolvingSymlinksInPath().path == destination.path }
|
|
guard !running else {
|
|
throw InstallError(errorDescription: "Quit the installed Conjure at \(destination.path) before reinstalling.")
|
|
}
|
|
return exists
|
|
}
|
|
|
|
_ = try validateDestination()
|
|
try manager.createDirectory(at: directory, withIntermediateDirectories: true)
|
|
let staging = directory.appendingPathComponent(".Conjure-install-\(UUID().uuidString).app", isDirectory: true)
|
|
defer { try? manager.removeItem(at: staging) }
|
|
try manager.copyItem(at: source, to: staging)
|
|
let verification = Process()
|
|
verification.executableURL = URL(fileURLWithPath: "/usr/bin/codesign")
|
|
verification.arguments = ["--verify", "--strict", staging.path]
|
|
try verification.run()
|
|
verification.waitUntilExit()
|
|
guard verification.terminationStatus == 0 else {
|
|
throw InstallError(errorDescription: "Bundle signature verification failed; the installed app was not changed.")
|
|
}
|
|
if try validateDestination() {
|
|
_ = try manager.replaceItemAt(destination, withItemAt: staging, options: .usingNewMetadataOnly)
|
|
} else {
|
|
try manager.moveItem(at: staging, to: destination)
|
|
}
|
|
print("Installed Conjure at \(destination.path).")
|
|
} catch {
|
|
FileHandle.standardError.write(Data("Installation failed: \(error.localizedDescription)\n".utf8))
|
|
exit(1)
|
|
}
|