Security patches only take effect after a reboot. In practice, users postpone restarts indefinitely β and the longer a Device runs without rebooting, the more security updates accumulate in a pending state. Machines that have been on for weeks also tend to show degraded performance and memory pressure that a simple restart would resolve.
The problem with forcing a reboot with an abrupt shutdown is that it causes interruptions and frustration. This Script takes a more gradual approach: it monitors uptime and escalates progressively, starting with a discreet notification and only forcing a countdown when the Device has been running for 13 or more days. Users get enough warning to save their work; IT gets the assurance that reboots actually happen.
Alerts are displayed using swiftDialog (installed automatically if not present) with your company's branding, so users recognize them as legitimate IT communications.
The Applivery Agent for macOS must be installed and active on the Device. Learn more about the macOS Agent.
Requirements
| Requirement | Detail |
|---|---|
| Platform | macOS 11.0 (Big Sur) or later |
| Execution privileges | Root (default in Applivery) |
| Corporate branding | /var/root/CompanyAssets/logo.png (optional, for branded dialogs) |
| swiftDialog | Installed automatically if not already present |
Escalation levels
The Script calculates the current system uptime in days and applies a four-level escalation policy:
| Uptime | Action |
|---|---|
| 0β4 days | No action |
| 5β8 days | macOS toast notification with an audible alert |
| 9β12 days | Dialog window with Restart now and Postpone (24h) options. Closes automatically after 14 minutes |
| 13+ days | Full-screen warning followed by a 10-minute countdown. The Mac restarts when the timer reaches zero, or the user clicks Restart now |
Setup
Before deploying this Script, make sure your company logo is available at /var/root/CompanyAssets/logo.png on each managed Device. The Script resizes and copies the logo to the swiftDialog resources folder automatically. Using the corporate logo ensures users recognize the alerts as IT communications and don't dismiss them as noise. You can distribute the file using Applivery File Management.
Once in theΒ Applivery Dashboard, follow the steps described here to create a Script. Paste the following Script into the editor, select Bash as the language, give it a descriptive name (e.g., Force Reboot Policy), and click Create.
#!/bin/bash
# ---
# Title: Force Reboot Policy (Uptime Enforcement)
# Description: Monitors macOS uptime and triggers escalating alerts via swiftDialog to force a restart.
# Author: Applivery
# Version: 1.2.0
# ---
# ==========================================
# 1. CLEANUP OLD INSTALLATIONS
# ==========================================
DIALOG_OLD="/Applications/Dialog.app"
if [ -d "$DIALOG_OLD" ]; then
echo "β Old Dialog.app found"
pgrep -if "Dialog.app" && {
echo " β Quitting Dialog..."
pkill -if "Dialog.app" 2>/dev/null
sleep 1
}
if sudo rm -rf "$DIALOG_OLD" 2>/dev/null; then
echo " β Removed successfully"
else
echo " β Failed to remove legacy app."
fi
else
echo "β No old Dialog.app present"
fi
# ==========================================
# 2. PRE-FLIGHT & BRANDING SETUP
# ==========================================
PATH=/usr/bin:/bin:/usr/sbin:/sbin
DIALOG_CLI="/usr/local/bin/dialog"
DIALOG_APP="/Library/Application Support/Dialog/Dialog.app"
DIALOG_ICON_DIR="/Library/Application Support/Dialog"
DIALOG_ICON="$DIALOG_ICON_DIR/Dialog.png"
BRAND_ICON="/var/root/CompanyAssets/logo.png"
needs_install=0
needs_reinstall=0
CURRENT_USER=$(stat -f %Su /dev/console)
USER_ID=$(id -u "$CURRENT_USER" 2>/dev/null || true)
get_swiftdialog_pkg_url() {
local url
url="$(/usr/bin/curl -fsSL -H "Accept: application/vnd.github+json" -H "User-Agent: Force_Reboot" \
"https://api.github.com/repos/swiftDialog/swiftDialog/releases/latest" | \
/usr/bin/sed -nE 's/.*"browser_download_url":"([^"]*\.pkg)".*/\1/p' | \
/usr/bin/head -n 1)"
[ -n "$url" ] && echo "$url" && return 0
return 1
}
run_as_user() {
if [ -z "$CURRENT_USER" ] || [ "$CURRENT_USER" = "loginwindow" ] || [ -z "$USER_ID" ]; then
return 1
fi
launchctl asuser "$USER_ID" /usr/bin/sudo -u "$CURRENT_USER" -- "$@"
}
if [ ! -x "$DIALOG_CLI" ] || [ ! -d "$DIALOG_APP" ]; then
needs_install=1
fi
mkdir -p "$DIALOG_ICON_DIR"
chmod 755 "$DIALOG_ICON_DIR"
if [ -f "$BRAND_ICON" ]; then
tmp_brand="$(/usr/bin/mktemp /tmp/dialog_brand.XXXXXX.png)"
if ! sips -z 512 512 "$BRAND_ICON" --out "$tmp_brand" >/dev/null 2>&1; then
/bin/cp "$BRAND_ICON" "$tmp_brand"
fi
if [ -f "$tmp_brand" ]; then
if [ ! -f "$DIALOG_ICON" ] || ! cmp -s "$tmp_brand" "$DIALOG_ICON"; then
cp "$tmp_brand" "$DIALOG_ICON"
chmod 644 "$DIALOG_ICON"
chown root:wheel "$DIALOG_ICON" >/dev/null 2>&1
needs_reinstall=1
fi
fi
rm -f "$tmp_brand"
fi
# ==========================================
# 3. SWIFTDIALOG INSTALLATION
# ==========================================
if [ "$needs_install" -eq 1 ] || [ "$needs_reinstall" -eq 1 ]; then
pkg_url="$(get_swiftdialog_pkg_url 2>/dev/null || true)"
if [ -n "$pkg_url" ]; then
pkg_path="/tmp/swiftDialog_$(date +%s).pkg"
/usr/bin/curl -fL --retry 3 --retry-delay 1 "$pkg_url" -o "$pkg_path"
installer -pkg "$pkg_path" -target /
rm -f "$pkg_path"
else
echo "ERROR: Could not retrieve swiftDialog URL." >&2
exit 1
fi
fi
killall Dialog 2>/dev/null
# ==========================================
# 4. UPTIME CALCULATION
# ==========================================
current_unix_time="$(date '+%s')"
boot_time_unix="$(sysctl -n kern.boottime | awk -F 'sec = |, usec' '{ print $2; exit }')"
uptime_seconds="$(( current_unix_time - boot_time_unix ))"
uptime_days="$(( uptime_seconds / 86400 ))"
# TEST_UPTIME_DAYS="7" # Uncomment for testing
if [ -n "$TEST_UPTIME_DAYS" ]; then
uptime_days="$TEST_UPTIME_DAYS"
fi
# ==========================================
# 5. ESCALATION LOGIC
# ==========================================
if [ "$uptime_days" -le 4 ]; then
echo "Uptime: $uptime_days days. No action needed."
exit 0
elif [ "$uptime_days" -ge 5 ] && [ "$uptime_days" -le 8 ]; then
echo "Uptime: $uptime_days days. Showing Notification."
run_as_user "$DIALOG_CLI" --notification \
--title "$uptime_days days without a reboot!" \
--message "Your Mac needs to restart to regain performance and apply security updates."
afplay "/System/Library/Sounds/Sosumi.aiff"
exit 0
elif [ "$uptime_days" -ge 9 ] && [ "$uptime_days" -le 12 ]; then
echo "Uptime: $uptime_days days. Showing Dialog with Postpone."
afplay "/System/Library/Sounds/Sosumi.aiff" &
run_as_user "$DIALOG_CLI" \
--title "Restart Required" \
--message "*${uptime_days} days without a reboot!* \n\nPlease save your work and restart. If postponed, you will be reminded in 24 hours." \
--icon "$DIALOG_ICON" \
--button1text "Restart now" \
--button2text "Postpone" \
--timer 840 --width 650 --height 280 --position bottomright --ontop
dialog_results=$?
elif [ "$uptime_days" -ge 13 ]; then
echo "Uptime: $uptime_days days. Final warning."
afplay "/System/Library/Sounds/Sosumi.aiff" & sleep 0.2 && afplay "/System/Library/Sounds/Sosumi.aiff" &
run_as_user "$DIALOG_CLI" \
--title "Restart Required" \
--message "*${uptime_days} days without a reboot!* \n\n*After pressing I Understand, you will have 10 minutes to save your work.*" \
--icon "$DIALOG_ICON" --button1text "I Understand" --width 650 --height 230 --blurscreen --ontop
run_as_user "$DIALOG_CLI" \
--title none --message "Computer will restart when the timer reaches zero." \
--button1text "Restart now" --timer 600 --width 320 --height 110 --position bottomright --icon none --ontop
dialog_results=$?
fi
# ==========================================
# 6. REBOOT EXECUTION
# ==========================================
if [ "$dialog_results" = "0" ] || [ "$dialog_results" = "4" ]; then
echo "Rebooting now..."
shutdown -r now
sleep 2
reboot
elif [ "$dialog_results" = "2" ]; then
echo "User postponed the restart."
fi
exit 0
Now, navigate to any of your Devices, select the Scripts tab, click on the + Assign Script button, and select the one you just created.
You can also assign Scripts to Policies. To do this, navigate to the Policies section, select the desired Policy, and click on the Scripts tab. The process will be the same as when assigning it directly to an individual Device.
| Method | Behaviour | Recommended? |
|---|---|---|
| Once | Runs one time per Device. | β Not suitable β this Script needs to run continuously to monitor uptime. |
| Loop | Runs repeatedly at the configured interval (15m, 1h, 6h, 1d, 7d). | β
Recommended β select the daily interval (1d) to check uptime every 24 hours and escalate alerts progressively. |
| On demand | Only runs when manually triggered. | β Not suitable for automated uptime enforcement. |
This Script does not require any arguments. System uptime is calculated automatically at runtime. Click Add to save the assignment.
What users will see
Level 2 β Notification (days 5β8): A non-intrusive macOS notification appears in the upper-right corner with an audible alert. The user can dismiss it and continue working.
Level 3 β Dialog with postpone option (days 9β12): A dialog window appears in the bottom-right corner. The user can click Restart now or Postpone. The dialog closes automatically after 14 minutes.
Level 4 β Forced countdown (day 13+): A full-screen blurred warning appears first. After the user acknowledges it, a 10-minute countdown begins in the bottom-right corner. When it reaches zero β or the user clicks Restart now β the Mac restarts.
Testing the Script
To test a specific escalation level without waiting for the real uptime, uncomment the TEST_UPTIME_DAYS line in the Script and set it to the desired number of days:
TEST_UPTIME_DAYS="7" # Uncomment for testing
Remember to comment it out again before deploying to production.
Available on GitHub
This Script is part of the Applivery Public Script Repository. A Device without a reboot is a patch without effect β this progressive escalation Policy keeps your fleet up to date without disrupting anyone.