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)