diff --git a/MDM/Jamf Pro/service-status-ea.sh b/MDM/Jamf Pro/service-status-ea.sh new file mode 100755 index 0000000..cab4454 --- /dev/null +++ b/MDM/Jamf Pro/service-status-ea.sh @@ -0,0 +1,24 @@ +#!/bin/zsh + +# Indicate if all the Outset daemons are enabled or not. + +outsetStatusRaw=$(/usr/local/outset/outset --service-status) +enabledDaemons=$(echo $outsetStatusRaw | grep -c 'Enabled$') + +# If there is no user logged in, none of the 3 agents will be active, only the daemons. + +expectedServices=6 + +currentUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' ) + +if [ -z "$currentUser" -o "$currentUser" = "loginwindow" ]; then + expectedServices=3 +fi + +healthyStatus="Not Healthy" + +if [ $enabledDaemons -eq $expectedServices ]; then + healthyStatus="Healthy" +fi + +echo "$healthyStatus" \ No newline at end of file diff --git a/Outset.xcodeproj/project.pbxproj b/Outset.xcodeproj/project.pbxproj index 503c21d..e672f21 100644 --- a/Outset.xcodeproj/project.pbxproj +++ b/Outset.xcodeproj/project.pbxproj @@ -512,7 +512,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 4.1.1; + MARKETING_VERSION = 4.1.2; PRODUCT_BUNDLE_IDENTIFIER = io.macadmins.Outset; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -549,7 +549,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 4.1.1; + MARKETING_VERSION = 4.1.2; PRODUCT_BUNDLE_IDENTIFIER = io.macadmins.Outset; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -587,7 +587,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 4.1.1; + MARKETING_VERSION = 4.1.2; PRODUCT_BUNDLE_IDENTIFIER = io.macadmins.Outset; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -629,7 +629,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 4.1.1; + MARKETING_VERSION = 4.1.2; PRODUCT_BUNDLE_IDENTIFIER = io.macadmins.Outset; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Outset/Info.plist b/Outset/Info.plist index 9a4fc14..d2ac65f 100644 --- a/Outset/Info.plist +++ b/Outset/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.1.1 + 4.1.2 CFBundleVersion - 4.1.1 + 4.1.2 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/Outset/Outset.swift b/Outset/Outset.swift index a36f287..a09f73f 100644 --- a/Outset/Outset.swift +++ b/Outset/Outset.swift @@ -161,7 +161,7 @@ struct Outset: ParsableCommand { // perform log file rotation performLogRotation(logFolderPath: logDirectory, logFileBaseName: logFileName, maxLogFiles: logFileMaxCount) - writeLog("Processing scheduled runs for boot", logLevel: .debug) + writeLog("Processing scheduled runs for boot", logLevel: .info) ensureWorkingFolders() writeOutsetPreferences(prefs: prefs) @@ -190,7 +190,7 @@ struct Outset: ParsableCommand { } if loginWindow { - writeLog("Processing scheduled runs for login window", logLevel: .debug) + writeLog("Processing scheduled runs for login window", logLevel: .info) if !folderContents(path: loginWindowDir).isEmpty { processItems(loginWindowDir) @@ -198,7 +198,7 @@ struct Outset: ParsableCommand { } if login { - writeLog("Processing scheduled runs for login", logLevel: .debug) + writeLog("Processing scheduled runs for login", logLevel: .info) if !prefs.ignoredUsers.contains(consoleUser) { if !folderContents(path: loginOnceDir).isEmpty { processItems(loginOnceDir, once: true, override: prefs.overrideLoginOnce) @@ -207,14 +207,14 @@ struct Outset: ParsableCommand { processItems(loginEveryDir) } if !folderContents(path: loginOncePrivilegedDir).isEmpty || !folderContents(path: loginEveryPrivilegedDir).isEmpty { - FileManager.default.createFile(atPath: loginPrivilegedTrigger, contents: nil) + createTrigger(loginPrivilegedTrigger) } } } if loginPrivileged { - writeLog("Processing scheduled runs for privileged login", logLevel: .debug) + writeLog("Processing scheduled runs for privileged login", logLevel: .info) if checkFileExists(path: loginPrivilegedTrigger) { pathCleanup(pathname: loginPrivilegedTrigger) } @@ -231,29 +231,24 @@ struct Outset: ParsableCommand { } if onDemand { - writeLog("Processing on-demand", logLevel: .debug) + writeLog("Processing on-demand", logLevel: .info) if !folderContents(path: onDemandDir).isEmpty { if !["root", "loginwindow"].contains(consoleUser) { let currentUser = NSUserName() if consoleUser == currentUser { processItems(onDemandDir) + createTrigger(cleanupTrigger) } else { writeLog("User \(currentUser) is not the current console user. Skipping on-demand run.") } } else { writeLog("No current user session. Skipping on-demand run.") } - FileManager.default.createFile(atPath: cleanupTrigger, contents: nil) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - if checkFileExists(path: cleanupTrigger) { - pathCleanup(pathname: cleanupTrigger) - } - } } } if loginEvery { - writeLog("Processing scripts in login-every", logLevel: .debug) + writeLog("Processing scripts in login-every", logLevel: .info) if !prefs.ignoredUsers.contains(consoleUser) { if !folderContents(path: loginEveryDir).isEmpty { processItems(loginEveryDir) @@ -262,7 +257,7 @@ struct Outset: ParsableCommand { } if loginOnce { - writeLog("Processing scripts in login-once", logLevel: .debug) + writeLog("Processing scripts in login-once", logLevel: .info) if !prefs.ignoredUsers.contains(consoleUser) { if !folderContents(path: loginOnceDir).isEmpty { processItems(loginOnceDir, once: true, override: prefs.overrideLoginOnce) @@ -273,7 +268,7 @@ struct Outset: ParsableCommand { } if cleanup { - writeLog("Cleaning up on-demand directory.", logLevel: .debug) + writeLog("Cleaning up on-demand directory.", logLevel: .info) if checkFileExists(path: onDemandTrigger) { pathCleanup(pathname: onDemandTrigger) } if checkFileExists(path: cleanupTrigger) { pathCleanup(pathname: cleanupTrigger) } if !folderContents(path: onDemandDir).isEmpty { pathCleanup(pathname: onDemandDir) } @@ -309,7 +304,7 @@ struct Outset: ParsableCommand { ensureRoot("add scripts to override list") for var override in addOverride { - if !override.contains(loginOnceDir) { + if !override.contains(loginOnceDir) && !override.contains(loginOncePrivilegedDir) { override = "\(loginOnceDir)/\(override)" } writeLog("Adding \(override) to override list", logLevel: .debug) diff --git a/Outset/Utils/FileUtils.swift b/Outset/Utils/FileUtils.swift index 275c391..0bd33fc 100644 --- a/Outset/Utils/FileUtils.swift +++ b/Outset/Utils/FileUtils.swift @@ -169,6 +169,10 @@ func deletePath(_ path: String) { } } +func createTrigger(_ path: String) { + FileManager.default.createFile(atPath: path, contents: nil) +} + func mountDmg(dmg: String) -> String { // Attaches dmg and returns the path let cmd = "/usr/bin/hdiutil attach -nobrowse -noverify -noautoopen \(dmg)" diff --git a/Outset/Utils/ItemProcessing.swift b/Outset/Utils/ItemProcessing.swift index 1d2979e..36382fb 100644 --- a/Outset/Utils/ItemProcessing.swift +++ b/Outset/Utils/ItemProcessing.swift @@ -85,7 +85,7 @@ func processItems(_ path: String, deleteItems: Bool=false, once: Bool=false, ove } if once { - writeLog("Processing run-once \(script)", logLevel: .debug) + writeLog("Processing run-once \(script)", logLevel: .info) // If this is supposed to be a runonce item then we want to check to see if has an existing runonce entry // looks for a key with the full script path. Writes the full path and run date when done if !runOnceDict.contains(where: {$0.key == script}) { @@ -119,7 +119,7 @@ func processItems(_ path: String, deleteItems: Bool=false, once: Bool=false, ove } } } else { - writeLog("Processing script \(script)", logLevel: .debug) + writeLog("Processing script \(script)", logLevel: .info) let (_, error, status) = runShellCommand(script, args: [consoleUser], verbose: true) if status != 0 { writeLog(error, logLevel: .error) diff --git a/Outset/Utils/Preferences.swift b/Outset/Utils/Preferences.swift index a01b20e..a47f7df 100644 --- a/Outset/Utils/Preferences.swift +++ b/Outset/Utils/Preferences.swift @@ -153,16 +153,17 @@ func migrateLegacyPreferences() { deletePath(newoldRootUserDefaults) } case legacyOutsetPreferencesFile: - writeLog("\(legacyOutsetPreferencesFile) migration", logLevel: .debug) - do { - let legacyPreferences = try PropertyListDecoder().decode(OutsetPreferences.self, from: data) - writeOutsetPreferences(prefs: legacyPreferences) - writeLog("Migrated Legacy Outset Preferences", logLevel: .debug) - deletePath(legacyOutsetPreferencesFile) - } catch { - writeLog("legacy Preferences migration failed", logLevel: .error) + if isRoot() { + writeLog("\(legacyOutsetPreferencesFile) migration", logLevel: .debug) + do { + let legacyPreferences = try PropertyListDecoder().decode(OutsetPreferences.self, from: data) + writeOutsetPreferences(prefs: legacyPreferences) + writeLog("Migrated Legacy Outset Preferences", logLevel: .debug) + deletePath(legacyOutsetPreferencesFile) + } catch { + writeLog("legacy Preferences migration failed", logLevel: .error) + } } - case legacyRootRunOncePlistFile, legacyUserRunOncePlistFile: writeLog("\(legacyRootRunOncePlistFile) and \(legacyUserRunOncePlistFile) migration", logLevel: .debug) do { diff --git a/Package/outset-pkg b/Package/outset-pkg index 2f1b8fe..3e7b180 100755 --- a/Package/outset-pkg +++ b/Package/outset-pkg @@ -12,7 +12,7 @@ # AUTHOR: Bart Reardon # ORGANIZATION: Macadmins Open Source # CREATED: 2023-03-23 -# REVISION: 1.0.1 +# REVISION: 1.0.2 # # COPYRIGHT: (C) Macadmins Open Source 2023. All rights reserved. # @@ -21,6 +21,9 @@ FILE_TO_PACKAGE="" FILE_TARGET="login-once" BUILD_ALL=false +POSTINSTALL_SCRIPT=false +MAKE_EXECUTABLE=false +POSTINSTALL_SCRIPT_FILE="" BUILD_DIRECTORY="/var/tmp/outset" PKGTITLE="Outset Custom Content" @@ -30,7 +33,16 @@ PKGID="${PKG_DOMAIN}.custom-content" OUTSET_ROOT="/usr/local/outset" PGKROOT="${BUILD_DIRECTORY}/PGKROOT" +SCRIPT_DIR="${PGKROOT}/scripts" PKGNAME="outset-custom-content" +ONDEMAND_POSTINSTALL=$(cat < Package the selected file" + echo " -x, --make-executable Ensure the selected file executable bit is set (only applies to script files)" echo " -t, --target Target processing directory (default 'login-once')" + echo " -s, --postinstall-script []" + echo " Include a postinstall script. If no argument is given, a standard postinstall" + echo " script to trigger on-demand will be included." echo " -v, --version, Set package version to the selected number (default is '1.0')" echo " -p, --build-path, Path to use as the build location (default ${BUILD_DIRECTORY})" echo " -h, --help Print this message" @@ -95,8 +111,17 @@ fi while [[ "$#" -gt 0 ]]; do case $1 in --file|-f) FILE_TO_PACKAGE="$2"; shift ;; + --make-executable|-x) MAKE_EXECUTABLE=true ;; --target|-t) FILE_TARGET=$(validateTarget "$2") || printValidTargets "$2"; shift ;; --version|-v) PKGVERSION=$(validateVersion "$2") || exitWithError "invalid version number $2"; shift ;; + --postinstall-script|-s) POSTINSTALL_SCRIPT_FILE="$2" && POSTINSTALL_SCRIPT=true + # if the first character is a hyphen, then no argument was passed + if [[ -z $POSTINSTALL_SCRIPT_FILE ]] || [[ "${POSTINSTALL_SCRIPT_FILE:0:1}" == "-" ]]; then + POSTINSTALL_SCRIPT_FILE="" + else + shift + fi + ;; --build-path|-p) if [[ -e "$2" ]]; then BUILD_DIRECTORY="${2%/}" @@ -116,6 +141,20 @@ done # create PGKROOT structure mkdir -p "${PGKROOT}${OUTSET_ROOT}" +if $POSTINSTALL_SCRIPT; then + mkdir -p "${SCRIPT_DIR}" + if [[ -n $POSTINSTALL_SCRIPT_FILE ]]; then + if [[ -e "${POSTINSTALL_SCRIPT_FILE}" ]]; then + cp "${POSTINSTALL_SCRIPT_FILE}" "${SCRIPT_DIR}/postinstall" + else + exitWithError "${POSTINSTALL_SCRIPT_FILE} doesn't exist" + fi + else + echo "${ONDEMAND_POSTINSTALL}" > "${SCRIPT_DIR}/postinstall" + fi + chmod 755 "${SCRIPT_DIR}/postinstall" +fi + if [[ -n $FILE_TO_PACKAGE ]]; then PKGID="${PKG_DOMAIN}.${FILE_TARGET}-${FILE_TO_PACKAGE##*/}" PKGNAME="outset-${FILE_TARGET}-${FILE_TO_PACKAGE##*/}_v${PKGVERSION}" @@ -125,6 +164,10 @@ if [[ -n $FILE_TO_PACKAGE ]]; then if [[ -e "${FILE_TO_PACKAGE}" ]]; then cp "${FILE_TO_PACKAGE}" "${TARGET_DIR}" + # if the file is not a pkg or dmg, make it executable + if $MAKE_EXECUTABLE && [[ "${FILE_TO_PACKAGE##*.}" != "pkg" ]] && [[ "${FILE_TO_PACKAGE##*.}" != "dmg" ]]; then + chmod 755 "${TARGET_DIR}/${FILE_TO_PACKAGE##*/}" + fi else exitWithError "${FILE_TO_PACKAGE} doesn't exist" fi @@ -139,7 +182,11 @@ fi TMP_PKG="${BUILD_DIRECTORY}/${PKGNAME}.component.pkg" BUILD_PKG="${BUILD_DIRECTORY}/${PKGNAME}.pkg" -/usr/bin/pkgbuild --root "${PGKROOT}" --identifier ${PKGID} --version ${PKGVERSION} "${TMP_PKG}" +if $POSTINSTALL_SCRIPT; then + /usr/bin/pkgbuild --root "${PGKROOT}" --scripts "${SCRIPT_DIR}" --identifier ${PKGID} --version ${PKGVERSION} "${TMP_PKG}" +else + /usr/bin/pkgbuild --root "${PGKROOT}" --identifier ${PKGID} --version ${PKGVERSION} "${TMP_PKG}" +fi /usr/bin/productbuild --identifier ${PKGID} --package "${TMP_PKG}" "${BUILD_PKG}" # clean up diff --git a/legacy/pkgroot/usr/local/outset/outset.py b/legacy/pkgroot/usr/local/outset/outset.py index 78fbac4..7fcfcfd 100755 --- a/legacy/pkgroot/usr/local/outset/outset.py +++ b/legacy/pkgroot/usr/local/outset/outset.py @@ -1,7 +1,7 @@ #!/usr/local/outset/python3 """ -This script automatically processes packages, profiles, and/or scripts at +This script automatically processes packages and/or scripts at boot, on demand, and/or login. """ @@ -35,12 +35,11 @@ import sys import time import warnings -from distutils.version import StrictVersion as version from platform import mac_ver from stat import S_IWOTH, S_IXOTH __author__ = "Joseph Chilcote (chilcote@gmail.com)" -__version__ = "3.0.3" +__version__ = "3.0.4" if not sys.warnoptions: warnings.simplefilter("ignore") @@ -232,27 +231,6 @@ def install_package(pkg): return True -def install_profile(pathname): - """Install mobileconfig located at given pathname""" - # profiles has new verbs in 10.13. - if version(mac_ver()[0]) >= version("10.13"): - cmd = ["/usr/bin/profiles", "install", "-path=%s" % pathname] - else: - cmd = ["/usr/bin/profiles", "-IF", pathname] - - try: - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - logging.info("Installing profile %s", pathname) - (_, err) = proc.communicate() - if err: - logging.error("Failure processing %s: %s", pathname, err.decode('utf-8')) - return False - except OSError as err: - logging.error("Failure processing %s: %s", pathname, err.decode('utf-8')) - return False - return True - - def run_script(pathname): """Runs script located at given pathname""" logging.info("Processing %s", pathname) @@ -286,7 +264,6 @@ def process_items(path, delete_items=False, once=False, override={}): items_to_process = [] packages = [] scripts = [] - profiles = [] d = {} for dirpath, _, files in os.walk(path): @@ -301,8 +278,6 @@ def process_items(path, delete_items=False, once=False, override={}): if check_perms(pathname): if pathname.lower().endswith(("pkg", "mpkg", "dmg")): packages.append(pathname) - elif pathname.lower().endswith("mobileconfig"): - profiles.append(pathname) else: scripts.append(pathname) else: @@ -330,21 +305,6 @@ def process_items(path, delete_items=False, once=False, override={}): if delete_items: cleanup(package) - for profile in profiles: - if once: - if profile not in d: - if install_profile(profile): - d[profile] = datetime.datetime.now() - else: - if profile in override: - if override[profile] > d[profile]: - if install_profile(profile): - d[profile] = datetime.datetime.now() - else: - install_profile(profile) - if delete_items: - cleanup(profile) - for script in scripts: if once: if script not in d: @@ -369,7 +329,7 @@ def main(): parser = argparse.ArgumentParser( description="This script automatically \ - processes packages, profiles, and/or scripts at boot, on demand,\ + processes packages and/or scripts at boot, on demand,\ and/or login." ) group = parser.add_mutually_exclusive_group(required=True)