diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..81559b9 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,6 @@ +semi: false +overrides: + - files: + - "*.md" + options: + tabWidth: 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index ec0ede5..291ba89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,26 +4,37 @@ No date +## [2.1] + +21.04.2022 + +- Remove `softwareupdate` downloading stage as can cause unexpected installation. +- Bugfix for identifying updates that do not require a restart. +- New `/Library/Scripts/nice_updater_status.txt` file that can be read by a Jamf Extension Attribute for easy checking of run status. +- Changed default defer count to 8. +- Changed default wait after previous update to 7 days. +- Other minor bugfixes. + ## [2.0.3] 21.05.2021 -- Bugfix for issue #1 +- Bugfix for issue #1 ## [2.0.2] 12.05.2021 -- Runs `jamfHelper` as the current user (this may not be necessary - was introduced to try and fix a problem where the tool was not showing though was running). -- Changed default defer count (back) to 10. +- Runs `jamfHelper` as the current user (this may not be necessary - was introduced to try and fix a problem where the tool was not showing though was running). +- Changed default defer count (back) to 10. ## [2.0.1] 30.04.2021 -- The uninstaller script now forgets the package. -- A delay is introduced after a user closes the Software Update pane before bringing the dialog back. This is to primarily prevent the popup showing up while a restart is happening. (Ideally we would be able to check if the restart has been initiated, but that is not happening yet.) -- Added the CHANGELOG.md file. +- The uninstaller script now forgets the package. +- A delay is introduced after a user closes the Software Update pane before bringing the dialog back. This is to primarily prevent the popup showing up while a restart is happening. (Ideally we would be able to check if the restart has been initiated, but that is not happening yet.) +- Added the CHANGELOG.md file. ## [2.0] @@ -44,9 +55,9 @@ Also, the last notification message no longer times out after 300s. It will stay 27.08.2019 -- Replaced StartInterval with StartCalendarInterval to ensure script starts regularly. -- Created an uninstaller script -- Created a post-install script for Jamf which will allow parameters to be overridden in a policy. +- Replaced StartInterval with StartCalendarInterval to ensure script starts regularly. +- Created an uninstaller script +- Created a post-install script for Jamf which will allow parameters to be overridden in a policy. ## [1.6] @@ -78,7 +89,8 @@ Changed the default button of the jamfHelper dialogs to Cancel, because after ti Also shortened the timeout to 82800 from 99999 seconds to prevent overlap of two days' dialogs. -[untagged]: https://github.com/grahampugh/nice-updater/compare/v2.0.3...HEAD +[untagged]: https://github.com/grahampugh/nice-updater/compare/v2.1...HEAD +[2.1]: https://github.com/grahampugh/nice-updater/compare/v2.0.3...v2.1 [2.0.3]: https://github.com/grahampugh/nice-updater/compare/v2.0.2...v2.0.3 [2.0.2]: https://github.com/grahampugh/nice-updater/compare/v2.0.1...v2.0.2 [2.0.1]: https://github.com/grahampugh/nice-updater/compare/v2.0...v2.0.1 diff --git a/build.sh b/build.sh index 92e2ee2..83000f6 100755 --- a/build.sh +++ b/build.sh @@ -4,7 +4,7 @@ identifier="com.github.grahampugh.nice_updater" # Default version of the build, you can leave this alone and specify as an argument like so: ./build.sh 1.7 -version="2.0.3" +version="2.1" # The title of the message that is displayed when software updates are in progress and a user is logged in updateRequiredTitle="macOS Software Updates Required" @@ -18,14 +18,17 @@ updateInProgressTitle="Software Update In Progress" # The location of your log, keep in mind that if you nest the log into a folder that does not exist you'll need to mkdir -p the directory as well log="/Library/Logs/Nice_Updater.log" +# The location of the status file, keep in mind that if the folder does not exist you'll need to mkdir -p the directory as well +EAFile="/Library/Scripts/nice_updater_status.txt" + # The number of days to check for updates after a full update has been performed -afterFullUpdateDelayDayCount="14" +afterFullUpdateDelayDayCount="7" # The number of days to check for updates after a updates were checked, but no updates were available afterEmptyUpdateDelayDayCount="3" # The number of times to alert a single user prior to forcibly installing updates -maxNotificationCount="10" +maxNotificationCount="8" # Calendar based start interval - hours and minutes. startIntervalHour="13" # valid is 0-23. If left blank, daemon will launch every hour instead of once per day. @@ -104,6 +107,7 @@ defaults write "$PWD/$preferenceFileName" UpdateRequiredTitle -string "$updateRe defaults write "$PWD/$preferenceFileName" UpdateRequiredMessage -string "$updateRequiredMessage" defaults write "$PWD/$preferenceFileName" UpdateInProgressTitle -string "$updateInProgressTitle" defaults write "$PWD/$preferenceFileName" Log -string "$log" +defaults write "$PWD/$preferenceFileName" EAFile -string "$EAFile" defaults write "$PWD/$preferenceFileName" AfterFullUpdateDelayDayCount -int "$afterFullUpdateDelayDayCount" defaults write "$PWD/$preferenceFileName" AfterEmptyUpdateDelayDayCount -int "$afterEmptyUpdateDelayDayCount" defaults write "$PWD/$preferenceFileName" MaxNotificationCount -int "$maxNotificationCount" @@ -127,6 +131,7 @@ if find "$PWD/custom_icon" -name "*.png" ; then defaults write "$PWD/$preferenceFileName" IconCustomPath -string "$icon_path" else echo "Nothing found at $PWD/custom_icon/*.png" + defaults write "$PWD/$preferenceFileName" IconCustomPath -string "" fi # Copy the LaunchDaemon plists to the temp build directory diff --git a/com.github.grahampugh.nice_updater.prefs.plist b/com.github.grahampugh.nice_updater.prefs.plist index 3500c1c..0ce4845 100644 --- a/com.github.grahampugh.nice_updater.prefs.plist +++ b/com.github.grahampugh.nice_updater.prefs.plist @@ -5,15 +5,17 @@ AfterEmptyUpdateDelayDayCount 3 AfterFullUpdateDelayDayCount - 14 + 7 AlertTimeout 3540 + EAFile + /Library/Scripts/nice_updater_status.txt IconCustomPath - /Library/Scripts/nice_updater_custom_icon.png + Log /Library/Logs/Nice_Updater.log MaxNotificationCount - 10 + 8 UpdateInProgressTitle Software Update In Progress UpdateRequiredMessage diff --git a/nice_updater.sh b/nice_updater.sh index 56a4f1b..6c66549 100755 --- a/nice_updater.sh +++ b/nice_updater.sh @@ -1,7 +1,7 @@ #!/bin/bash # Nice Updater 2 -version="2.0.3" +version="2.1.0" # These variables will be automagically updated if you run build.sh, no need to modify them preferenceFileFullPath="/Library/Preferences/com.github.grahampugh.nice_updater.prefs.plist" @@ -11,6 +11,7 @@ helperTitle=$(defaults read "$preferenceFileFullPath" UpdateRequiredTitle) helperDesc=$(defaults read "$preferenceFileFullPath" UpdateRequiredMessage) alertTimeout=$(defaults read "$preferenceFileFullPath" AlertTimeout) log=$(defaults read "$preferenceFileFullPath" Log) +EAFile=$(defaults read "$preferenceFileFullPath" EAFile) afterFullUpdateDelayDayCount=$(defaults read "$preferenceFileFullPath" AfterFullUpdateDelayDayCount) afterEmptyUpdateDelayDayCount=$(defaults read "$preferenceFileFullPath" AfterEmptyUpdateDelayDayCount) maxNotificationCount=$(defaults read "$preferenceFileFullPath" MaxNotificationCount) @@ -37,34 +38,34 @@ writelog() { /bin/echo "$DATE" " $1" >> "$log" } +write_status() { + /bin/echo "$1" > "$EAFile" +} + finish() { writelog "======== Finished $scriptName ========" exit "$1" } random_delay() { - delay_time=$(( (RANDOM % 60)+1 )) - writelog "Delaying software update check by ${delay_time}s." - sleep ${delay_time}s + delay_time=$(( (RANDOM % 10)+1 )) + writelog "Delaying software update check by ${delay_time}s" + # sleep ${delay_time} } record_last_full_update() { - writelog "Done with update process; recording last full update time." + writelog "Done with update process; recording last full update time" /usr/libexec/PlistBuddy -c "Delete :last_full_update_time" $preferenceFileFullPath 2> /dev/null /usr/libexec/PlistBuddy -c "Add :last_full_update_time string $(date +%Y-%m-%d\ %H:%M:%S)" $preferenceFileFullPath - writelog "Clearing user alert data." + writelog "Clearing user alert data" /usr/libexec/PlistBuddy -c "Delete :users" $preferenceFileFullPath - writelog "Clearing On-Demand Update Key." + writelog "Clearing On-Demand Update Key" /usr/libexec/PlistBuddy -c "Delete :update_key" $preferenceFileFullPath 2> /dev/null /usr/libexec/PlistBuddy -c "Add :update_key array" $preferenceFileFullPath 2> /dev/null } -trigger_nonrestart_updates() { - /usr/sbin/softwareupdate --install "$1" -} - open_software_update() { /usr/bin/open -W /System/Library/PreferencePanes/SoftwareUpdate.prefPane & suPID=$! @@ -74,6 +75,7 @@ open_software_update() { sleep 1 done writelog "Software Update was closed" + write_status "Software Update was closed" was_closed=1 } @@ -88,7 +90,7 @@ compare_date() { alert_user() { local subtitle="$1" - [[ "$notificationsLeft" == "1" ]] && local subtitle="1 remaining alert before auto-install." + [[ "$notificationsLeft" == "1" ]] && local subtitle="1 remaining deferral" [[ "$notificationsLeft" == "0" ]] && local subtitle="No deferrals remaining! Click on \"Install Now\" to proceed" if /usr/bin/pgrep jamfHelper ; then @@ -126,18 +128,21 @@ alert_user() { pkill jamfHelper helperExitCode=1 else - writelog "A button was pressed." + writelog "A button was pressed" fi fi # writelog "Response: $helperExitCode" if [[ $helperExitCode == 0 ]]; then - writelog "User initiated installation." + writelog "User initiated installation" + write_status "User initiated installation" open_software_update elif [[ $helperExitCode == 2 ]]; then - writelog "User cancelled installation." + writelog "User cancelled installation" + write_status "User cancelled installation" else - writelog "Alert timed out without response." + writelog "Alert timed out without response" + write_status "Alert timed out without response" ((notificationCount--)) fi @@ -154,45 +159,59 @@ alert_logic() { if [[ "$notificationCount" -ge "$maxNotificationCount" ]]; then notificationsLeft="$((maxNotificationCount - notificationCount))" writelog "$loggedInUser has been notified $notificationCount times; not waiting any longer." - alert_user "$notificationsLeft remaining alerts before auto-install." "$notificationCount" + alert_user "$notificationsLeft remaining deferrals." "$notificationCount" else ((notificationCount++)) notificationsLeft="$((maxNotificationCount - notificationCount))" - writelog "$notificationsLeft remaining alerts before auto-install." - alert_user "$notificationsLeft remaining alerts before auto-install." "$notificationCount" + writelog "$notificationsLeft remaining deferrals." + alert_user "$notificationsLeft remaining deferrals." "$notificationCount" fi } update_check() { osVersion=$( /usr/bin/sw_vers -productVersion ) writelog "Determining available Software Updates for macOS $osVersion..." - updates=$(/usr/sbin/softwareupdate -l) - updatesNoRestart=$(echo "$updates" | grep -v restart | grep -B1 recommended | grep -v recommended | grep -v "\-\-" | sed 's|.*\* ||g') - updatesRestart=$(echo "$updates" | grep -i restart | grep -v '\*' | cut -d , -f 1) - updateCount=$(echo "$updates" | grep -i -c recommended) + update_file="/tmp/nice_updater_updates.txt" + /usr/sbin/softwareupdate --list > "$update_file" + + # create list of updates that do not require a restart + updatesNoRestart=() + while IFS= read -r line; do + if [[ -n "$line" ]]; then + updatesNoRestart+=("$line") + writelog "Added '$line' to list of updates that do not require a restart" + fi + done <<< "$(grep -v restart "$update_file" | grep -B1 'Recommended: YES' | grep -v -i Recommended | grep -v '\-\-' | sed 's|.*\* ||g' | sed 's|^Label: ||')" + + # create list of updates that do require a restart + updatesRestart=() + while IFS= read -r line; do + if [[ -n "$line" ]]; then + updatesRestart+=("$line") + writelog "Added '$line' to list of updates that require a restart" + fi + done <<< "$(grep -B1 'Recommended: YES, Action: restart' "$update_file" | grep -v restart | grep -v '\-\-' | sed 's|.*\* ||g' | sed 's|^Label: ||')" + + updateCount=$(grep -c "Recommended: YES" "$update_file") if [[ "$updateCount" -gt "0" ]]; then # Download the updates - writelog "Downloading $updateCount update(s)..." - /usr/sbin/softwareupdate --download --recommended | grep --line-buffered Downloaded | while read -r LINE; do writelog "$LINE"; done + # writelog "Downloading $updateCount update(s)..." + # /usr/sbin/softwareupdate --download "${updatesNoRestart[@]}" | grep --line-buffered Downloaded | while read -r LINE; do writelog "$LINE"; done - # Don't waste the user's time - install any updates that do not require a restart first. - if [[ -n "$updatesNoRestart" ]]; then + # install any updates that do not require a restart, as these do not require authentication. + if [[ "${#updatesNoRestart[@]}" -gt 0 ]]; then writelog "Installing updates that DO NOT require a restart in the background..." - while IFS='' read -r line; do - writelog "Updating: $line" - trigger_nonrestart_updates "$line" - done <<< "$updatesNoRestart" + /usr/sbin/softwareupdate --install "${updatesNoRestart[@]}" fi # If the script moves past this point, a restart is required. - if [[ -n "$updatesRestart" ]]; then - writelog "A restart is required for remaining updates." - # If no user is logged in, just update and restart. Check the user now as some time has past since the script began. + if [[ "${#updatesRestart[@]}" -gt 0 ]]; then + writelog "A restart is required for remaining updates" + # Abort if no user is logged in. Check the user now as some time has past since the script began. loggedInUser=$(/usr/sbin/scutil <<< "show State:/Users/ConsoleUser" | /usr/bin/awk -F': ' '/[[:space:]]+Name[[:space:]]:/ { if ( $2 != "loginwindow" ) { print $2 }}') - # loggedInUID=$(id -u "$loggedInUser") if [[ "$loggedInUser" == "root" ]] || [[ -z "$loggedInUser" ]]; then - writelog "No user logged in. Cannot proceed." + writelog "No user logged in. Cannot proceed" else # Getting here means a user is logged in, alert them that they will need to install and restart alert_logic @@ -204,11 +223,13 @@ update_check() { fi else record_last_full_update - writelog "No updates that require a restart available; exiting." + writelog "No updates that require a restart available; exiting" + write_status "No updates that require a restart available; exiting" finish 0 fi else - writelog "No updates at this time; exiting." + writelog "No updates at this time; exiting" + write_status "No updates at this time; exiting" /usr/libexec/PlistBuddy -c "Delete :last_empty_update_time" $preferenceFileFullPath 2> /dev/null /usr/libexec/PlistBuddy -c "Add :last_empty_update_time string $(date +%Y-%m-%d\ %H:%M:%S)" $preferenceFileFullPath /usr/libexec/PlistBuddy -c "Delete :users" $preferenceFileFullPath 2> /dev/null @@ -225,7 +246,8 @@ main() { # See if we are blocking updates, if so exit updatesBlocked=$(/usr/libexec/PlistBuddy -c "Print :updates_blocked" $preferenceFileFullPath 2> /dev/null | xargs 2> /dev/null) if [[ "$updatesBlocked" == "true" ]]; then - writelog "Updates are blocked for this client at this time; exiting." + writelog "Updates are blocked for this client at this time; exiting" + write_status "Updates are blocked for this client at this time; exiting" finish 0 fi @@ -241,27 +263,29 @@ main() { if [[ -n "$lastFullUpdateTime" ]]; then daysSinceLastFullUpdate="$(compare_date "$lastFullUpdateTime")" if [[ "$daysSinceLastFullUpdate" -ge "$afterFullUpdateDelayDayCount" ]]; then - writelog "$afterFullUpdateDelayDayCount or more days have passed since last full update." + writelog "$afterFullUpdateDelayDayCount or more days have passed since last full update" # delay script's actions by up to 1 min to prevent all computers running software update at the same time random_delay update_check else - writelog "Less than $afterFullUpdateDelayDayCount days since last full update; exiting." + writelog "Less than $afterFullUpdateDelayDayCount days since last full update; exiting" + write_status "Less than $afterFullUpdateDelayDayCount days since last full update; exiting" finish 0 fi elif [[ -n "$lastEmptyUpdateTime" ]]; then daysSinceLastEmptyUpdate="$(compare_date "$lastEmptyUpdateTime")" if [[ "$daysSinceLastEmptyUpdate" -ge "$afterEmptyUpdateDelayDayCount" ]]; then - writelog "$afterEmptyUpdateDelayDayCount or more days have passed since last empty update check." + writelog "$afterEmptyUpdateDelayDayCount or more days have passed since last empty update check" # delay script's actions by up to 1 min to prevent all computers running software update at the same time random_delay update_check else - writelog "Less than $afterEmptyUpdateDelayDayCount days since last empty update check; exiting." + writelog "Less than $afterEmptyUpdateDelayDayCount days since last empty update check; exiting" + write_status "Less than $afterEmptyUpdateDelayDayCount days since last empty update check; exiting" finish 0 fi else - writelog "This device might not have performed a full update yet." + writelog "This device might not have performed a full update yet" # delay script's actions by up to 1 min to prevent all computers running software update at the same time random_delay update_check diff --git a/nice_updater_uninstall.sh b/nice_updater_uninstall.sh index 0eb4e45..141cc2f 100755 --- a/nice_updater_uninstall.sh +++ b/nice_updater_uninstall.sh @@ -12,6 +12,7 @@ mainOnDemandDaemonPlist="/Library/LaunchDaemons/${identifier}_on_demand.plist" watchPathsPlist="/Library/Preferences/${identifier}.trigger.plist" preferenceFileFullPath="/Library/Preferences/${identifier}.prefs.plist" iconPath="/Library/Scripts/nice_updater_custom_icon.png" +EAFilePath="/Library/Scripts/nice_updater_status.txt" scriptPath="/Library/Scripts/nice_updater.sh" uninstallScriptPath="/Library/Scripts/nice_updater_uninstall.sh" uninstallScriptName=$(basename "$uninstallScriptPath") @@ -52,8 +53,10 @@ writelog "Deleting NiceUpdater Preferences..." writelog "Deleting NiceUpdater files..." -# Delete the main preferences file +# Delete the icon file [[ -e "$iconPath" ]] && rm -f "$iconPath" +# Delete the status file +[[ -e "$EAFilePath" ]] && rm -f "$iconPath" # Delete the main preferences file [[ -e "$scriptPath" ]] && rm -f "$scriptPath" [[ -e "$uninstallScriptPath" ]] && rm -f "$uninstallScriptPath" diff --git a/postinstall.sh b/postinstall.sh index b09b96a..8534b1a 100644 --- a/postinstall.sh +++ b/postinstall.sh @@ -14,9 +14,9 @@ chown root:wheel /Library/Scripts/nice_updater.sh chmod 755 /Library/Scripts/nice_updater.sh chown root:wheel /Library/Scripts/nice_updater_uninstall.sh chmod 755 /Library/Scripts/nice_updater_uninstall.sh -if [[ -f /Library/Scripts/custom_icon.png ]]; then - chown root:wheel /Library/Scripts/custom_icon.png - chmod 644 /Library/Scripts/custom_icon.png +if [[ -f /Library/Scripts/nice_updater_custom_icon.png ]]; then + chown root:wheel /Library/Scripts/nice_updater_custom_icon.png + chmod 644 /Library/Scripts/nice_updater_custom_icon.pngg fi # Start our LaunchDaemons