Skip to content

Commit

Permalink
Create legacy ISOs on apple silicon (#166)
Browse files Browse the repository at this point in the history
* Add Installer variables

* Fix SwiftLint warnings

* Add adHocCodesign function

* Create legacy bootable ISOs

* Create legacy bootable disks

* Relax legacy ISO requirement

* Update descriptions

* Add missing cleanup step

* Refactor cleanup steps

* Update README.md
  • Loading branch information
ninxsoft authored Jun 11, 2024
1 parent a4a0e4f commit b69c006
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 12 deletions.
4 changes: 2 additions & 2 deletions Mist/Commands/Download/DownloadInstallerCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ struct DownloadInstallerCommand: ParsableCommand {

if
let architecture: Architecture = Hardware.architecture,
architecture == .appleSilicon && !installer.bigSurOrNewer && options.outputType.contains(.iso) {
!options.quiet ? PrettyPrint.print("macOS Catalina 10.15 and older cannot generate Bootable Disk Images on \(architecture.description) Macs...", noAnsi: options.noAnsi) : Mist.noop()
architecture == .appleSilicon && !installer.mavericksOrNewer && options.outputType.contains(.iso) {
!options.quiet ? PrettyPrint.print("OS X Mountain Lion 10.8 and older cannot generate Bootable Disk Images on \(architecture.description) Macs...", noAnsi: options.noAnsi) : Mist.noop()
!options.quiet ? PrettyPrint.print("Replace 'iso' with another output type or select a newer version of macOS, exiting...", noAnsi: options.noAnsi, prefix: .ending) : Mist.noop()
return
}
Expand Down
4 changes: 2 additions & 2 deletions Mist/Commands/Download/DownloadInstallerOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ struct DownloadInstallerOptions: ParsableArguments {
* application to generate a macOS Installer Application Bundle (.app).
* image to generate a macOS Disk Image (.dmg).
* iso to generate a Bootable macOS Disk Image (.iso), for use with virtualization software (ie. Parallels Desktop, VMware Fusion, VirtualBox).
Note: This option will fail when targeting macOS Catalina 10.15 and older on Apple Silicon Macs.
Note: This option will fail when targeting OS X Mountain Lion 10.8 and older on Apple Silicon Macs.
* package to generate a macOS Installer Package (.pkg).
* bootableinstaller to create a Bootable macOS Installer on a mounted volume
Note: This option will fail when targeting macOS Catalina 10.15 and older on Apple Silicon Macs.
Note: This option will fail when targeting OS X Mountain Lion 10.8 and older on Apple Silicon Macs.
""")
var outputType: [InstallerOutputType]

Expand Down
112 changes: 105 additions & 7 deletions Mist/Helpers/Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

// swiftlint:disable file_length
// swiftlint:disable file_length type_body_length

/// Helper Struct used to generate macOS Firmwares, Installers, Disk Images and Installer Packages.
enum Generator {
Expand Down Expand Up @@ -179,7 +179,7 @@ enum Generator {
!options.quiet ? PrettyPrint.print("Created image '\(destinationURL.path)'", noAnsi: options.noAnsi) : Mist.noop()
}

// swiftlint:disable function_body_length
// swiftlint:disable cyclomatic_complexity function_body_length

/// Generates a Bootable macOS Installer Disk Image.
///
Expand Down Expand Up @@ -225,15 +225,40 @@ enum Generator {
try updatePropertyList(url, key: "CFBundleShortVersionString", value: "12.6.03")
}

!options.quiet ? PrettyPrint.print("Creating install media at mount point '\(installer.temporaryISOMountPointURL.path)'...", noAnsi: options.noAnsi) : Mist.noop()
arguments = ["\(installer.temporaryInstallerURL.path)/Contents/Resources/createinstallmedia", "--volume", installer.temporaryISOMountPointURL.path, "--nointeraction"]
arguments = ["\(installer.temporaryInstallerURL.path)/Contents/Resources/createinstallmedia"]

if !installer.bigSurOrNewer {
// swiftlint:disable:next line_length
!options.quiet ? PrettyPrint.print("Copying '\(installer.temporaryInstallerURL.path)' to '\(installer.temporaryInstallerWithAdHocCodeSignaturesURL.path)'...", noAnsi: options.noAnsi) : Mist.noop()
try FileManager.default.copyItem(at: installer.temporaryInstallerURL, to: installer.temporaryInstallerWithAdHocCodeSignaturesURL)
!options.quiet ? PrettyPrint.print("Ad-hoc code signing '\(installer.temporaryInstallerWithAdHocCodeSignaturesURL.path)'...", noAnsi: options.noAnsi) : Mist.noop()
try adHocCodesign(installer.temporaryInstallerWithAdHocCodeSignaturesURL)
arguments = ["\(installer.temporaryInstallerWithAdHocCodeSignaturesURL.path)/Contents/Resources/createinstallmedia"]
}

arguments += ["--volume", installer.temporaryISOMountPointURL.path, "--nointeraction"]

if installer.sierraOrOlder {
arguments += ["--applicationpath", installer.temporaryInstallerURL.path]
}

!options.quiet ? PrettyPrint.print("Creating install media at mount point '\(installer.temporaryISOMountPointURL.path)'...", noAnsi: options.noAnsi) : Mist.noop()
_ = try Shell.execute(arguments)

if !installer.bigSurOrNewer {
for url in [
installer.temporaryInstallerWithAdHocCodeSignaturesURL,
installer.temporaryISOInstallerWithAdHocCodeSignaturesURL,
installer.temporaryISOInstallerURL
] where FileManager.default.fileExists(atPath: url.path) {
!options.quiet ? PrettyPrint.print("Deleting '\(url.path)'...", noAnsi: options.noAnsi) : Mist.noop()
try FileManager.default.removeItem(at: url)
}

!options.quiet ? PrettyPrint.print("Copying '\(installer.temporaryInstallerURL.path)' to '\(installer.temporaryISOInstallerURL.path)'...", noAnsi: options.noAnsi) : Mist.noop()
try FileManager.default.copyItem(at: installer.temporaryInstallerURL, to: installer.temporaryISOInstallerURL)
}

!options.quiet ? PrettyPrint.print("Unmounting disk image at mount point '\(installer.temporaryISOMountPointURL.path)'...", noAnsi: options.noAnsi) : Mist.noop()
arguments = ["hdiutil", "detach", installer.temporaryISOMountPointURL.path, "-force"]
_ = try Shell.execute(arguments)
Expand Down Expand Up @@ -276,7 +301,7 @@ enum Generator {
}
}

// swiftlint:enable function_body_length
// swiftlint:enable cyclomatic_complexity function_body_length

/// Generates a macOS Installer Package, optionally codesigning.
///
Expand Down Expand Up @@ -363,16 +388,46 @@ enum Generator {
try updatePropertyList(url, key: "CFBundleShortVersionString", value: "12.6.03")
}

var arguments: [String] = ["\(installer.temporaryInstallerURL.path)/Contents/Resources/createinstallmedia", "--volume", volume, "--nointeraction"]
let destinationURL: URL = .init(fileURLWithPath: volume).deletingLastPathComponent().appendingPathComponent("Install \(installer.name)")
var arguments: [String] = ["\(installer.temporaryInstallerURL.path)/Contents/Resources/createinstallmedia"]

if !installer.bigSurOrNewer {
// swiftlint:disable:next line_length
!options.quiet ? PrettyPrint.print("Copying '\(installer.temporaryInstallerURL.path)' to '\(installer.temporaryInstallerWithAdHocCodeSignaturesURL.path)'...", noAnsi: options.noAnsi) : Mist.noop()
try FileManager.default.copyItem(at: installer.temporaryInstallerURL, to: installer.temporaryInstallerWithAdHocCodeSignaturesURL)
!options.quiet ? PrettyPrint.print("Ad-hoc code signing '\(installer.temporaryInstallerWithAdHocCodeSignaturesURL.path)'...", noAnsi: options.noAnsi) : Mist.noop()
try adHocCodesign(installer.temporaryInstallerWithAdHocCodeSignaturesURL)
arguments = ["\(installer.temporaryInstallerWithAdHocCodeSignaturesURL.path)/Contents/Resources/createinstallmedia"]
}

arguments += ["--volume", installer.temporaryISOMountPointURL.path, "--nointeraction"]

if installer.sierraOrOlder {
arguments += ["--applicationpath", installer.temporaryInstallerURL.path]
}

let destinationURL: URL = .init(fileURLWithPath: volume).deletingLastPathComponent().appendingPathComponent("Install \(installer.name)")

!options.quiet ? PrettyPrint.print("Creating bootable macOS Installer at mount point '\(volume)'...", noAnsi: options.noAnsi) : Mist.noop()
_ = try Shell.execute(arguments)
!options.quiet ? PrettyPrint.print("Created bootable macOS installer at mount point '\(destinationURL.path)'", noAnsi: options.noAnsi) : Mist.noop()

if !installer.bigSurOrNewer {
// swiftlint:disable:next identifier_name
let temporaryBootableInstallerWithAdHocCodeSignaturesURL: URL = destinationURL.appendingPathComponent("Install \(installer.name).ad-hoc-code-signatures.app")
let temporaryBootableInstallerURL: URL = destinationURL.appendingPathComponent("Install \(installer.name).app")

for url in [
installer.temporaryInstallerWithAdHocCodeSignaturesURL,
temporaryBootableInstallerWithAdHocCodeSignaturesURL,
temporaryBootableInstallerURL
] where FileManager.default.fileExists(atPath: url.path) {
!options.quiet ? PrettyPrint.print("Deleting '\(url.path)'...", noAnsi: options.noAnsi) : Mist.noop()
try FileManager.default.removeItem(at: url)
}

!options.quiet ? PrettyPrint.print("Copying '\(installer.temporaryInstallerURL.path)' to '\(temporaryBootableInstallerURL.path)'...", noAnsi: options.noAnsi) : Mist.noop()
try FileManager.default.copyItem(at: installer.temporaryInstallerURL, to: temporaryBootableInstallerURL)
}
}

/// Update a key-pair value in a Property List.
Expand Down Expand Up @@ -401,4 +456,47 @@ enum Generator {
let output: String = .init(decoding: data, as: UTF8.self)
try output.write(to: url, atomically: true, encoding: .utf8)
}

/// Sign the provided URL with an ad-hoc code signature.
///
/// - Parameters:
/// - url: The URL of the file or directory to sign with an ad-hoc code signature.
///
/// - Throws: An `Error` if the command failed to execute.
private static func adHocCodesign(_ url: URL) throws {
guard
let enumerator: FileManager.DirectoryEnumerator = FileManager.default.enumerator(
at: url,
includingPropertiesForKeys: [.isRegularFileKey],
options: [.skipsHiddenFiles, .skipsPackageDescendants]
) else {
throw MistError.invalidURL(url.path)
}

for case let url as URL in enumerator {
let fileAttributes: URLResourceValues = try url.resourceValues(forKeys: [.isRegularFileKey])

guard
let isRegularFile: Bool = fileAttributes.isRegularFile,
isRegularFile else {
continue
}

do {
let arguments: [String] = ["codesign", "--remove-signature", "--force", url.path]
_ = try Shell.execute(arguments)
} catch {
// do nothing
}

do {
let arguments: [String] = ["codesign", "--sign", "-", "--force", url.path]
_ = try Shell.execute(arguments)
} catch {
// do nothing
}
}
}
}

// swiftlint:enable type_body_length
14 changes: 14 additions & 0 deletions Mist/Model/Installer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -569,10 +569,24 @@ struct Installer: Decodable {
temporaryDiskImageMountPointURL.appendingPathComponent("Applications/Install \(name).app")
}

// swiftlint:disable:next identifier_name
var temporaryInstallerWithAdHocCodeSignaturesURL: URL {
URL(fileURLWithPath: "\(NSTemporaryDirectory())Install \(name).ad-hoc-code-signatures.app")
}

var temporaryISOMountPointURL: URL {
URL(fileURLWithPath: "/Volumes/Install \(name)")
}

var temporaryISOInstallerURL: URL {
temporaryISOMountPointURL.appendingPathComponent("Install \(name).app")
}

// swiftlint:disable:next identifier_name
var temporaryISOInstallerWithAdHocCodeSignaturesURL: URL {
temporaryISOMountPointURL.appendingPathComponent("Install \(name).ad-hoc-code-signatures.app")
}

var dictionary: [String: Any] {
[
"identifier": identifier,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ mist download installer "macOS Sonoma" application image iso package \

## Build Requirements

- Swift **5.9**.
- Swift **5.10**.
- Runs on **macOS Big Sur 11** and later.

## Download
Expand Down

0 comments on commit b69c006

Please sign in to comment.