Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Pareto/ParetoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@
}

do {
try fileManager.moveItem(atPath: bundlePath, toPath: destinationPath)
try AppUpdater.safeCopyBundle(from: bundlePath, to: destinationPath)
print("Moved app to /Applications")
AppUpdater.clearExtendedAttributes(at: destinationPath)

// Clean up original bundle after successful copy
try? fileManager.removeItem(atPath: bundlePath)
} catch {
print("Failed to move the app: \(error)")
print("Failed to copy the app: \(error)")
}
exit(0) // Terminate old instance
}
Expand Down Expand Up @@ -81,7 +81,7 @@
for claim in Claims.global.all {
claim.configure()
for check in claim.checksSorted {
check.run()

Check warning on line 84 in Pareto/ParetoApp.swift

View workflow job for this annotation

GitHub Actions / Build and Archive Native

result of call to 'run()' is unused

Check warning on line 84 in Pareto/ParetoApp.swift

View workflow job for this annotation

GitHub Actions / Test

result of call to 'run()' is unused

Check warning on line 84 in Pareto/ParetoApp.swift

View workflow job for this annotation

GitHub Actions / Build and Archive SetApp

result of call to 'run()' is unused
print(check.report + "\n")
}
}
Expand All @@ -94,7 +94,7 @@
for claim in Claims.global.all {
claim.configure()
for check in claim.checksSorted {
check.run()

Check warning on line 97 in Pareto/ParetoApp.swift

View workflow job for this annotation

GitHub Actions / Build and Archive Native

result of call to 'run()' is unused

Check warning on line 97 in Pareto/ParetoApp.swift

View workflow job for this annotation

GitHub Actions / Test

result of call to 'run()' is unused

Check warning on line 97 in Pareto/ParetoApp.swift

View workflow job for this annotation

GitHub Actions / Build and Archive SetApp

result of call to 'run()' is unused
print(check.reportJSON + ",\n")
}
}
Expand All @@ -108,7 +108,7 @@
exit(0)
}

var inviteParam = CommandLine.arguments.last ?? ""

Check warning on line 111 in Pareto/ParetoApp.swift

View workflow job for this annotation

GitHub Actions / Build and Archive Native

variable 'inviteParam' was never mutated; consider changing to 'let' constant

Check warning on line 111 in Pareto/ParetoApp.swift

View workflow job for this annotation

GitHub Actions / Test

variable 'inviteParam' was never mutated; consider changing to 'let' constant

Check warning on line 111 in Pareto/ParetoApp.swift

View workflow job for this annotation

GitHub Actions / Build and Archive SetApp

variable 'inviteParam' was never mutated; consider changing to 'let' constant

if inviteParam.isEmpty {
print("Missing team invite parameter")
Expand Down
59 changes: 38 additions & 21 deletions Pareto/Updater.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,25 @@
}

extension AppUpdater {
/// Clears extended attributes (including quarantine flags) from the app bundle
/// to prevent binary corruption issues after update
static func clearExtendedAttributes(at path: String) {
let xattrProc = Process()
xattrProc.launchPath = "/usr/bin/xattr"
xattrProc.arguments = ["-cr", path]
xattrProc.launch()
xattrProc.waitUntilExit()
os_log("Cleared extended attributes from: \(path)")
/// Uses ditto to preserve code signatures and permissions when copying app bundles
static func safeCopyBundle(from source: String, to destination: String) throws {
// First remove destination if it exists
if FileManager.default.fileExists(atPath: destination) {
try FileManager.default.removeItem(atPath: destination)
}

// Use ditto to preserve all metadata including code signatures
let dittoProc = Process()
dittoProc.launchPath = "/usr/bin/ditto"
dittoProc.arguments = [source, destination]
dittoProc.launch()
dittoProc.waitUntilExit()

if dittoProc.terminationStatus != 0 {
throw Error.invalidDownloadedBundle
}

os_log("Successfully copied bundle from \(source) to \(destination)")
}
}

Expand Down Expand Up @@ -197,14 +207,10 @@
if validate(downloadedAppBundle, installedAppBundle) {
do {
if SystemUser.current.isAdmin {
try installedAppBundle.path.delete()
os_log("Delete installedAppBundle: \(installedAppBundle)")
try downloadedAppBundle.path.move(to: installedAppBundle.path)
os_log("Move new app to installedAppBundle: \(installedAppBundle)")
AppUpdater.clearExtendedAttributes(at: installedAppBundle.path.string)
try AppUpdater.safeCopyBundle(from: downloadedAppBundle.path.string, to: installedAppBundle.path.string)
} else {
let path = "\(downloadedAppBundle.path)/Contents/MacOS/Pareto Security".shellEscaped()
AppUpdater.runCMDasAdmin(cmd: "\(path) -update")

Check warning on line 213 in Pareto/Updater.swift

View workflow job for this annotation

GitHub Actions / Build and Archive Native

result of call to 'runCMDasAdmin(cmd:)' is unused

Check warning on line 213 in Pareto/Updater.swift

View workflow job for this annotation

GitHub Actions / Test

result of call to 'runCMDasAdmin(cmd:)' is unused

Check warning on line 213 in Pareto/Updater.swift

View workflow job for this annotation

GitHub Actions / Build and Archive SetApp

result of call to 'runCMDasAdmin(cmd:)' is unused
}

let proc = Process()
Expand Down Expand Up @@ -263,33 +269,44 @@

private func unzip(_ url: URL, contentType: Release.Asset.ContentType) -> URL {
let proc = Process()
let extractDir = url.deletingLastPathComponent()

if #available(OSX 10.13, *) {
proc.currentDirectoryURL = url.deletingLastPathComponent()
proc.currentDirectoryURL = extractDir
} else {
proc.currentDirectoryPath = url.deletingLastPathComponent().path
proc.currentDirectoryPath = extractDir.path
}

switch contentType {
case .tar:
proc.launchPath = "/usr/bin/tar"
proc.arguments = ["xf", url.path]
case .zip:
proc.launchPath = "/usr/bin/unzip"
proc.arguments = [url.path]
// Use ditto to properly extract zip files and preserve code signatures
proc.launchPath = "/usr/bin/ditto"
proc.arguments = ["-xk", url.path, extractDir.path]
case .unknown:
proc.launchPath = "/usr/bin/unzip"
proc.arguments = [url.path]
// Default to ditto for unknown types as well
proc.launchPath = "/usr/bin/ditto"
proc.arguments = ["-xk", url.path, extractDir.path]
}

func findApp() throws -> URL? {
let files = try FileManager.default.contentsOfDirectory(at: url.deletingLastPathComponent(), includingPropertiesForKeys: [.isDirectoryKey], options: .skipsSubdirectoryDescendants)
let files = try FileManager.default.contentsOfDirectory(at: extractDir, includingPropertiesForKeys: [.isDirectoryKey], options: .skipsSubdirectoryDescendants)
for url in files {
guard url.pathExtension == "app" else { continue }
guard let foo = try url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory, foo else { continue }
return url
}
return nil
}

proc.launch()
proc.waitUntilExit()

if proc.terminationStatus != 0 {
os_log("Failed to extract archive: \(url.path)")
}

return try! findApp()!
}
Loading