mirror of
https://github.com/munki/munki.git
synced 2026-05-13 07:48:56 -05:00
Initial Swfit code for CLI tools; makecatalogs should be functional
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# .DS_Store files!
|
||||
.DS_Store
|
||||
|
||||
# Xcode user data
|
||||
*.xcodeproj/project.xcworkspace/
|
||||
*.xcodeproj/xcuserdata/
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// makecatalogs.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/25/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ArgumentParser
|
||||
|
||||
@main
|
||||
struct MakeCatalogs: ParsableCommand {
|
||||
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "makecatalogs",
|
||||
abstract: "Builds Munki catalogs from pkginfo files.",
|
||||
usage: "makecatalogs [options] [<repo_path>]"
|
||||
)
|
||||
|
||||
@Flag(name: [.long, .customShort("V")], help: "Print the version of the munki tools and exit.")
|
||||
var version = false
|
||||
|
||||
@Flag(name: .shortAndLong, help: "Disable sanity checks.")
|
||||
var force = false
|
||||
|
||||
@Flag(name: .shortAndLong,
|
||||
help: "Skip checking of pkg existence. Useful when pkgs aren't on the same server as pkginfo, catalogs and manifests.")
|
||||
var skipPkgCheck = false
|
||||
|
||||
@Option(name: [.customLong("repo-url"), .customLong("repo_url")],
|
||||
help: "Optional repo URL that takes precedence over the default repo_url specified via preferences.")
|
||||
var repoURL = ""
|
||||
|
||||
@Option(help: "Specify a custom plugin to connect to the Munki repo.")
|
||||
var plugin = "FileRepo"
|
||||
|
||||
@Argument(help: "Path to Munki repo")
|
||||
var repo_path = ""
|
||||
|
||||
var actual_repo_url = ""
|
||||
|
||||
mutating func validate() throws {
|
||||
if version {
|
||||
// asking for version info; we don't need to validate there's a repo URL
|
||||
return
|
||||
}
|
||||
// figure out what repo we're working with: we can get a repo URL one of three ways:
|
||||
// - as a file path provided at the command line
|
||||
// - as a --repo_url option
|
||||
// - as a preference stored in the com.googlecode.munki.munkiimport domain
|
||||
if !repo_path.isEmpty && !repoURL.isEmpty {
|
||||
// user has specified _both_ repo_path and repo_url!
|
||||
throw ValidationError("Please specify only one of --repo_url or <repo_path>!")
|
||||
}
|
||||
if !repo_path.isEmpty {
|
||||
// convert path to file URL
|
||||
if let repo_url_string = NSURL(fileURLWithPath: repo_path).absoluteString {
|
||||
actual_repo_url = repo_url_string
|
||||
}
|
||||
} else if !repoURL.isEmpty {
|
||||
actual_repo_url = repoURL
|
||||
} else if let pref_repo_url = adminPref("repo_url") as? String {
|
||||
actual_repo_url = pref_repo_url
|
||||
}
|
||||
|
||||
if actual_repo_url.isEmpty {
|
||||
throw ValidationError("Please specify --repo_url or a repo path.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mutating func run() throws {
|
||||
if version {
|
||||
print(getVersion())
|
||||
return
|
||||
}
|
||||
|
||||
let options = MakeCatalogOptions(
|
||||
skipPkgCheck: skipPkgCheck,
|
||||
force: force,
|
||||
verbose: true
|
||||
)
|
||||
|
||||
do {
|
||||
let repo = try repoConnect(url: actual_repo_url, plugin: plugin)
|
||||
// TODO: implement repo defining its own makecatalogs method
|
||||
// let errors = try repo.makecatalogs(options: options)
|
||||
var catalogsmaker = try CatalogsMaker(repo: repo, options: options)
|
||||
let errors = catalogsmaker.makecatalogs()
|
||||
if !errors.isEmpty {
|
||||
for error in errors {
|
||||
printStderr(error)
|
||||
}
|
||||
throw ExitCode(-1)
|
||||
}
|
||||
} catch RepoError.error(let description) {
|
||||
printStderr("Repo error: \(description)")
|
||||
throw ExitCode(-1)
|
||||
} catch MakeCatalogsError.PkginfoAccessError(let description) {
|
||||
printStderr("Pkginfo read error: \(description)")
|
||||
throw ExitCode(-1)
|
||||
} catch MakeCatalogsError.CatalogWriteError(let description) {
|
||||
printStderr("Catalog write error: \(description)")
|
||||
throw ExitCode(-1)
|
||||
} catch {
|
||||
printStderr("Unexpected error: \(error)")
|
||||
throw ExitCode(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// managedsoftwareupdate.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/24/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ArgumentParser
|
||||
|
||||
@main
|
||||
struct ManagedSoftwareUpdate: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "managedsoftwareupdate",
|
||||
usage: "maangedsoftwareupdate [options]"
|
||||
)
|
||||
|
||||
@Flag(name: [.long, .customShort("V")],
|
||||
help: "Print the version of the munki tools and exit.")
|
||||
var version = false
|
||||
|
||||
@Flag(name: .long,
|
||||
help: "Print the current configuration and exit.")
|
||||
var showConfig = false
|
||||
|
||||
mutating func run() throws {
|
||||
if version {
|
||||
print(getVersion())
|
||||
return
|
||||
}
|
||||
if showConfig {
|
||||
printConfig()
|
||||
return
|
||||
}
|
||||
print("Nothing much implemented yet!")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
C013643D2C2DC529008DB215 /* makecatalogslib.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013643B2C2DC529008DB215 /* makecatalogslib.swift */; };
|
||||
C01364402C2DCA5C008DB215 /* admincommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013643E2C2DCA5C008DB215 /* admincommon.swift */; };
|
||||
C01364412C2DCA5C008DB215 /* admincommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013643E2C2DCA5C008DB215 /* admincommon.swift */; };
|
||||
C01364432C2DD1BA008DB215 /* plistutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364422C2DD1BA008DB215 /* plistutils.swift */; };
|
||||
C01364442C2DD1BA008DB215 /* plistutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364422C2DD1BA008DB215 /* plistutils.swift */; };
|
||||
C01364452C2DD1BA008DB215 /* plistutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364422C2DD1BA008DB215 /* plistutils.swift */; };
|
||||
C01364462C2E051F008DB215 /* makecatalogslib.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013643B2C2DC529008DB215 /* makecatalogslib.swift */; };
|
||||
C013644A2C2F8EFE008DB215 /* GitFileRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364482C2F8EFE008DB215 /* GitFileRepo.swift */; };
|
||||
C013644B2C2F8EFE008DB215 /* GitFileRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364482C2F8EFE008DB215 /* GitFileRepo.swift */; };
|
||||
C013644E2C30F5D8008DB215 /* RepoFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013644C2C30F5D8008DB215 /* RepoFactory.swift */; };
|
||||
C013644F2C30F5D8008DB215 /* RepoFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013644C2C30F5D8008DB215 /* RepoFactory.swift */; };
|
||||
C01364522C311DFA008DB215 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C01364512C311DFA008DB215 /* ArgumentParser */; };
|
||||
C07A6FA92C2A82B400090743 /* managedsoftwareupdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FA82C2A82B400090743 /* managedsoftwareupdate.swift */; };
|
||||
C07A6FB02C2B22A400090743 /* prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FAF2C2B22A400090743 /* prefs.swift */; };
|
||||
C07A6FB22C2B22D300090743 /* constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FB12C2B22D300090743 /* constants.swift */; };
|
||||
C07A6FBA2C2B5ADE00090743 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FB92C2B5ADE00090743 /* main.swift */; };
|
||||
C07A6FBE2C2B5AF000090743 /* prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FAF2C2B22A400090743 /* prefs.swift */; };
|
||||
C07A6FBF2C2B5AF400090743 /* constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FB12C2B22D300090743 /* constants.swift */; };
|
||||
C07A6FC72C2B5C0700090743 /* makecatalogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FC62C2B5C0700090743 /* makecatalogs.swift */; };
|
||||
C07A6FCB2C2B5C3A00090743 /* prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FAF2C2B22A400090743 /* prefs.swift */; };
|
||||
C07A6FCC2C2B5C3D00090743 /* constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FB12C2B22D300090743 /* constants.swift */; };
|
||||
C07A6FCF2C2B62A600090743 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C07A6FCE2C2B62A600090743 /* ArgumentParser */; };
|
||||
C07A6FD22C2B654300090743 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD12C2B654300090743 /* utils.swift */; };
|
||||
C07A6FD32C2B659300090743 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD12C2B654300090743 /* utils.swift */; };
|
||||
C07A6FD42C2B659400090743 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD12C2B654300090743 /* utils.swift */; };
|
||||
C07A6FD72C2B7F2100090743 /* FileRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD52C2B7F2100090743 /* FileRepo.swift */; };
|
||||
C07A6FD82C2B7F2100090743 /* FileRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD52C2B7F2100090743 /* FileRepo.swift */; };
|
||||
C07A6FDA2C2CF19600090743 /* cliutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD92C2CF19600090743 /* cliutils.swift */; };
|
||||
C07A6FDB2C2CF19600090743 /* cliutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD92C2CF19600090743 /* cliutils.swift */; };
|
||||
C07A6FDC2C2CF19600090743 /* cliutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD92C2CF19600090743 /* cliutils.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
C07A6FA32C2A82B400090743 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
C07A6FB52C2B5ADE00090743 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
C07A6FC22C2B5C0700090743 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
C013643B2C2DC529008DB215 /* makecatalogslib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = makecatalogslib.swift; sourceTree = "<group>"; };
|
||||
C013643E2C2DCA5C008DB215 /* admincommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = admincommon.swift; sourceTree = "<group>"; };
|
||||
C01364422C2DD1BA008DB215 /* plistutils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = plistutils.swift; sourceTree = "<group>"; };
|
||||
C01364482C2F8EFE008DB215 /* GitFileRepo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitFileRepo.swift; sourceTree = "<group>"; };
|
||||
C013644C2C30F5D8008DB215 /* RepoFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepoFactory.swift; sourceTree = "<group>"; };
|
||||
C07A6FA52C2A82B400090743 /* managedsoftwareupdate */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = managedsoftwareupdate; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C07A6FA82C2A82B400090743 /* managedsoftwareupdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = managedsoftwareupdate.swift; sourceTree = "<group>"; };
|
||||
C07A6FAF2C2B22A400090743 /* prefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = prefs.swift; sourceTree = "<group>"; };
|
||||
C07A6FB12C2B22D300090743 /* constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = constants.swift; sourceTree = "<group>"; };
|
||||
C07A6FB72C2B5ADE00090743 /* munkitester */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = munkitester; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C07A6FB92C2B5ADE00090743 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
C07A6FC42C2B5C0700090743 /* makecatalogs */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = makecatalogs; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C07A6FC62C2B5C0700090743 /* makecatalogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = makecatalogs.swift; sourceTree = "<group>"; };
|
||||
C07A6FD12C2B654300090743 /* utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = utils.swift; sourceTree = "<group>"; };
|
||||
C07A6FD52C2B7F2100090743 /* FileRepo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRepo.swift; sourceTree = "<group>"; };
|
||||
C07A6FD92C2CF19600090743 /* cliutils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = cliutils.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
C07A6FA22C2A82B400090743 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C01364522C311DFA008DB215 /* ArgumentParser in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C07A6FB42C2B5ADE00090743 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C07A6FC12C2B5C0700090743 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C07A6FCF2C2B62A600090743 /* ArgumentParser in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
C013643A2C2DC50E008DB215 /* admin */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C013643B2C2DC529008DB215 /* makecatalogslib.swift */,
|
||||
C013643E2C2DCA5C008DB215 /* admincommon.swift */,
|
||||
);
|
||||
path = admin;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C01364472C2F8EC0008DB215 /* munkirepo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C07A6FD52C2B7F2100090743 /* FileRepo.swift */,
|
||||
C01364482C2F8EFE008DB215 /* GitFileRepo.swift */,
|
||||
C013644C2C30F5D8008DB215 /* RepoFactory.swift */,
|
||||
);
|
||||
path = munkirepo;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C01364502C311DFA008DB215 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C07A6F9C2C2A82B400090743 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C07A6FD02C2B631800090743 /* shared */,
|
||||
C07A6FA72C2A82B400090743 /* managedsoftwareupdate */,
|
||||
C07A6FB82C2B5ADE00090743 /* munkitester */,
|
||||
C07A6FC52C2B5C0700090743 /* makecatalogs */,
|
||||
C07A6FA62C2A82B400090743 /* Products */,
|
||||
C01364502C311DFA008DB215 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C07A6FA62C2A82B400090743 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C07A6FA52C2A82B400090743 /* managedsoftwareupdate */,
|
||||
C07A6FB72C2B5ADE00090743 /* munkitester */,
|
||||
C07A6FC42C2B5C0700090743 /* makecatalogs */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C07A6FA72C2A82B400090743 /* managedsoftwareupdate */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C07A6FA82C2A82B400090743 /* managedsoftwareupdate.swift */,
|
||||
);
|
||||
path = managedsoftwareupdate;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C07A6FB82C2B5ADE00090743 /* munkitester */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C07A6FB92C2B5ADE00090743 /* main.swift */,
|
||||
);
|
||||
path = munkitester;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C07A6FC52C2B5C0700090743 /* makecatalogs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C07A6FC62C2B5C0700090743 /* makecatalogs.swift */,
|
||||
);
|
||||
path = makecatalogs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C07A6FD02C2B631800090743 /* shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C01364472C2F8EC0008DB215 /* munkirepo */,
|
||||
C013643A2C2DC50E008DB215 /* admin */,
|
||||
C07A6FAF2C2B22A400090743 /* prefs.swift */,
|
||||
C07A6FB12C2B22D300090743 /* constants.swift */,
|
||||
C07A6FD12C2B654300090743 /* utils.swift */,
|
||||
C07A6FD92C2CF19600090743 /* cliutils.swift */,
|
||||
C01364422C2DD1BA008DB215 /* plistutils.swift */,
|
||||
);
|
||||
path = shared;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
C07A6FA42C2A82B400090743 /* managedsoftwareupdate */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = C07A6FAC2C2A82B400090743 /* Build configuration list for PBXNativeTarget "managedsoftwareupdate" */;
|
||||
buildPhases = (
|
||||
C07A6FA12C2A82B400090743 /* Sources */,
|
||||
C07A6FA22C2A82B400090743 /* Frameworks */,
|
||||
C07A6FA32C2A82B400090743 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = managedsoftwareupdate;
|
||||
packageProductDependencies = (
|
||||
C01364512C311DFA008DB215 /* ArgumentParser */,
|
||||
);
|
||||
productName = munki;
|
||||
productReference = C07A6FA52C2A82B400090743 /* managedsoftwareupdate */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
C07A6FB62C2B5ADE00090743 /* munkitester */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = C07A6FBB2C2B5ADE00090743 /* Build configuration list for PBXNativeTarget "munkitester" */;
|
||||
buildPhases = (
|
||||
C07A6FB32C2B5ADE00090743 /* Sources */,
|
||||
C07A6FB42C2B5ADE00090743 /* Frameworks */,
|
||||
C07A6FB52C2B5ADE00090743 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = munkitester;
|
||||
productName = munkitester;
|
||||
productReference = C07A6FB72C2B5ADE00090743 /* munkitester */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
C07A6FC32C2B5C0700090743 /* makecatalogs */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = C07A6FC82C2B5C0700090743 /* Build configuration list for PBXNativeTarget "makecatalogs" */;
|
||||
buildPhases = (
|
||||
C07A6FC02C2B5C0700090743 /* Sources */,
|
||||
C07A6FC12C2B5C0700090743 /* Frameworks */,
|
||||
C07A6FC22C2B5C0700090743 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = makecatalogs;
|
||||
packageProductDependencies = (
|
||||
C07A6FCE2C2B62A600090743 /* ArgumentParser */,
|
||||
);
|
||||
productName = makecatalogs;
|
||||
productReference = C07A6FC42C2B5C0700090743 /* makecatalogs */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
C07A6F9D2C2A82B400090743 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1540;
|
||||
LastUpgradeCheck = 1540;
|
||||
TargetAttributes = {
|
||||
C07A6FA42C2A82B400090743 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
};
|
||||
C07A6FB62C2B5ADE00090743 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
};
|
||||
C07A6FC32C2B5C0700090743 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = C07A6FA02C2A82B400090743 /* Build configuration list for PBXProject "munki" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = C07A6F9C2C2A82B400090743;
|
||||
packageReferences = (
|
||||
C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
|
||||
);
|
||||
productRefGroup = C07A6FA62C2A82B400090743 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
C07A6FA42C2A82B400090743 /* managedsoftwareupdate */,
|
||||
C07A6FB62C2B5ADE00090743 /* munkitester */,
|
||||
C07A6FC32C2B5C0700090743 /* makecatalogs */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
C07A6FA12C2A82B400090743 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C07A6FDA2C2CF19600090743 /* cliutils.swift in Sources */,
|
||||
C01364432C2DD1BA008DB215 /* plistutils.swift in Sources */,
|
||||
C07A6FB02C2B22A400090743 /* prefs.swift in Sources */,
|
||||
C07A6FA92C2A82B400090743 /* managedsoftwareupdate.swift in Sources */,
|
||||
C07A6FB22C2B22D300090743 /* constants.swift in Sources */,
|
||||
C07A6FD22C2B654300090743 /* utils.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C07A6FB32C2B5ADE00090743 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C01364442C2DD1BA008DB215 /* plistutils.swift in Sources */,
|
||||
C01364462C2E051F008DB215 /* makecatalogslib.swift in Sources */,
|
||||
C07A6FDB2C2CF19600090743 /* cliutils.swift in Sources */,
|
||||
C013644E2C30F5D8008DB215 /* RepoFactory.swift in Sources */,
|
||||
C07A6FBE2C2B5AF000090743 /* prefs.swift in Sources */,
|
||||
C07A6FBA2C2B5ADE00090743 /* main.swift in Sources */,
|
||||
C07A6FBF2C2B5AF400090743 /* constants.swift in Sources */,
|
||||
C013644A2C2F8EFE008DB215 /* GitFileRepo.swift in Sources */,
|
||||
C07A6FD32C2B659300090743 /* utils.swift in Sources */,
|
||||
C07A6FD72C2B7F2100090743 /* FileRepo.swift in Sources */,
|
||||
C01364402C2DCA5C008DB215 /* admincommon.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C07A6FC02C2B5C0700090743 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C07A6FDC2C2CF19600090743 /* cliutils.swift in Sources */,
|
||||
C01364452C2DD1BA008DB215 /* plistutils.swift in Sources */,
|
||||
C07A6FCB2C2B5C3A00090743 /* prefs.swift in Sources */,
|
||||
C013644F2C30F5D8008DB215 /* RepoFactory.swift in Sources */,
|
||||
C07A6FC72C2B5C0700090743 /* makecatalogs.swift in Sources */,
|
||||
C07A6FCC2C2B5C3D00090743 /* constants.swift in Sources */,
|
||||
C07A6FD42C2B659400090743 /* utils.swift in Sources */,
|
||||
C013644B2C2F8EFE008DB215 /* GitFileRepo.swift in Sources */,
|
||||
C07A6FD82C2B7F2100090743 /* FileRepo.swift in Sources */,
|
||||
C01364412C2DCA5C008DB215 /* admincommon.swift in Sources */,
|
||||
C013643D2C2DC529008DB215 /* makecatalogslib.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
C07A6FAA2C2A82B400090743 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.5;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
C07A6FAB2C2A82B400090743 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.5;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
C07A6FAD2C2A82B400090743 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 52ZFZKM6BK;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
C07A6FAE2C2A82B400090743 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 52ZFZKM6BK;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
C07A6FBC2C2B5ADE00090743 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 52ZFZKM6BK;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
C07A6FBD2C2B5ADE00090743 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 52ZFZKM6BK;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
C07A6FC92C2B5C0700090743 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 52ZFZKM6BK;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
C07A6FCA2C2B5C0700090743 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 52ZFZKM6BK;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
C07A6FA02C2A82B400090743 /* Build configuration list for PBXProject "munki" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C07A6FAA2C2A82B400090743 /* Debug */,
|
||||
C07A6FAB2C2A82B400090743 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
C07A6FAC2C2A82B400090743 /* Build configuration list for PBXNativeTarget "managedsoftwareupdate" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C07A6FAD2C2A82B400090743 /* Debug */,
|
||||
C07A6FAE2C2A82B400090743 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
C07A6FBB2C2B5ADE00090743 /* Build configuration list for PBXNativeTarget "munkitester" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C07A6FBC2C2B5ADE00090743 /* Debug */,
|
||||
C07A6FBD2C2B5ADE00090743 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
C07A6FC82C2B5C0700090743 /* Build configuration list for PBXNativeTarget "makecatalogs" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C07A6FC92C2B5C0700090743 /* Debug */,
|
||||
C07A6FCA2C2B5C0700090743 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/apple/swift-argument-parser.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.4.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
C01364512C311DFA008DB215 /* ArgumentParser */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
|
||||
productName = ArgumentParser;
|
||||
};
|
||||
C07A6FCE2C2B62A600090743 /* ArgumentParser */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
|
||||
productName = ArgumentParser;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = C07A6F9D2C2A82B400090743 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"originHash" : "59ba1edda695b389d6c9ac1809891cd779e4024f505b0ce1a9d5202b6762e38a",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-argument-parser",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-argument-parser.git",
|
||||
"state" : {
|
||||
"revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b",
|
||||
"version" : "1.4.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// main.swift
|
||||
// munkitester
|
||||
//
|
||||
// Created by Greg Neagle on 6/25/24.
|
||||
//
|
||||
|
||||
// this is a temporary target to use to test things
|
||||
|
||||
import Foundation
|
||||
|
||||
/*
|
||||
do {
|
||||
let repo = try FileRepo("file:///Users/Shared/munki_repo")
|
||||
let pkgsinfo = try repo.itemlist("pkgsinfo")
|
||||
print(pkgsinfo)
|
||||
let data = try repo.get("manifests/site_default")
|
||||
if let plist = NSString(data: data, encoding: NSUTF8StringEncoding) {
|
||||
print(plist)
|
||||
}
|
||||
try repo.get("manifests/site_default", toFile: "/tmp/sitr_default")
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
do {
|
||||
let repo = try FileRepo("file:///Users/Shared/munki_repo")
|
||||
print(repo.baseurl)
|
||||
print(repo.root)
|
||||
let catalogsmaker = try CatalogsMaker(repo: repo)
|
||||
let errors = catalogsmaker.makecatalogs()
|
||||
if !errors.isEmpty {
|
||||
print(catalogsmaker.errors)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
let options = MakeCatalogOptions(
|
||||
skip_payload_check: true,
|
||||
force: false,
|
||||
verbose: true
|
||||
)
|
||||
|
||||
do {
|
||||
let repo = try repoConnect(url: "file:///Users/Shared/munki_repo")
|
||||
var catalogsmaker = try CatalogsMaker(repo: repo, options: options)
|
||||
let errors = catalogsmaker.makecatalogs()
|
||||
if !errors.isEmpty {
|
||||
for error in errors {
|
||||
printStderr(error)
|
||||
}
|
||||
exit(-1)
|
||||
}
|
||||
} catch RepoError.error(let description) {
|
||||
printStderr("Repo error: \(description)")
|
||||
exit(-1)
|
||||
} catch MakeCatalogsError.PkginfoAccessError(let description) {
|
||||
printStderr("Pkginfo read error: \(description)")
|
||||
exit(-1)
|
||||
} catch MakeCatalogsError.CatalogWriteError(let description) {
|
||||
printStderr("Catalog write error: \(description)")
|
||||
exit(-1)
|
||||
} catch {
|
||||
printStderr("Unexpected error: \(error)")
|
||||
exit(-1)
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
do {
|
||||
let repo = try repoConnect(
|
||||
url: "file:///Users/Shared/munki_repo",
|
||||
plugin: "GitFileRepo"
|
||||
)
|
||||
let localFilePath = "/Users/Shared/munki_repo/manifests/site_default"
|
||||
let identifier = "manifests/foo"
|
||||
try repo.put(identifier, content: Data())
|
||||
try repo.put(identifier, fromFile: localFilePath)
|
||||
try repo.delete(identifier)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// admincommon.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/27/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let ADMIN_BUNDLE_ID = "com.googlecode.munki.munkiimport" as CFString
|
||||
|
||||
func adminPref(_ pref_name: String) -> Any? {
|
||||
/* Return an admin preference. Since this uses CFPreferencesCopyAppValue,
|
||||
Preferences can be defined several places. Precedence is:
|
||||
- MCX/configuration profile
|
||||
- ~/Library/Preferences/ByHost/com.googlecode.munki.munkiimport.XXXXXX.plist
|
||||
- ~/Library/Preferences/com.googlecode.munki.munkiimport.plist
|
||||
- /Library/Preferences/com.googlecode.munki.munkiimport.plist
|
||||
- .GlobalPreferences defined at various levels (ByHost, user, system)
|
||||
But typically these preferences are _not_ managed and are stored in the
|
||||
user's preferences (~/Library/Preferences/com.googlecode.munki.munkiimport.plist)
|
||||
*/
|
||||
return CFPreferencesCopyAppValue(pref_name as CFString, ADMIN_BUNDLE_ID)
|
||||
}
|
||||
|
||||
func listItemsOfKind(_ repo: Repo, _ kind: String) throws -> [String] {
|
||||
// Returns a list of items of kind. Relative pathnames are prepended
|
||||
// with kind. (example: ["icons/Bar.png", "icons/Foo.png"])
|
||||
// Could throw RepoError
|
||||
let itemlist = try repo.list(kind)
|
||||
return itemlist.map(
|
||||
{ (kind as NSString).appendingPathComponent($0) }
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
//
|
||||
// makecatalogslib.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/27/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
enum MakeCatalogsError: Error {
|
||||
case PkginfoAccessError(description: String)
|
||||
case CatalogWriteError(description: String)
|
||||
}
|
||||
|
||||
struct MakeCatalogOptions {
|
||||
var skipPkgCheck: Bool = false
|
||||
var force: Bool = false
|
||||
var verbose: Bool = false
|
||||
}
|
||||
|
||||
struct CatalogsMaker {
|
||||
var repo: Repo
|
||||
var options: MakeCatalogOptions
|
||||
var pkgsinfoList: [String]
|
||||
var pkgsList: [String]
|
||||
var catalogs: [String:[PlistDict]]
|
||||
var errors: [String]
|
||||
|
||||
init(repo: Repo,
|
||||
options: MakeCatalogOptions = MakeCatalogOptions() ) throws {
|
||||
self.repo = repo
|
||||
self.options = options
|
||||
catalogs = [String:[PlistDict]]()
|
||||
errors = [String]()
|
||||
pkgsinfoList = [String]()
|
||||
pkgsList = [String]()
|
||||
try getPkgsinfoList()
|
||||
try getPkgsList()
|
||||
}
|
||||
|
||||
mutating func getPkgsinfoList() throws {
|
||||
// returns a list of pkginfo identifiers
|
||||
do {
|
||||
pkgsinfoList = try listItemsOfKind(repo, "pkgsinfo")
|
||||
} catch is RepoError {
|
||||
throw MakeCatalogsError.PkginfoAccessError(
|
||||
description: "Error getting list of pkgsinfo items" )
|
||||
}
|
||||
}
|
||||
|
||||
mutating func getPkgsList() throws {
|
||||
// returns a list of pkg identifiers
|
||||
do {
|
||||
pkgsList = try listItemsOfKind(repo, "pkgs")
|
||||
} catch is RepoError {
|
||||
throw MakeCatalogsError.PkginfoAccessError(
|
||||
description: "Error getting list of pkgs items" )
|
||||
}
|
||||
}
|
||||
|
||||
mutating func hashIcons() -> [String:String] {
|
||||
// Builds a dictionary containing hashes for all our repo icons
|
||||
if options.verbose {
|
||||
print("Getting list of icons...")
|
||||
}
|
||||
var iconHashes = [String:String]()
|
||||
if var iconList = try? repo.list("icons") {
|
||||
// remove plist of hashes from the list
|
||||
if let index = iconList.firstIndex(of: "_icon_hashes.plist") {
|
||||
iconList.remove(at: index)
|
||||
}
|
||||
for icon in iconList {
|
||||
if options.verbose {
|
||||
print("Hashing \(icon)...")
|
||||
}
|
||||
do {
|
||||
let icondata = try repo.get("icons/" + icon)
|
||||
let hashed = SHA256.hash(data: icondata)
|
||||
let hashString = hashed.compactMap { String(format: "%02x", $0) }.joined()
|
||||
iconHashes[icon] = hashString
|
||||
} catch RepoError.error(let description) {
|
||||
errors.append("RepoError reading icons/\(icon): \(description)")
|
||||
} catch {
|
||||
errors.append("Unexpected error reading icons/\(icon): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
return iconHashes
|
||||
}
|
||||
|
||||
func caseInsensitivePkgsListContains(_ installer_item: String) -> String? {
|
||||
// returns a case-insentitive match for installer_item from pkgsList, if any
|
||||
for repo_pkg in pkgsList {
|
||||
if installer_item.lowercased() == repo_pkg.lowercased() {
|
||||
return repo_pkg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
mutating func verify(_ identifier: String, _ pkginfo: PlistDict) -> Bool {
|
||||
// Returns true if referenced installer items are present,
|
||||
// false otherwise. Updates list of errors.
|
||||
if let installer_type = pkginfo["installer_type"] as? String {
|
||||
if ["nopkg", "apple_update_metadata"].contains(installer_type) {
|
||||
// no associated installer item (pkg) for these types
|
||||
return true
|
||||
}
|
||||
}
|
||||
if !((pkginfo["PackageCompleteURL"] as? String ?? "").isEmpty) {
|
||||
// installer item may be on a different server
|
||||
return true
|
||||
}
|
||||
if !((pkginfo["PackageURL"] as? String ?? "").isEmpty) {
|
||||
// installer item may be on a different server
|
||||
return true
|
||||
}
|
||||
|
||||
// Build path to installer item
|
||||
let installeritemlocation = pkginfo["installer_item_location"] as? String ?? ""
|
||||
if installeritemlocation.isEmpty {
|
||||
errors.append(
|
||||
"WARNING: empty or invalid installer_item_location in \(identifier)")
|
||||
return false
|
||||
}
|
||||
let installeritempath = "pkgs/" + installeritemlocation
|
||||
|
||||
// Check if the installer item actually exists
|
||||
if !(pkgsList.contains(installeritempath)) {
|
||||
// didn't find it in the pkgsList; let's look case-insensitive
|
||||
if let match = caseInsensitivePkgsListContains(installeritempath) {
|
||||
errors.append(
|
||||
"WARNING: \(identifier) refers to installer item: \(installeritemlocation). The pathname of the item in the repo has different case: \(match). This may cause issues depending on the case-sensitivity of the underlying filesystem."
|
||||
)
|
||||
} else {
|
||||
errors.append(
|
||||
"WARNING: \(identifier) refers to missing installer item: \(installeritemlocation)"
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// uninstaller checking
|
||||
if let uninstalleritemlocation = pkginfo["uninstaller_item_location"] as? String {
|
||||
if uninstalleritemlocation.isEmpty {
|
||||
errors.append(
|
||||
"WARNING: empty or invalid uninstaller_item_location in \(identifier)")
|
||||
return false
|
||||
}
|
||||
let uninstalleritempath = "pkgs/" + uninstalleritemlocation
|
||||
// Check if the uninstaller item actually exists
|
||||
if !(pkgsList.contains(uninstalleritempath)) {
|
||||
// didn't find it in the pkgsList; let's look case-insensitive
|
||||
if let match = caseInsensitivePkgsListContains(uninstalleritempath) {
|
||||
errors.append(
|
||||
"WARNING: \(identifier) refers to uninstaller item: \(uninstalleritemlocation). The pathname of the item in the repo has different case: \(match). This may cause issues depending on the case-sensitivity of the underlying filesystem."
|
||||
)
|
||||
} else {
|
||||
errors.append(
|
||||
"WARNING: \(identifier) refers to missing uninstaller item: \(uninstalleritemlocation)"
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we get here we passed all the checks
|
||||
return true
|
||||
}
|
||||
|
||||
mutating func processPkgsinfo() {
|
||||
// Processes pkginfo files and updates catalogs and errors instance variables
|
||||
catalogs["all"] = [PlistDict]()
|
||||
// Walk through the pkginfo files
|
||||
for pkginfoIdentifier in pkgsinfoList {
|
||||
// Try to read the pkginfo file
|
||||
var pkginfo = PlistDict()
|
||||
do {
|
||||
let data = try repo.get(pkginfoIdentifier)
|
||||
pkginfo = try readPlistFromData(data) as? PlistDict ?? PlistDict()
|
||||
} catch {
|
||||
errors.append("Unexpected error reading \(pkginfoIdentifier): \(error)")
|
||||
continue
|
||||
}
|
||||
if !(pkginfo.keys.contains("name")) {
|
||||
errors.append("WARNING: \(pkginfoIdentifier)is missing name key")
|
||||
continue
|
||||
}
|
||||
// don't copy admin notes to catalogs
|
||||
if pkginfo.keys.contains("notes") {
|
||||
pkginfo["notes"] = nil
|
||||
}
|
||||
// strip out any keys that start with "_"
|
||||
for key in pkginfo.keys {
|
||||
if key.hasPrefix("_") {
|
||||
pkginfo[key] = nil
|
||||
}
|
||||
}
|
||||
// sanity checking
|
||||
if !options.skipPkgCheck {
|
||||
let verified = verify(pkginfoIdentifier, pkginfo)
|
||||
if !verified && !options.force {
|
||||
// Skip this pkginfo unless we're running with force flag
|
||||
continue
|
||||
}
|
||||
}
|
||||
// append the pkginfo to the relevant catalogs
|
||||
catalogs["all"]?.append(pkginfo)
|
||||
if let catalog_list = pkginfo["catalogs"] as? [String] {
|
||||
if catalog_list.isEmpty {
|
||||
errors.append("WARNING: \(pkginfoIdentifier)) has an empty catalogs array!")
|
||||
} else {
|
||||
for catalog in catalog_list {
|
||||
if !catalogs.keys.contains(catalog) {
|
||||
catalogs[catalog] = [PlistDict]()
|
||||
}
|
||||
catalogs[catalog]?.append(pkginfo)
|
||||
if options.verbose {
|
||||
print("Adding \(pkginfoIdentifier) to \(catalog)...")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.append("WARNING: \(pkginfoIdentifier)) has no catalogs array!")
|
||||
}
|
||||
}
|
||||
// look for catalog names that differ only in case
|
||||
var duplicateCatalogs = [String]()
|
||||
for name in catalogs.keys {
|
||||
let filtered_lowercase_names = catalogs.keys.filter( { $0 != name } ).map( { $0.lowercased() })
|
||||
if filtered_lowercase_names.contains(name.lowercased()) {
|
||||
duplicateCatalogs.append(name)
|
||||
}
|
||||
}
|
||||
if !duplicateCatalogs.isEmpty {
|
||||
errors.append(
|
||||
"WARNING: There are catalogs with names that differ only by case. " +
|
||||
"This may cause issues depending on the case-sensitivity of the " +
|
||||
"underlying filesystem: \(duplicateCatalogs)")
|
||||
}
|
||||
}
|
||||
|
||||
mutating func cleanupCatalogs() {
|
||||
// clear out old catalogs
|
||||
do {
|
||||
let catalogList = try repo.list("catalogs")
|
||||
for catalogName in catalogList {
|
||||
if !(catalogs.keys.contains(catalogName)) {
|
||||
let catalogIdentifier = "catalogs/" + catalogName
|
||||
do {
|
||||
try repo.delete(catalogIdentifier)
|
||||
} catch {
|
||||
errors.append("Could not delete catalog \(catalogName): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
errors.append("Could not get list of current catalogs to clean up: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
mutating func makecatalogs() -> [String] {
|
||||
// Assembles all pkginfo files into catalogs.
|
||||
// User calling this needs to be able to write to the repo/catalogs
|
||||
// directory.
|
||||
// Returns a list of any errors it encountered
|
||||
|
||||
// process pkgsinfo items
|
||||
processPkgsinfo()
|
||||
|
||||
// clean up old catalogs no longer needed
|
||||
cleanupCatalogs()
|
||||
|
||||
// write the new catalogs
|
||||
for key in catalogs.keys {
|
||||
if !(catalogs[key]?.isEmpty ?? true) {
|
||||
let catalogIdentifier = "catalogs/" + key
|
||||
do {
|
||||
if let value = catalogs[key] {
|
||||
let data = try plistToData(value)
|
||||
try repo.put(catalogIdentifier, content: data)
|
||||
if options.verbose {
|
||||
print("Created \(catalogIdentifier)...")
|
||||
}
|
||||
}
|
||||
} catch PlistError.writeError(let description) {
|
||||
errors.append("Could not serialize catalog \(key): \(description)")
|
||||
} catch RepoError.error(let description){
|
||||
errors.append("Failed to create catalog \(key): \(description)")
|
||||
} catch {
|
||||
errors.append("Unexpected error creating catalog \(key): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make icon hashes
|
||||
let iconHashes = hashIcons()
|
||||
// create icon_hashes resource
|
||||
if !iconHashes.isEmpty {
|
||||
let iconHashesIdentifier = "icons/_icon_hashes.plist"
|
||||
do {
|
||||
let iconHashesData = try plistToData(iconHashes)
|
||||
try repo.put(iconHashesIdentifier, content: iconHashesData)
|
||||
if options.verbose {
|
||||
print("Created \(iconHashesIdentifier)...")
|
||||
}
|
||||
} catch PlistError.writeError(let description) {
|
||||
errors.append("Could not serialize icon hashes: \(description)")
|
||||
} catch RepoError.error(let description){
|
||||
errors.append("Failed to create \(iconHashesIdentifier): \(description)")
|
||||
} catch {
|
||||
errors.append("Unexpected error creating \(iconHashesIdentifier): \(error)")
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// cliutils.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/26/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func printStderr(_ items: Any..., separator: String = " ", terminator: String = "\n") {
|
||||
// similar to print() function, but prints to stderr
|
||||
let output = items
|
||||
.map { String(describing: $0) }
|
||||
.joined(separator: separator) + terminator
|
||||
|
||||
FileHandle.standardError.write(output.data(using: .utf8)!)
|
||||
}
|
||||
|
||||
func trimTrailingNewline(_ s: String) -> String {
|
||||
var trimmedString = s
|
||||
if trimmedString.last == "\n" {
|
||||
trimmedString = String(trimmedString.dropLast())
|
||||
}
|
||||
return trimmedString
|
||||
}
|
||||
|
||||
struct CLIResults {
|
||||
var exitcode: Int = 0
|
||||
var output: String = ""
|
||||
var error: String = ""
|
||||
}
|
||||
|
||||
func runCLI(_ tool: String, arguments: [String] = [], stdIn: String = "") -> CLIResults {
|
||||
// runs a command line tool synchronously, returns CLIResults
|
||||
// not a good choice for tools that might generate a lot of output or error output
|
||||
let inPipe = Pipe.init()
|
||||
let outPipe = Pipe.init()
|
||||
let errorPipe = Pipe.init()
|
||||
|
||||
let task = Process.init()
|
||||
task.launchPath = tool
|
||||
task.arguments = arguments
|
||||
|
||||
task.standardInput = inPipe
|
||||
task.standardOutput = outPipe
|
||||
task.standardError = errorPipe
|
||||
|
||||
task.launch()
|
||||
if stdIn != "" {
|
||||
if let data = stdIn.data(using: .utf8) {
|
||||
inPipe.fileHandleForWriting.write(data)
|
||||
}
|
||||
}
|
||||
inPipe.fileHandleForWriting.closeFile()
|
||||
task.waitUntilExit()
|
||||
|
||||
let outputData = outPipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let outputString = trimTrailingNewline(String(data: outputData, encoding: .utf8) ?? "")
|
||||
outPipe.fileHandleForReading.closeFile()
|
||||
|
||||
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let errorString = trimTrailingNewline(String(data: errorData, encoding: .utf8) ?? "")
|
||||
errorPipe.fileHandleForReading.closeFile()
|
||||
|
||||
return (CLIResults(
|
||||
exitcode: Int(task.terminationStatus),
|
||||
output: outputString,
|
||||
error: errorString)
|
||||
)
|
||||
}
|
||||
|
||||
enum CalledProcessError: Error {
|
||||
case error(description: String)
|
||||
}
|
||||
|
||||
func checkCall(_ tool: String, arguments: [String] = [], stdIn: String = "") throws -> String {
|
||||
// like Python's subprocess.check_call
|
||||
let result = runCLI(tool, arguments: arguments, stdIn: stdIn)
|
||||
if result.exitcode != 0 {
|
||||
throw CalledProcessError.error(description: result.error)
|
||||
}
|
||||
return result.output
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// constants.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/25/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// NOTE: it's very important that defined exit codes are never changed!
|
||||
// Preflight exit codes
|
||||
let EXIT_STATUS_PREFLIGHT_FAILURE = 1
|
||||
// Client config exit codes.
|
||||
let EXIT_STATUS_OBJC_MISSING = 100 // no longer relevant
|
||||
let EXIT_STATUS_MUNKI_DIRS_FAILURE = 101
|
||||
// Server connection exit codes.
|
||||
let EXIT_STATUS_SERVER_UNAVAILABLE = 150
|
||||
// User related exit codes.
|
||||
let EXIT_STATUS_INVALID_PARAMETERS = 200
|
||||
let EXIT_STATUS_ROOT_REQUIRED = 201
|
||||
|
||||
let BUNDLE_ID = "ManagedInstalls" as CFString
|
||||
let DEFAULT_GUI_CACHE_AGE_SECS = 3600
|
||||
let WRITEABLE_SELF_SERVICE_MANIFEST_PATH = "/Users/Shared/.SelfServeManifest"
|
||||
|
||||
let ADDITIONAL_HTTP_HEADERS_KEY = "AdditionalHttpHeaders"
|
||||
|
||||
let LOGINWINDOW = "/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow"
|
||||
|
||||
let CHECKANDINSTALLATSTARTUPFLAG = "/Users/Shared/.com.googlecode.munki.checkandinstallatstartup"
|
||||
let INSTALLATSTARTUPFLAG = "/Users/Shared/.com.googlecode.munki.installatstartup"
|
||||
let INSTALLATLOGOUTFLAG = "/private/tmp/com.googlecode.munki.installatlogout"
|
||||
let UPDATECHECKLAUNCHFILE = "/private/tmp/.com.googlecode.munki.updatecheck.launchd"
|
||||
let INSTALLWITHOUTLOGOUTFILE = "/private/tmp/.com.googlecode.munki.managedinstall.launchd"
|
||||
|
||||
// postinstall actions
|
||||
let POSTACTION_NONE = 0
|
||||
let POSTACTION_LOGOUT = 1
|
||||
let POSTACTION_RESTART = 2
|
||||
let POSTACTION_SHUTDOWN = 4
|
||||
|
||||
typealias PlistDict = [String:Any]
|
||||
@@ -0,0 +1,282 @@
|
||||
//
|
||||
// munkirepo.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/25/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import NetFS
|
||||
|
||||
// Base classes
|
||||
enum RepoError: Error {
|
||||
/// General error class for repo errors
|
||||
case error(description: String)
|
||||
}
|
||||
|
||||
protocol Repo {
|
||||
// Defines methods all repo classes must implement
|
||||
init(_ url: String) throws
|
||||
func list(_ kind: String) throws -> [String]
|
||||
func get(_ identifier: String) throws -> Data
|
||||
func get(_ identifier: String, toFile local_file_path: String) throws
|
||||
func put(_ identifier: String, content: Data) throws
|
||||
func put(_ identifier: String, fromFile local_file_path: String) throws
|
||||
func delete(_ identifier: String) throws
|
||||
}
|
||||
|
||||
// utility funcs
|
||||
func isDir(_ path: String) -> Bool {
|
||||
let filemanager = FileManager.default
|
||||
do {
|
||||
let fileType = (try filemanager.attributesOfItem(atPath: path) as NSDictionary).fileType()
|
||||
return fileType == FileAttributeType.typeDirectory.rawValue
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: share mounting functions
|
||||
// NetFS error codes
|
||||
/*
|
||||
* ENETFSPWDNEEDSCHANGE -5045
|
||||
* ENETFSPWDPOLICY -5046
|
||||
* ENETFSACCOUNTRESTRICTED -5999
|
||||
* ENETFSNOSHARESAVAIL -5998
|
||||
* ENETFSNOAUTHMECHSUPP -5997
|
||||
* ENETFSNOPROTOVERSSUPP -5996
|
||||
*
|
||||
* from <NetAuth/NetAuthErrors.h>
|
||||
* kNetAuthErrorInternal -6600
|
||||
* kNetAuthErrorMountFailed -6602
|
||||
* kNetAuthErrorNoSharesAvailable -6003
|
||||
* kNetAuthErrorGuestNotSupported -6004
|
||||
* kNetAuthErrorAlreadyClosed -6005
|
||||
*/
|
||||
|
||||
enum ShareMountError: Error {
|
||||
case generalError(Int32)
|
||||
case authorizationNeeded(Int32)
|
||||
}
|
||||
|
||||
func mountShare(_ shareURL: String, username: String = "", password: String = "") throws -> String {
|
||||
// Mounts a share at /Volumes, optionally using credentials.
|
||||
// Returns the mount point or throws an error
|
||||
let cfShareURL = CFURLCreateWithString(nil, shareURL as CFString, nil)
|
||||
// Set UI to reduced interaction
|
||||
let open_options: NSMutableDictionary = [kNAUIOptionKey: kNAUIOptionNoUI]
|
||||
// Allow mounting sub-directories of root shares
|
||||
let mount_options: NSMutableDictionary = [kNetFSAllowSubMountsKey: true]
|
||||
var mountpoints: Unmanaged<CFArray>? = nil
|
||||
var result: Int32 = 0
|
||||
if !username.isEmpty {
|
||||
result = NetFSMountURLSync(cfShareURL, nil, username as CFString, password as CFString, open_options as CFMutableDictionary, mount_options as CFMutableDictionary, &mountpoints)
|
||||
} else {
|
||||
result = NetFSMountURLSync(cfShareURL, nil, nil, nil, open_options as CFMutableDictionary, mount_options as CFMutableDictionary, &mountpoints)
|
||||
}
|
||||
// Check if it worked
|
||||
if result != 0 {
|
||||
if [-6600, EINVAL, ENOTSUP, EAUTH].contains(result) {
|
||||
// -6600 is kNetAuthErrorInternal in NetFS.h 10.9+
|
||||
// EINVAL is returned if an afp share needs a login in some versions of macOS
|
||||
// ENOTSUP is returned if an afp share needs a login in some versions of macOS
|
||||
// EAUTH is returned if authentication fails (SMB for sure)
|
||||
throw ShareMountError.authorizationNeeded(result)
|
||||
}
|
||||
throw ShareMountError.generalError(result)
|
||||
}
|
||||
let mounts = (mountpoints?.takeUnretainedValue()) as! [CFString]
|
||||
return mounts[0] as String
|
||||
}
|
||||
|
||||
func mountShareURL(_ share_url: String) throws -> String {
|
||||
do {
|
||||
return try mountShare(share_url)
|
||||
} catch ShareMountError.authorizationNeeded {
|
||||
//pass
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
var username = ""
|
||||
print("Username: ", terminator: "")
|
||||
if let input = readLine(strippingNewline: true) {
|
||||
username = input
|
||||
}
|
||||
var password = ""
|
||||
if let input = getpass("Password: ") {
|
||||
password = String(cString: input, encoding: .utf8) ?? ""
|
||||
}
|
||||
return try mountShare(share_url, username: username, password: password)
|
||||
}
|
||||
|
||||
|
||||
// MARK: File repo class
|
||||
class FileRepo: Repo {
|
||||
// MARK: instance variables
|
||||
var baseurl: String
|
||||
var urlScheme: String
|
||||
var root: String
|
||||
var weMountedTheRepo: Bool
|
||||
|
||||
// MARK: init/deinit
|
||||
required init(_ url: String) throws {
|
||||
baseurl = url
|
||||
urlScheme = NSURL(string: url)?.scheme ?? ""
|
||||
root = ""
|
||||
if urlScheme == "file" {
|
||||
root = NSURL(string: url)?.path ?? ""
|
||||
} else {
|
||||
// repo is on a fileshare that will be mounted under /Volumes
|
||||
root = "/Volumes" + (NSURL(string: url)?.path ?? "")
|
||||
}
|
||||
weMountedTheRepo = false
|
||||
try _connect()
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Destructor -- unmount the fileshare if we mounted it
|
||||
if weMountedTheRepo && isDir(root) {
|
||||
print("Attempting to unmount \(root)...")
|
||||
let results = runCLI(
|
||||
"/usr/sbin/diskutil", arguments: ["unmount", root])
|
||||
if results.exitcode == 0 {
|
||||
print(results.output)
|
||||
} else {
|
||||
print("Exit code: \(results.exitcode)")
|
||||
printStderr(results.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: utility methods
|
||||
func fullPath(_ identifier: String) -> String {
|
||||
// returns the full (absolute) filesystem path to identifier
|
||||
return (root as NSString).appendingPathComponent(identifier)
|
||||
}
|
||||
|
||||
func parentDir(_ identifier: String) -> String {
|
||||
// returns the filesystem path to the parent dir of identifier
|
||||
return (fullPath(identifier) as NSString).deletingLastPathComponent
|
||||
}
|
||||
|
||||
private func _connect() throws {
|
||||
// If self.root is present, return. Otherwise, if the url scheme is not
|
||||
// "file" then try to mount the share url.
|
||||
if isDir(root) {
|
||||
return
|
||||
}
|
||||
if urlScheme != "file" {
|
||||
do {
|
||||
print("Attempting to mount fileshare \(baseurl)...")
|
||||
root = try mountShareURL(baseurl)
|
||||
weMountedTheRepo = true
|
||||
} catch is ShareMountError {
|
||||
throw RepoError.error(description: "Error mounting repo file share")
|
||||
}
|
||||
}
|
||||
// does root dir exist now?
|
||||
if !isDir(root) {
|
||||
throw RepoError.error(description: "Repo path does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: API methods
|
||||
func list(_ kind: String) throws -> [String] {
|
||||
// Returns a list of identifiers for each item of kind.
|
||||
// Kind might be 'catalogs', 'manifests', 'pkgsinfo', 'pkgs', or 'icons'.
|
||||
// For a file-backed repo this would be a list of pathnames.
|
||||
var fileList = [String]()
|
||||
let searchPath = (root as NSString).appendingPathComponent(kind)
|
||||
let filemanager = FileManager.default
|
||||
let dirEnum = filemanager.enumerator(atPath: searchPath)
|
||||
while let file = dirEnum?.nextObject() as? String {
|
||||
let fullpath = (searchPath as NSString).appendingPathComponent(file)
|
||||
if !isDir(fullpath) {
|
||||
let basename = (file as NSString).lastPathComponent
|
||||
if !basename.hasPrefix(".") {
|
||||
fileList.append(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileList
|
||||
}
|
||||
|
||||
func get(_ identifier: String) throws -> Data {
|
||||
// Returns the content of item with given resource_identifier.
|
||||
// For a file-backed repo, a resource_identifier of
|
||||
// 'pkgsinfo/apps/Firefox-52.0.plist' would return the contents of
|
||||
// <repo_root>/pkgsinfo/apps/Firefox-52.0.plist.
|
||||
// Avoid using this method with the 'pkgs' kind as it might return a
|
||||
// really large blob of data.
|
||||
let repoFilePath = fullPath(identifier)
|
||||
if let data = FileManager.default.contents(atPath: repoFilePath) {
|
||||
return data
|
||||
}
|
||||
throw RepoError.error(description: "Error getting contents from \(repoFilePath)")
|
||||
}
|
||||
|
||||
func get(_ identifier: String, toFile local_file_path: String) throws {
|
||||
// Gets the contents of item with given resource_identifier and saves
|
||||
// it to local_file_path.
|
||||
// For a file-backed repo, a resource_identifier
|
||||
// of 'pkgsinfo/apps/Firefox-52.0.plist' would copy the contents of
|
||||
// <repo_root>/pkgsinfo/apps/Firefox-52.0.plist to a local file given by
|
||||
// local_file_path.
|
||||
// TODO: make this atomic
|
||||
let filemanager = FileManager.default
|
||||
if filemanager.fileExists(atPath: local_file_path) {
|
||||
try filemanager.removeItem(atPath: local_file_path)
|
||||
}
|
||||
try filemanager.copyItem(atPath: fullPath(identifier), toPath: local_file_path)
|
||||
}
|
||||
|
||||
func put(_ identifier: String, content: Data) throws {
|
||||
// Stores content on the repo based on resource_identifier.
|
||||
// For a file-backed repo, a resource_identifier of
|
||||
// 'pkgsinfo/apps/Firefox-52.0.plist' would result in the content being
|
||||
// saved to <repo_root>/pkgsinfo/apps/Firefox-52.0.plist.
|
||||
let filemanager = FileManager.default
|
||||
let dirPath = parentDir(identifier)
|
||||
if !filemanager.fileExists(atPath: dirPath) {
|
||||
try filemanager.createDirectory(
|
||||
atPath: dirPath,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: [.posixPermissions: 0o755]
|
||||
)
|
||||
}
|
||||
if !((content as NSData).write(toFile: fullPath(identifier), atomically: true)) {
|
||||
throw RepoError.error(description: "write failed")
|
||||
}
|
||||
}
|
||||
|
||||
func put(_ identifier: String, fromFile localFilePath: String) throws {
|
||||
// Copies the content of local_file_path to the repo based on
|
||||
// resource_identifier. For a file-backed repo, a resource_identifier
|
||||
// of 'pkgsinfo/apps/Firefox-52.0.plist' would result in the content
|
||||
// being saved to <repo_root>/pkgsinfo/apps/Firefox-52.0.plist.
|
||||
let filemanager = FileManager.default
|
||||
let dirPath = parentDir(identifier)
|
||||
if !filemanager.fileExists(atPath: dirPath) {
|
||||
try filemanager.createDirectory(
|
||||
atPath: dirPath,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: [.posixPermissions: 0o755]
|
||||
)
|
||||
}
|
||||
let repoFilePath = fullPath(identifier)
|
||||
if filemanager.fileExists(atPath: repoFilePath) {
|
||||
// if file already exists, we have to remove it first
|
||||
try filemanager.removeItem(atPath: repoFilePath)
|
||||
}
|
||||
try filemanager.copyItem(atPath: localFilePath, toPath: repoFilePath)
|
||||
}
|
||||
|
||||
func delete(_ identifier: String) throws {
|
||||
// Deletes a repo object located by resource_identifier.
|
||||
// For a file-backed repo, a resource_identifier of
|
||||
// 'pkgsinfo/apps/Firefox-52.0.plist' would result in the deletion of
|
||||
// <repo_root>/pkgsinfo/apps/Firefox-52.0.plist.
|
||||
try FileManager.default.removeItem(atPath: fullPath(identifier))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// GitFileRepo.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/28/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class GitFileRepo: FileRepo {
|
||||
// A subclass of FileRepo that also does git commits for repo changes
|
||||
// MARK: instance variables
|
||||
var cmd: String
|
||||
|
||||
// MARK: override init
|
||||
required init(_ url: String) throws {
|
||||
// try to get path to git binary from admin prefs or use default path
|
||||
cmd = adminPref("GitBinaryPath") as? String ?? "/usr/bin/git"
|
||||
// init the rest
|
||||
try super.init(url)
|
||||
}
|
||||
|
||||
// MARK: git functions
|
||||
func runGit(args: [String] = []) -> CLIResults {
|
||||
return runCLI(cmd, arguments: args)
|
||||
}
|
||||
|
||||
func isGitIgnored(_ identifier: String) -> Bool {
|
||||
// Returns True if file referred to by identifer will be ignored by Git
|
||||
// (usually due to being in a .gitignore file)
|
||||
let results = runGit(
|
||||
args: ["-C", parentDir(identifier),
|
||||
"check-ignore", fullPath(identifier)]
|
||||
)
|
||||
return results.exitcode == 0
|
||||
}
|
||||
|
||||
func isInGitRepo(_ identifier: String) -> Bool {
|
||||
// Returns True if file referred to by identifer is in a Git repo, false otherwise.
|
||||
let results = runGit(
|
||||
args: ["-C", parentDir(identifier),
|
||||
"status", "-z", fullPath(identifier)]
|
||||
)
|
||||
return results.exitcode == 0
|
||||
}
|
||||
|
||||
func gitCommit(_ identifier: String) {
|
||||
// Commits the file referred to be identifier. This method will also automatically
|
||||
// generate the commit log appropriate for the status of the file where
|
||||
// status would be 'modified', 'new file', or 'deleted'
|
||||
|
||||
// figure out the name of the tool in use
|
||||
let processPath = ProcessInfo.processInfo.arguments[0]
|
||||
let toolname = (processPath as NSString).lastPathComponent
|
||||
|
||||
// get the current username
|
||||
let username = NSUserName()
|
||||
|
||||
// get the status of file at path
|
||||
let statusResults = runGit(
|
||||
args:["-C", parentDir(identifier),
|
||||
"status", "-s", fullPath(identifier)]
|
||||
)
|
||||
var action = ""
|
||||
if statusResults.output.hasPrefix("A") {
|
||||
action = "added"
|
||||
} else if statusResults.output.hasPrefix("D") {
|
||||
action = "deleted"
|
||||
} else if statusResults.output.hasPrefix("M") {
|
||||
action = "modified"
|
||||
} else {
|
||||
action = "made unexpected change to"
|
||||
}
|
||||
|
||||
// generate the log message
|
||||
let logMessage = "\(username) \(action) '\(identifier)' via \(toolname)"
|
||||
// do the commit
|
||||
print("Doing git commit: \(logMessage)")
|
||||
let results = runGit(
|
||||
args: ["-C", parentDir(identifier),
|
||||
"commit", "-m", logMessage]
|
||||
)
|
||||
if results.exitcode != 0 {
|
||||
printStderr("Failed to commit changes to \(identifier)")
|
||||
printStderr(results.error)
|
||||
}
|
||||
}
|
||||
|
||||
func _gitAddOrRemove(_ identifier: String, _ operation: String) {
|
||||
// Does a git add or rm of a file at path. "operation" must be either "add" or "rm"
|
||||
if isInGitRepo(identifier) {
|
||||
if !isGitIgnored(identifier) {
|
||||
let results = runGit(
|
||||
args: ["-C", parentDir(identifier),
|
||||
operation, fullPath(identifier)]
|
||||
)
|
||||
if results.exitcode == 0 {
|
||||
gitCommit(identifier)
|
||||
} else {
|
||||
printStderr("git error: \(results.error)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printStderr("\(identifier) is not in a git repo.")
|
||||
}
|
||||
}
|
||||
|
||||
func gitAdd(_ identifier: String) {
|
||||
// Adds and commits file at path
|
||||
_gitAddOrRemove(identifier, "add")
|
||||
}
|
||||
|
||||
func gitDelete(_ identifier: String) {
|
||||
// Deletes file at path and commits the result
|
||||
_gitAddOrRemove(identifier, "rm")
|
||||
}
|
||||
|
||||
// MARK: override FileRepo API methods
|
||||
override func put(_ identifier: String, content: Data) throws {
|
||||
try super.put(identifier, content: content)
|
||||
gitAdd(identifier)
|
||||
}
|
||||
|
||||
override func put(_ identifier: String, fromFile local_path: String) throws {
|
||||
try super.put(identifier, fromFile: local_path)
|
||||
gitAdd(identifier)
|
||||
}
|
||||
|
||||
override func delete(_ identifier: String) throws {
|
||||
try super.delete(identifier)
|
||||
gitDelete(identifier)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// RepoFactory.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/29/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func repoConnect(url: String, plugin: String = "FileRepo") throws -> Repo {
|
||||
// Factory function that returns an instance of a specific Repo class
|
||||
switch plugin {
|
||||
case "FileRepo":
|
||||
return try FileRepo(url)
|
||||
case "GitFileRepo":
|
||||
return try GitFileRepo(url)
|
||||
default:
|
||||
throw RepoError.error(description: "No repo plugin named \"\(plugin)\"")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// plistutils.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/27/24.
|
||||
|
||||
import Foundation
|
||||
|
||||
enum PlistError: Error {
|
||||
case readError(description: String)
|
||||
case writeError(description: String)
|
||||
}
|
||||
|
||||
func deserialize(_ data: Data?) throws -> Any? {
|
||||
if data != nil {
|
||||
do {
|
||||
let dataObject = try PropertyListSerialization.propertyList(
|
||||
from: data!,
|
||||
options: PropertyListSerialization.MutabilityOptions.mutableContainers,
|
||||
format: nil)
|
||||
return dataObject
|
||||
} catch {
|
||||
throw PlistError.readError(description: "\(error)")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readPlist(_ filepath: String) throws -> Any? {
|
||||
return try deserialize(NSData(contentsOfFile: filepath) as Data?)
|
||||
}
|
||||
|
||||
func readPlistFromData(_ data: Data) throws -> Any? {
|
||||
return try deserialize(data)
|
||||
}
|
||||
|
||||
func readPlistFromString(_ stringData: String) throws -> Any? {
|
||||
return try deserialize(stringData.data(using: String.Encoding.utf8))
|
||||
}
|
||||
|
||||
func serialize(_ plist: Any) throws -> Data {
|
||||
do {
|
||||
let plistData = try PropertyListSerialization.data(
|
||||
fromPropertyList: plist,
|
||||
format: PropertyListSerialization.PropertyListFormat.xml,
|
||||
options: 0)
|
||||
return plistData
|
||||
} catch {
|
||||
throw PlistError.writeError(description: "\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func writePlist(_ dataObject: Any, toFile filepath: String) throws {
|
||||
do {
|
||||
let data = try serialize(dataObject) as NSData
|
||||
if !(data.write(toFile: filepath, atomically: true)) {
|
||||
throw PlistError.writeError(description: "write failed")
|
||||
}
|
||||
} catch {
|
||||
throw PlistError.writeError(description: "\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func plistToData(_ dataObject: Any) throws -> Data {
|
||||
return try serialize(dataObject)
|
||||
}
|
||||
|
||||
func plistToString(_ dataObject: Any) throws -> String {
|
||||
do {
|
||||
let data = try serialize(dataObject)
|
||||
return String(data: data, encoding: String.Encoding.utf8)!
|
||||
} catch {
|
||||
throw PlistError.writeError(description: "\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
//
|
||||
// prefs.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 6/25/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let DEFAULT_INSECURE_REPO_URL = "http://munki/repo"
|
||||
|
||||
// unlike the previous Python implementation, we define default
|
||||
// preference values only if they are not None/nil
|
||||
let DEFAULT_PREFS: [String: Any] = [
|
||||
//"AdditionalHttpHeaders": None,
|
||||
"AggressiveUpdateNotificationDays": 14,
|
||||
"AppleSoftwareUpdatesIncludeMajorOSUpdates": false,
|
||||
"AppleSoftwareUpdatesOnly": false,
|
||||
//"CatalogURL": None,
|
||||
//"ClientCertificatePath": None,
|
||||
"ClientIdentifier": "",
|
||||
//"ClientKeyPath": None,
|
||||
//"ClientResourcesFilename": None,
|
||||
//"ClientResourceURL": None,
|
||||
"DaysBetweenNotifications": 1,
|
||||
"EmulateProfileSupport": false,
|
||||
"FollowHTTPRedirects": "none",
|
||||
//"HelpURL": None,
|
||||
//"IconURL": None,
|
||||
"IgnoreMiddleware": false,
|
||||
"IgnoreSystemProxies": false,
|
||||
"InstallRequiresLogout": false,
|
||||
"InstallAppleSoftwareUpdates": false,
|
||||
"LastNotifiedDate": NSDate.init(timeIntervalSince1970: 0),
|
||||
//"LocalOnlyManifest": None,
|
||||
"LogFile": "/Library/Managed Installs/Logs/ManagedSoftwareUpdate.log",
|
||||
"LoggingLevel": 1,
|
||||
"LogToSyslog": false,
|
||||
"ManagedInstallDir": "/Library/Managed Installs",
|
||||
//"ManifestURL": None,
|
||||
//"PackageURL": None,
|
||||
"PackageVerificationMode": "hash",
|
||||
"PerformAuthRestarts": false,
|
||||
//"RecoveryKeyFile": None,
|
||||
"ShowOptionalInstallsForHigherOSVersions": false,
|
||||
//"SoftwareRepoCACertificate": None,
|
||||
//"SoftwareRepoCAPath": None,
|
||||
"SoftwareRepoURL": DEFAULT_INSECURE_REPO_URL,
|
||||
//"SoftwareUpdateServerURL": None,
|
||||
"SuppressAutoInstall": false,
|
||||
"SuppressLoginwindowInstall": false,
|
||||
"SuppressStopButtonOnInstall": false,
|
||||
"SuppressUserNotification": false,
|
||||
"UnattendedAppleUpdates": false,
|
||||
"UseClientCertificate": false,
|
||||
"UseClientCertificateCNAsClientIdentifier": false,
|
||||
"UseNotificationCenterDays": 3,
|
||||
]
|
||||
|
||||
// and since we don't define default values if they are None/nil
|
||||
// we need a list of keynames we will display for --show-config
|
||||
let CONFIG_KEY_NAMES = [
|
||||
"AdditionalHttpHeaders",
|
||||
"AggressiveUpdateNotificationDays",
|
||||
"AppleSoftwareUpdatesIncludeMajorOSUpdates",
|
||||
"AppleSoftwareUpdatesOnly",
|
||||
"CatalogURL",
|
||||
"ClientCertificatePath",
|
||||
"ClientIdentifier",
|
||||
"ClientKeyPath",
|
||||
"ClientResourcesFilename",
|
||||
"ClientResourceURL",
|
||||
"DaysBetweenNotifications",
|
||||
"EmulateProfileSupport",
|
||||
"FollowHTTPRedirects",
|
||||
"HelpURL",
|
||||
"IconURL",
|
||||
"IgnoreMiddleware",
|
||||
"IgnoreSystemProxies",
|
||||
"InstallRequiresLogout",
|
||||
"InstallAppleSoftwareUpdates",
|
||||
"LocalOnlyManifest",
|
||||
"LogFile",
|
||||
"LoggingLevel",
|
||||
"LogToSyslog",
|
||||
"ManagedInstallDir",
|
||||
"ManifestURL",
|
||||
"PackageURL",
|
||||
"PackageVerificationMode",
|
||||
"PerformAuthRestarts",
|
||||
"RecoveryKeyFile",
|
||||
"ShowOptionalInstallsForHigherOSVersions",
|
||||
"SoftwareRepoCACertificate",
|
||||
"SoftwareRepoCAPath",
|
||||
"SoftwareRepoURL",
|
||||
"SoftwareUpdateServerURL",
|
||||
"SuppressAutoInstall",
|
||||
"SuppressLoginwindowInstall",
|
||||
"SuppressStopButtonOnInstall",
|
||||
"SuppressUserNotification",
|
||||
"UnattendedAppleUpdates",
|
||||
"UseClientCertificate",
|
||||
"UseClientCertificateCNAsClientIdentifier",
|
||||
"UseNotificationCenterDays",
|
||||
]
|
||||
|
||||
|
||||
func reloadPrefs() {
|
||||
/* Uses CFPreferencesAppSynchronize(BUNDLE_ID)
|
||||
to make sure we have the latest prefs. Call this
|
||||
if you have modified /Library/Preferences/ManagedInstalls.plist
|
||||
or /var/root/Library/Preferences/ManagedInstalls.plist directly */
|
||||
CFPreferencesAppSynchronize(BUNDLE_ID)
|
||||
}
|
||||
|
||||
|
||||
func setPref(_ prefName: String, _ prefValue: Any) {
|
||||
/* Sets a preference, writing it to
|
||||
/Library/Preferences/ManagedInstalls.plist.
|
||||
This should normally be used only for 'bookkeeping' values;
|
||||
values that control the behavior of munki may be overridden
|
||||
elsewhere (by MCX, for example) */
|
||||
if let key = prefName as CFString? {
|
||||
if let value = prefValue as CFPropertyList? {
|
||||
CFPreferencesSetValue(
|
||||
key, value, BUNDLE_ID,
|
||||
kCFPreferencesAnyUser, kCFPreferencesCurrentHost)
|
||||
CFPreferencesAppSynchronize(BUNDLE_ID)
|
||||
} else {
|
||||
// raise error about illegal value?
|
||||
}
|
||||
} else {
|
||||
// raise error about illegal key?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func pref(_ prefName: String) -> Any? {
|
||||
/* Return a preference. Since this uses CFPreferencesCopyAppValue,
|
||||
Preferences can be defined several places. Precedence is:
|
||||
- MCX/configuration profile
|
||||
- /var/root/Library/Preferences/ByHost/ManagedInstalls.XXXXXX.plist
|
||||
- /var/root/Library/Preferences/ManagedInstalls.plist
|
||||
- /Library/Preferences/ManagedInstalls.plist
|
||||
- .GlobalPreferences defined at various levels (ByHost, user, system)
|
||||
- default_prefs defined here.
|
||||
*/
|
||||
var prefValue: Any?
|
||||
prefValue = CFPreferencesCopyAppValue(prefName as CFString, BUNDLE_ID)
|
||||
if prefValue == nil {
|
||||
if let defaultValue = DEFAULT_PREFS[prefName] {
|
||||
prefValue = defaultValue
|
||||
// we're using a default value. We'll write it out to
|
||||
// /Library/Preferences/<BUNDLE_ID>.plist for admin discoverability
|
||||
setPref(prefName, defaultValue)
|
||||
}
|
||||
}
|
||||
// prior Python implementation converted dates to strings; we won't do that
|
||||
/*if isinstance(pref_value, NSDate):
|
||||
# convert NSDate/CFDates to strings
|
||||
pref_value = str(pref_value)*/
|
||||
return prefValue
|
||||
}
|
||||
|
||||
|
||||
struct prefsDomain {
|
||||
var file: String
|
||||
var domain: CFString
|
||||
var user: CFString
|
||||
var host: CFString
|
||||
}
|
||||
|
||||
|
||||
func isEqual(_ a: CFPropertyList, _ b: CFPropertyList) -> Bool {
|
||||
// attempt to compare two CFPropertyList objects that are actually one of:
|
||||
// String, Number, Boolean, Date
|
||||
if let aString = a as? String, let bString = b as? String {
|
||||
return aString == bString
|
||||
}
|
||||
if let aNumber = a as? NSNumber, let bNumber = b as? NSNumber {
|
||||
return aNumber == bNumber
|
||||
}
|
||||
if let aDate = a as? NSDate, let bDate = b as? NSDate {
|
||||
return aDate == bDate
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
func getConfigLevel(_ domain: String, _ prefName: String, _ value: Any?) -> String {
|
||||
// Returns a string indicating where the given preference is defined
|
||||
if value == nil {
|
||||
return "[not set]"
|
||||
}
|
||||
if CFPreferencesAppValueIsForced(prefName as CFString, domain as CFString) {
|
||||
return "[MANAGED]"
|
||||
}
|
||||
// define all the places we need to search, in priority order
|
||||
let levels: [prefsDomain] = [
|
||||
prefsDomain(
|
||||
file: "/var/root/Library/Preferences/ByHost/\(domain).xxxx.plist",
|
||||
domain: domain as CFString,
|
||||
user: kCFPreferencesCurrentUser,
|
||||
host: kCFPreferencesCurrentHost
|
||||
),
|
||||
prefsDomain(
|
||||
file: "/var/root/Library/Preferences/\(domain).plist",
|
||||
domain: domain as CFString,
|
||||
user: kCFPreferencesCurrentUser,
|
||||
host: kCFPreferencesAnyHost
|
||||
),
|
||||
prefsDomain(
|
||||
file: "/var/root/Library/Preferences/ByHost/.GlobalPreferences.xxxx.plist",
|
||||
domain: ".GlobalPreferences" as CFString,
|
||||
user: kCFPreferencesCurrentUser,
|
||||
host: kCFPreferencesCurrentHost
|
||||
),
|
||||
prefsDomain(
|
||||
file: "/var/root/Library/Preferences/.GlobalPreferences.plist",
|
||||
domain: ".GlobalPreferences" as CFString,
|
||||
user: kCFPreferencesCurrentUser,
|
||||
host: kCFPreferencesAnyHost
|
||||
),
|
||||
prefsDomain(
|
||||
file: "/Library/Preferences/\(domain).plist",
|
||||
domain: domain as CFString,
|
||||
user: kCFPreferencesAnyUser,
|
||||
host: kCFPreferencesCurrentHost
|
||||
),
|
||||
prefsDomain(
|
||||
file: "/Library/Preferences/.GlobalPreferences.plist",
|
||||
domain: ".GlobalPreferences" as CFString,
|
||||
user: kCFPreferencesAnyUser,
|
||||
host: kCFPreferencesCurrentHost
|
||||
)
|
||||
]
|
||||
for level in levels {
|
||||
if let levelValue = CFPreferencesCopyValue(
|
||||
prefName as CFString,
|
||||
level.domain,
|
||||
level.user,
|
||||
level.host
|
||||
) {
|
||||
if let ourValue = value as? CFPropertyList {
|
||||
if isEqual(ourValue, levelValue) {
|
||||
return "[\(level.file)]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let value = value as? CFPropertyList, let defaultValue = DEFAULT_PREFS["pref_name"] as? CFPropertyList {
|
||||
if isEqual(value, defaultValue) {
|
||||
return "[default]"
|
||||
}
|
||||
}
|
||||
return "[unknown]"
|
||||
}
|
||||
|
||||
|
||||
func printConfig() {
|
||||
// Prints the current Munki configuration
|
||||
print("Current Munki configuration:")
|
||||
let maxPrefNameLen = CONFIG_KEY_NAMES.max(by: {$1.count > $0.count})?.count ?? 0
|
||||
let padding = " "
|
||||
for prefName in CONFIG_KEY_NAMES.sorted() {
|
||||
let value = pref(prefName)
|
||||
let level = getConfigLevel(BUNDLE_ID as String, prefName, value)
|
||||
var reprValue = "None"
|
||||
// it's hard to distinguish a boolean from a number in a CFPropertyList item, so
|
||||
// we look at the type of the default value if defined
|
||||
if let numberValue = value as? NSNumber {
|
||||
if DEFAULT_PREFS[prefName] is Bool {
|
||||
if numberValue != 0 {
|
||||
reprValue = "True"
|
||||
} else {
|
||||
reprValue = "False"
|
||||
}
|
||||
} else {
|
||||
reprValue = "\(numberValue)"
|
||||
}
|
||||
} else if let stringValue = value as? String {
|
||||
reprValue = "\"\(stringValue)\""
|
||||
} else if let arrayValue = value as? NSArray {
|
||||
reprValue = "\(arrayValue)"
|
||||
}
|
||||
//print(('%' + str(max_pref_name_len) + 's: %5s %s ') % (
|
||||
// pref_name, repr_value, level))
|
||||
let paddedPrefName = (padding + prefName).suffix(maxPrefNameLen)
|
||||
print("\(paddedPrefName): \(reprValue) \(level)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// utils.swift
|
||||
// managedsoftwareupdate
|
||||
//
|
||||
// Created by Greg Neagle on 6/25/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func getVersion() -> String {
|
||||
// TODO: actually read this from a file
|
||||
// or figure out a way to update this at build time
|
||||
return "0.0.1"
|
||||
}
|
||||
Reference in New Issue
Block a user