mirror of
https://github.com/munki/munki.git
synced 2025-12-16 20:14:48 -06:00
Merge branch 'Munki7dev'
This commit is contained in:
@@ -229,13 +229,14 @@ func notifyUserOfUpdates(force: Bool = false) {
|
||||
if !force, activeDisplaySleepAssertion() {
|
||||
// user may be in a virtual meeting or presenting.
|
||||
// Skip the notification; hopefully we'll be able to notify later.
|
||||
munkiLog("Skipping user notification.")
|
||||
munkiLog("Skipping user notification because there is an active display sleep assertion.")
|
||||
munkiLog("This may indicate the user is presenting or in a virtual meeting.")
|
||||
return
|
||||
}
|
||||
// record current notification date
|
||||
setPref("LastNotifiedDate", now)
|
||||
munkiLog("Notifying user of available updates.")
|
||||
munkiLog("LastNotifiedDate was \(lastNotifiedDate)")
|
||||
munkiLog("LastNotifiedDate was \(RFC3339String(for: lastNotifiedDate))")
|
||||
// trigger LaunchAgent to launch munki-notifier.app in the right context
|
||||
let launchfile = "/var/run/com.googlecode.munki.munki-notifier"
|
||||
FileManager.default.createFile(atPath: launchfile, contents: nil)
|
||||
@@ -243,6 +244,9 @@ func notifyUserOfUpdates(force: Bool = false) {
|
||||
// clear the trigger file. We have to do it because we're root,
|
||||
// and the munki-notifier process is running as the user
|
||||
try? FileManager.default.removeItem(atPath: launchfile)
|
||||
} else {
|
||||
munkiLog("Skipping user notification")
|
||||
munkiLog("Last notification was \(RFC3339String(for: lastNotifiedDate)) and notification interval is \(daysBetweenNotifications) day(s).")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +336,6 @@ func doInstallTasks(doAppleUpdates: Bool = false, onlyUnattended: Bool = false)
|
||||
}
|
||||
|
||||
var munkiItemsRestartAction = PostAction.none
|
||||
//var appleItemsRestartAction = PostAction.none
|
||||
|
||||
if munkiUpdatesAvailable() > 0 {
|
||||
// install Munki updates
|
||||
@@ -353,7 +356,6 @@ func doInstallTasks(doAppleUpdates: Bool = false, onlyUnattended: Bool = false)
|
||||
|
||||
Report.shared.save()
|
||||
|
||||
//return max(appleItemsRestartAction, munkiItemsRestartAction) // we no longer support installing Apple updates
|
||||
return munkiItemsRestartAction
|
||||
}
|
||||
|
||||
|
||||
@@ -120,21 +120,6 @@ struct installedStateTests {
|
||||
#expect(await installedState(item) == .thisVersionNotInstalled)
|
||||
}
|
||||
|
||||
/*
|
||||
/// If version_script something that isn't parseable as a version, return .thisVersionNotInstalled
|
||||
@Test func installedStateWithVersionScriptInvalidOutputReturnsNotInstalled() async throws {
|
||||
let item: PlistDict = [
|
||||
"name": "Foo",
|
||||
"version": "1.2.3",
|
||||
"version_script": """
|
||||
#!/bin/sh
|
||||
echo "Foobarbaz"
|
||||
"""
|
||||
]
|
||||
#expect(await installedState(item) == .thisVersionNotInstalled)
|
||||
}
|
||||
*/
|
||||
|
||||
/// If version_script exits non-zero, return .thisVersionNotInstalled
|
||||
@Test func versionScriptErrorReturnsNotInstalled() async throws {
|
||||
let item: PlistDict = [
|
||||
@@ -147,6 +132,69 @@ struct installedStateTests {
|
||||
]
|
||||
#expect(await installedState(item) == .thisVersionNotInstalled)
|
||||
}
|
||||
|
||||
/// If a receipt is not present, return .thisVersionNotInstalled
|
||||
@Test func receiptNotPresentReturnsNotInstalled() async throws {
|
||||
let item: PlistDict = [
|
||||
"name": "Foo",
|
||||
"version": "1.2.3",
|
||||
"receipts": [
|
||||
[
|
||||
"packageid": "bar.doesntexist.foo",
|
||||
"version": "1.2.3",
|
||||
],
|
||||
],
|
||||
]
|
||||
#expect(await installedState(item) == .thisVersionNotInstalled)
|
||||
}
|
||||
|
||||
/// if receipt present and higher version, return .newerVersionInstalled
|
||||
@Test func receiptPresentReturnsInstalled() async throws {
|
||||
// this depends on a receipt installed by Apple that is present
|
||||
// on macOS Sequoia and Tahoe, but might go away in the future...
|
||||
let item: PlistDict = [
|
||||
"name": "com.apple.files.data-template",
|
||||
"version": "0.1",
|
||||
"receipts": [
|
||||
[
|
||||
"packageid": "com.apple.files.data-template",
|
||||
"version": "0.1",
|
||||
],
|
||||
],
|
||||
]
|
||||
#expect(await installedState(item) == .newerVersionInstalled)
|
||||
}
|
||||
|
||||
/// if receipts array is empty (and no other installed criteria), return .thisVersionInstalled
|
||||
@Test func emptyReceiptsArrayReturnsInstalled() async throws {
|
||||
// If there's no way to determine what's installed,
|
||||
// installedState() returns .thisVersionInstalled so that
|
||||
// Munki does _not_ try to install
|
||||
let item: PlistDict = [
|
||||
"name": "Foo",
|
||||
"version": "0.1",
|
||||
"receipts": [],
|
||||
]
|
||||
#expect(await installedState(item) == .thisVersionInstalled)
|
||||
}
|
||||
|
||||
/// if install array is defined but empty, ignore it and fall through to considering receipts
|
||||
@Test func emptyInstallArrayAndNonexistentReceiptReturnsNotInstalled() async throws {
|
||||
// If the installs array is empty, the check should fail though to
|
||||
// use the receipts array, and should return .thisVersionNotInstalled
|
||||
let item: PlistDict = [
|
||||
"name": "Foo",
|
||||
"version": "1.2.3",
|
||||
"installs": [],
|
||||
"receipts": [
|
||||
[
|
||||
"packageid": "bar.doesntexist.foo",
|
||||
"version": "1.2.3",
|
||||
],
|
||||
],
|
||||
]
|
||||
#expect(await installedState(item) == .thisVersionNotInstalled)
|
||||
}
|
||||
}
|
||||
|
||||
struct someVersionInstalledTests {
|
||||
@@ -263,6 +311,24 @@ struct someVersionInstalledTests {
|
||||
]
|
||||
#expect(await someVersionInstalled(item) == false)
|
||||
}
|
||||
|
||||
/// if install array is defined but empty, ignore it and fall through to considering receipts
|
||||
@Test func emptyInstallArrayAndNonexistentReceiptReturnsNotInstalled() async throws {
|
||||
// If the installs array is empty, the check should fail though to
|
||||
// use the receipts array, and should return false
|
||||
let item: PlistDict = [
|
||||
"name": "Foo",
|
||||
"version": "1.2.3",
|
||||
"installs": [],
|
||||
"receipts": [
|
||||
[
|
||||
"packageid": "bar.doesntexist.foo",
|
||||
"version": "1.2.3",
|
||||
],
|
||||
],
|
||||
]
|
||||
#expect(await someVersionInstalled(item) == false)
|
||||
}
|
||||
}
|
||||
|
||||
struct evidenceThisIsInstalledTests {
|
||||
@@ -425,4 +491,22 @@ struct evidenceThisIsInstalledTests {
|
||||
]
|
||||
#expect(await evidenceThisIsInstalled(item) == true)
|
||||
}
|
||||
|
||||
/// if install array is defined but empty, ignore it and fall through to considering receipts
|
||||
@Test func emptyInstallArrayAndNonexistentReceiptReturnsNotInstalled() async throws {
|
||||
// If the installs array is empty, the check should fail though to
|
||||
// use the receipts array, and should return false
|
||||
let item: PlistDict = [
|
||||
"name": "Foo",
|
||||
"version": "1.2.3",
|
||||
"installs": [],
|
||||
"receipts": [
|
||||
[
|
||||
"packageid": "bar.doesntexist.foo",
|
||||
"version": "1.2.3",
|
||||
],
|
||||
],
|
||||
]
|
||||
#expect(await evidenceThisIsInstalled(item) == false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ enum RestartAction: String, CaseIterable, ExpressibleByArgument {
|
||||
/// Supported installer types for --installer-type argument
|
||||
enum InstallerType: String, CaseIterable, ExpressibleByArgument {
|
||||
case copy_from_dmg
|
||||
case startosinstall
|
||||
case stage_os_installer
|
||||
}
|
||||
|
||||
|
||||
@@ -155,25 +155,9 @@ func createPkgInfoForDragNDrop(_ mountpoint: String, options: PkginfoOptions) th
|
||||
guard !dragNDropItem.isEmpty else {
|
||||
throw MunkiError("No application found on disk image.")
|
||||
}
|
||||
// check to see if item is a macOS installer and we can generate a startosinstall item
|
||||
// TODO: remove this or print warning
|
||||
// since it looks like Munki 7 won't support this installer_type
|
||||
let itempath = (mountpoint as NSString).appendingPathComponent(dragNDropItem)
|
||||
let itemIsInstallMacOSApp = pathIsInstallMacOSApp(itempath)
|
||||
if itemIsInstallMacOSApp,
|
||||
options.type.installerType == .startosinstall
|
||||
{
|
||||
if options.hidden.printWarnings,
|
||||
installMacOSAppIsStub(itempath)
|
||||
{
|
||||
printStderr("WARNING: the provided disk image appears to contain an Install macOS application, but the application does not contain Contents/SharedSupport/InstallESD.dmg or Contents/SharedSupport/SharedSupport.dmg")
|
||||
}
|
||||
return try makeStartOSInstallPkgInfo(
|
||||
mountpoint: mountpoint, item: dragNDropItem
|
||||
)
|
||||
}
|
||||
|
||||
// continue as copy_from_dmg item
|
||||
let itempath = (mountpoint as NSString).appendingPathComponent(dragNDropItem)
|
||||
installsitem = createInstallsItem(itempath)
|
||||
|
||||
if !installsitem.isEmpty {
|
||||
@@ -234,7 +218,7 @@ func createPkgInfoForDragNDrop(_ mountpoint: String, options: PkginfoOptions) th
|
||||
info["uninstall_method"] = "remove_copied_items"
|
||||
|
||||
// Should we add extra info for a stage_os_installer item?
|
||||
if itemIsInstallMacOSApp,
|
||||
if pathIsInstallMacOSApp(itempath),
|
||||
options.type.installerType == nil || options.type.installerType == .stage_os_installer
|
||||
{
|
||||
let additionalInfo = try makeStageOSInstallerPkgInfo(itempath)
|
||||
@@ -310,11 +294,12 @@ func createPkgInfoFromDmg(_ dmgpath: String,
|
||||
|
||||
/// Attempt to read a file with the same name as the input string and return its text,
|
||||
/// otherwise return the input string
|
||||
func readFileOrString(_ fileNameOrString: String) -> String {
|
||||
if !pathExists(fileNameOrString) {
|
||||
func readFileOrString(_ fileNameOrString: String) throws -> String {
|
||||
let expandedPath = (fileNameOrString as NSString).expandingTildeInPath
|
||||
if !pathExists(expandedPath) {
|
||||
return fileNameOrString
|
||||
}
|
||||
return (try? String(contentsOfFile: fileNameOrString, encoding: .utf8)) ?? fileNameOrString
|
||||
return try fileContents(fileNameOrString)
|
||||
}
|
||||
|
||||
/// If path appears to be inside the repo's pkgs directory, return a path relative to the pkgs dir
|
||||
@@ -359,6 +344,16 @@ func getMinimumOSVersionFromInstallsApps(_ pkginfo: PlistDict) -> String? {
|
||||
return minimumOSVersions.max()?.value
|
||||
}
|
||||
|
||||
/// return contents of file at path, expanding tilde as needed
|
||||
func fileContents(_ path: String) throws -> String {
|
||||
let expandedPath = (path as NSString).expandingTildeInPath
|
||||
do {
|
||||
return try String(contentsOfFile: expandedPath, encoding: .utf8)
|
||||
} catch {
|
||||
throw MunkiError("Failed to read file \(expandedPath): \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a pkginfo dictionary for installeritem
|
||||
func makepkginfo(_ filepath: String?,
|
||||
options: PkginfoOptions) throws -> PlistDict
|
||||
@@ -448,7 +443,7 @@ func makepkginfo(_ filepath: String?,
|
||||
pkginfo["catalogs"] = options.other.catalog
|
||||
}
|
||||
if let description = options.override.description {
|
||||
pkginfo["description"] = readFileOrString(description)
|
||||
pkginfo["description"] = try readFileOrString(description)
|
||||
}
|
||||
if let displayname = options.override.displayname {
|
||||
pkginfo["display_name"] = displayname
|
||||
@@ -494,46 +489,30 @@ func makepkginfo(_ filepath: String?,
|
||||
// add pkginfo scripts if specified
|
||||
// TODO: verify scripts start with a shebang line?
|
||||
if let installcheckScript = options.script.installcheckScript {
|
||||
if let scriptText = try? String(contentsOfFile: installcheckScript, encoding: .utf8) {
|
||||
pkginfo["installcheck_script"] = scriptText
|
||||
}
|
||||
pkginfo["installcheck_script"] = try fileContents(installcheckScript)
|
||||
}
|
||||
if let uninstallcheckScript = options.script.uninstallcheckScript {
|
||||
if let scriptText = try? String(contentsOfFile: uninstallcheckScript, encoding: .utf8) {
|
||||
pkginfo["uninstallcheck_script"] = scriptText
|
||||
}
|
||||
pkginfo["uninstallcheck_script"] = try fileContents(uninstallcheckScript)
|
||||
}
|
||||
if let postinstallScript = options.script.postinstallScript {
|
||||
if let scriptText = try? String(contentsOfFile: postinstallScript, encoding: .utf8) {
|
||||
pkginfo["postinstall_script"] = scriptText
|
||||
}
|
||||
pkginfo["postinstall_script"] = try fileContents(postinstallScript)
|
||||
}
|
||||
if let preinstallScript = options.script.preinstallScript {
|
||||
if let scriptText = try? String(contentsOfFile: preinstallScript, encoding: .utf8) {
|
||||
pkginfo["preinstall_script"] = scriptText
|
||||
}
|
||||
pkginfo["preinstall_script"] = try fileContents(preinstallScript)
|
||||
}
|
||||
if let postuninstallScript = options.script.postuninstallScript {
|
||||
if let scriptText = try? String(contentsOfFile: postuninstallScript, encoding: .utf8) {
|
||||
pkginfo["postuninstall_script"] = scriptText
|
||||
}
|
||||
pkginfo["postuninstall_script"] = try fileContents(postuninstallScript)
|
||||
}
|
||||
if let preuninstallScript = options.script.preuninstallScript {
|
||||
if let scriptText = try? String(contentsOfFile: preuninstallScript, encoding: .utf8) {
|
||||
pkginfo["preuninstall_script"] = scriptText
|
||||
}
|
||||
pkginfo["preuninstall_script"] = try fileContents(preuninstallScript)
|
||||
}
|
||||
if let uninstallScript = options.script.uninstallScript {
|
||||
if let scriptText = try? String(contentsOfFile: uninstallScript, encoding: .utf8) {
|
||||
pkginfo["uninstall_script"] = scriptText
|
||||
pkginfo["uninstall_method"] = "uninstall_script"
|
||||
pkginfo["uninstallable"] = true
|
||||
}
|
||||
pkginfo["uninstall_script"] = try fileContents(uninstallScript)
|
||||
pkginfo["uninstall_method"] = "uninstall_script"
|
||||
pkginfo["uninstallable"] = true
|
||||
}
|
||||
if let versionScript = options.script.versionScript {
|
||||
if let scriptText = try? String(contentsOfFile: versionScript, encoding: .utf8) {
|
||||
pkginfo["version_script"] = scriptText
|
||||
}
|
||||
pkginfo["version_script"] = try fileContents(versionScript)
|
||||
}
|
||||
// more options and pkginfo bits
|
||||
if !installeritem.isEmpty || options.type.nopkg {
|
||||
@@ -588,7 +567,7 @@ func makepkginfo(_ filepath: String?,
|
||||
pkginfo["installer_environment"] = options.pkg.installerEnvironmentDict
|
||||
}
|
||||
if let notes = options.other.notes {
|
||||
pkginfo["notes"] = readFileOrString(notes)
|
||||
pkginfo["notes"] = try readFileOrString(notes)
|
||||
}
|
||||
|
||||
return pkginfo
|
||||
|
||||
@@ -224,7 +224,9 @@ func installItem(_ item: PlistDict) async -> (Int, Bool) {
|
||||
needToRestart = requiresRestart(item)
|
||||
default:
|
||||
// unknown or no longer supported installer type
|
||||
if ["appdmg", "profiles"].contains(installerType) || installerType.hasPrefix("Adobe") {
|
||||
if ["appdmg", "apple_update_metadata", "startosinstall", "profile"].contains(installerType) ||
|
||||
installerType.hasPrefix("Adobe")
|
||||
{
|
||||
display.error("Installer type '\(installerType)' for \(installerItem) is no longer supported.")
|
||||
} else {
|
||||
display.error("Installer type '\(installerType)' for \(installerItem) is an unknown installer type.")
|
||||
@@ -266,7 +268,7 @@ func installWithInstallInfo(
|
||||
|
||||
if installerType == "startosinstall" {
|
||||
skippedInstalls.append(item)
|
||||
display.debug1("Skipping install of \(itemName) because it's a startosinstall item. Will install later.")
|
||||
display.debug1("Skipping install of \(itemName) because it's a startosinstall item, which is no longer supported.")
|
||||
continue
|
||||
}
|
||||
if onlyUnattended {
|
||||
|
||||
@@ -44,17 +44,6 @@ func findInstallMacOSApp(_ dirpath: String) -> String? {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Some downloaded macOS installer apps are stubs that don't contain
|
||||
/// all the needed resources, which are later downloaded when the app is run
|
||||
/// we can't use those
|
||||
func installMacOSAppIsStub(_ apppath: String) -> Bool {
|
||||
let installESDdmg = (apppath as NSString).appendingPathComponent("Contents/SharedSupport/InstallESD.dmg")
|
||||
let sharedSupportDmg = (apppath as NSString).appendingPathComponent("Contents/SharedSupport/SharedSupport.dmg")
|
||||
let filemanager = FileManager.default
|
||||
return !(filemanager.fileExists(atPath: installESDdmg) ||
|
||||
filemanager.fileExists(atPath: sharedSupportDmg))
|
||||
}
|
||||
|
||||
/// Returns info parsed out of OS Installer app
|
||||
func getInfoFromInstallMacOSApp(_ appPath: String) throws -> PlistDict {
|
||||
var appInfo = PlistDict()
|
||||
@@ -128,62 +117,6 @@ func generateInstallableCondition(_ models: [String]) -> String {
|
||||
return predicates.joined(separator: " OR ")
|
||||
}
|
||||
|
||||
/// Returns pkginfo for a macOS installer on a disk image, using the startosinstall installation method
|
||||
func makeStartOSInstallPkgInfo(mountpoint: String, item: String) throws -> PlistDict {
|
||||
let appPath = (mountpoint as NSString).appendingPathComponent(item)
|
||||
guard pathIsInstallMacOSApp(appPath) else {
|
||||
throw MunkiError("Disk image item \(item) doesn't appear to be a macOS installer app")
|
||||
}
|
||||
let appName = (item as NSString).lastPathComponent
|
||||
let appInfo = try getInfoFromInstallMacOSApp(appPath)
|
||||
guard let version = appInfo["version"] as? String else {
|
||||
throw MunkiError("Could not parse version from \(item)")
|
||||
}
|
||||
let displayName = (appName as NSString).deletingPathExtension
|
||||
let munkiItemName = displayName.replacingOccurrences(of: " ", with: "_")
|
||||
let description = "Installs macOS version \(version)"
|
||||
|
||||
var installedSize = Int(18.5 * 1024 * 1024)
|
||||
var minimumMunkiVersion = "3.6.3"
|
||||
let minimumOSVersion = "10.9"
|
||||
if version.hasPrefix("10.14") {
|
||||
// https://support.apple.com/en-us/HT201475
|
||||
// use initial values
|
||||
} else if version.hasPrefix("11.") {
|
||||
// https://support.apple.com/en-us/HT211238
|
||||
installedSize = Int(35.5 * 1024 * 1024)
|
||||
minimumMunkiVersion = "5.1.0"
|
||||
} else if version.hasPrefix("12.") {
|
||||
// https://support.apple.com/en-us/HT212551
|
||||
installedSize = Int(26 * 1024 * 1024)
|
||||
minimumMunkiVersion = "5.1.0"
|
||||
} else {
|
||||
// no published guidance from Apple, just use same as Monterey
|
||||
installedSize = Int(26 * 1024 * 1024)
|
||||
minimumMunkiVersion = "5.1.0"
|
||||
}
|
||||
var pkginfo: PlistDict
|
||||
pkginfo = [
|
||||
"RestartAction": "RequireRestart",
|
||||
"apple_item": true,
|
||||
"description": description,
|
||||
"display_name": displayName,
|
||||
"installed_size": installedSize,
|
||||
"installer_type": "startosinstall",
|
||||
"minimum_munki_version": minimumMunkiVersion,
|
||||
"minimum_os_version": minimumOSVersion,
|
||||
"name": munkiItemName,
|
||||
"supported_architectures": ["x86_64"],
|
||||
"uninstallable": false,
|
||||
"version": version,
|
||||
]
|
||||
if let models = appInfo["SupportedDeviceModels"] as? [String] {
|
||||
pkginfo["installable_condition_disabled"] = generateInstallableCondition(models)
|
||||
}
|
||||
|
||||
return pkginfo
|
||||
}
|
||||
|
||||
/// Returns additional pkginfo from macOS installer at app_path,
|
||||
/// describing a stage_os_installer item
|
||||
func makeStageOSInstallerPkgInfo(_ appPath: String) throws -> PlistDict {
|
||||
|
||||
@@ -256,7 +256,7 @@ func processInstall(
|
||||
if installedState == .thisVersionNotInstalled {
|
||||
if !dependenciesMet {
|
||||
// we should not attempt to install
|
||||
display.warning("Didn't attempt ro install \(manifestItemName) because could not resolve all dependencies.")
|
||||
display.warning("Didn't attempt to install \(manifestItemName) because could not resolve all dependencies.")
|
||||
// add information to managed_installs so we have some feedback
|
||||
// to display in MSC.app
|
||||
processedItem["installed"] = false
|
||||
|
||||
@@ -207,10 +207,11 @@ func downloadIcons(_ itemList: [PlistDict]) {
|
||||
var iconsToKeep = [String]()
|
||||
let iconsDir = managedInstallsDir(subpath: "icons")
|
||||
let iconHashes = getIconHashes()
|
||||
let supportedIconExtensions = ["bmp", "gif", "icns", "jpg", "jpeg", "png", "psd", "tga", "tif", "tiff", "yuv"]
|
||||
|
||||
for item in itemList {
|
||||
var iconName = item["icon_name"] as? String ?? item["name"] as? String ?? "<unknown>"
|
||||
if (iconName as NSString).pathExtension.isEmpty {
|
||||
if !supportedIconExtensions.contains((iconName as NSString).pathExtension) {
|
||||
iconName += ".png"
|
||||
}
|
||||
iconsToKeep.append(iconName)
|
||||
|
||||
@@ -128,7 +128,9 @@ func installedState(_ pkginfo: PlistDict) async -> InstallationState {
|
||||
return .thisVersionInstalled
|
||||
}
|
||||
// do we have installs items?
|
||||
if let installItems = pkginfo["installs"] as? [PlistDict] {
|
||||
if let installItems = pkginfo["installs"] as? [PlistDict],
|
||||
!installItems.isEmpty
|
||||
{
|
||||
for item in installItems {
|
||||
do {
|
||||
let compareResult = try compareItem(item)
|
||||
@@ -145,6 +147,8 @@ func installedState(_ pkginfo: PlistDict) async -> InstallationState {
|
||||
}
|
||||
}
|
||||
} else if let receipts = pkginfo["receipts"] as? [PlistDict] {
|
||||
// if there are no 'installs' items, then we'll use receipt info
|
||||
// to determine install status.
|
||||
for item in receipts {
|
||||
do {
|
||||
let compareResult = try await compareReceipt(item)
|
||||
@@ -209,7 +213,9 @@ func someVersionInstalled(_ pkginfo: PlistDict) async -> Bool {
|
||||
return true
|
||||
}
|
||||
// do we have installs items?
|
||||
if let installItems = pkginfo["installs"] as? [PlistDict] {
|
||||
if let installItems = pkginfo["installs"] as? [PlistDict],
|
||||
!installItems.isEmpty
|
||||
{
|
||||
for item in installItems {
|
||||
do {
|
||||
let compareResult = try compareItem(item)
|
||||
@@ -300,6 +306,7 @@ func evidenceThisIsInstalled(_ pkginfo: PlistDict) async -> Bool {
|
||||
}
|
||||
var foundAllInstallItems = false
|
||||
if let installItems = pkginfo["installs"] as? [PlistDict],
|
||||
!installItems.isEmpty,
|
||||
(pkginfo["uninstall_method"] as? String ?? "") != "removepackages"
|
||||
{
|
||||
display.debug2("Checking 'installs' items...")
|
||||
|
||||
@@ -447,8 +447,8 @@ func checkForUpdates(clientID: String? = nil, localManifestPath: String? = nil)
|
||||
managedInstalls = nonStartOSInstallItems + startOSInstallItems
|
||||
installInfo["managed_installs"] = managedInstalls
|
||||
|
||||
if startOSInstallItems.count > 1 {
|
||||
display.warning("There are multiple startosinstall items in managed_installs. Only the install of the first one will be attempted.")
|
||||
if startOSInstallItems.count > 0 {
|
||||
display.warning("There are startosinstall items in managed_installs. This type of install is no longer supported.")
|
||||
}
|
||||
|
||||
// record detail before we throw it away...
|
||||
|
||||
@@ -211,7 +211,7 @@ func runCLI(_ tool: String,
|
||||
let task = Process()
|
||||
task.executableURL = URL(fileURLWithPath: tool)
|
||||
task.arguments = arguments
|
||||
if !environment.isEmpty == false {
|
||||
if !environment.isEmpty {
|
||||
task.environment = environment
|
||||
}
|
||||
|
||||
|
||||
@@ -53,3 +53,19 @@ func addTZOffsetToDate(_ date: Date) -> Date {
|
||||
// return new Date plus the offset
|
||||
return Date(timeInterval: secondsOffset, since: date)
|
||||
}
|
||||
|
||||
/// Returns an ISO 8601-formatted string in UTC for given date
|
||||
func ISO8601String(for date: Date) -> String {
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.formatOptions = [.withInternetDateTime]
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
|
||||
/// Retutns an RFC 3339-formatted string in the current time zone for given date
|
||||
func RFC3339String(for date: Date) -> String {
|
||||
// RFC 3339 date format like `2024-07-01 17:30:32-08:00`
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.timeZone = TimeZone.current
|
||||
formatter.formatOptions = [.withInternetDateTime, .withSpaceBetweenDateAndTime]
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
/// one single place to define a version for CLI tools
|
||||
let CLI_TOOLS_VERSION = "7.0.3"
|
||||
let CLI_TOOLS_VERSION = "7.0.4"
|
||||
let BUILD = "<BUILD_GOES_HERE>"
|
||||
|
||||
/// Returns version of Munki tools
|
||||
|
||||
Reference in New Issue
Block a user