mirror of
https://github.com/munki/munki.git
synced 2026-01-05 05:59:57 -06:00
Ensure that 60, 30, 10, and 5 minute notifications are *all* seen before forcefully rebooting a machine, not merely the 60 minute notification. Previously, if the 60m notice was seen, then the machine was put to sleep for 55m+, it could reboot without another notice after waking up.
202 lines
7.4 KiB
Python
Executable File
202 lines
7.4 KiB
Python
Executable File
#!/usr/bin/python
|
|
# encoding: utf-8
|
|
#
|
|
# Copyright 2011-2014 Greg Neagle.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# https://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""
|
|
logouthelper
|
|
|
|
Created by Greg Neagle on 2011-06-21.
|
|
|
|
A helper tool for forced logouts to allow munki to force install items by
|
|
a certain deadline.
|
|
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
from munkilib import munkicommon
|
|
from munkilib import FoundationPlist
|
|
from munkilib import updatecheck
|
|
from Foundation import NSDate
|
|
from Foundation import NSDictionary
|
|
from Foundation import NSDistributedNotificationCenter
|
|
from Foundation import NSNotificationDeliverImmediately
|
|
from Foundation import NSNotificationPostToAllSessions
|
|
|
|
|
|
NOTIFICATION_MINS = [240, 180, 120, 90, 60, 45, 30, 15, 10, 5]
|
|
MANDATORY_NOTIFICATIONS = [60, 30, 10, 5]
|
|
|
|
def log(msg):
|
|
'''Logs messages from this tool with an identifier'''
|
|
PROCESS_ID = 'com.googlecode.munki.logouthelper'
|
|
munkicommon.log('%s: %s' % (PROCESS_ID, msg))
|
|
|
|
|
|
def earliestForceInstallDate():
|
|
'''Check installable packages for force_install_after_dates
|
|
Returns None or earliest force_install_after_date converted to local time
|
|
'''
|
|
earliest_date = None
|
|
|
|
ManagedInstallDir = munkicommon.pref('ManagedInstallDir')
|
|
installinfo_types = {
|
|
'InstallInfo.plist' : 'managed_installs',
|
|
'AppleUpdates.plist': 'AppleUpdates'
|
|
}
|
|
installinfopath = os.path.join(ManagedInstallDir, 'InstallInfo.plist')
|
|
|
|
try:
|
|
installinfo = FoundationPlist.readPlist(installinfopath)
|
|
except FoundationPlist.NSPropertyListSerializationException:
|
|
return None
|
|
|
|
for plist_name in installinfo_types.keys():
|
|
key_to_check = installinfo_types[plist_name]
|
|
plist_path = os.path.join(ManagedInstallDir, plist_name)
|
|
try:
|
|
installinfo = FoundationPlist.readPlist(plist_path)
|
|
except FoundationPlist.NSPropertyListSerializationException:
|
|
continue
|
|
|
|
for install in installinfo.get(key_to_check, []):
|
|
force_install_date = install.get('force_install_after_date')
|
|
|
|
if force_install_date:
|
|
force_install_date = updatecheck.subtractTimeZoneOffsetFromDate(
|
|
force_install_date)
|
|
if not earliest_date or force_install_date < earliest_date:
|
|
earliest_date = force_install_date
|
|
|
|
return earliest_date
|
|
|
|
|
|
def alertUserOfForcedLogout(info=None):
|
|
'''Uses Managed Software Center.app to notify the user of an
|
|
upcoming forced logout.
|
|
|
|
Args:
|
|
info: dict of data to send with the notification.
|
|
'''
|
|
consoleuser = munkicommon.getconsoleuser()
|
|
if not munkicommon.findProcesses(
|
|
exe="/Applications/Managed Software Center.app",
|
|
user=consoleuser):
|
|
# Managed Software Center.app isn't running.
|
|
# Use our LaunchAgent to start
|
|
# Managed Software Center.app in the user context.
|
|
launchfile = '/var/run/com.googlecode.munki.ManagedSoftwareUpdate'
|
|
f = open(launchfile, 'w')
|
|
f.close()
|
|
time.sleep(0.5)
|
|
if os.path.exists(launchfile):
|
|
os.unlink(launchfile)
|
|
# now wait a bit for it to launch before proceeding
|
|
# because if we don't, sending the logoutwarn notification
|
|
# may fall on deaf ears.
|
|
time.sleep(4)
|
|
|
|
# if set, convert Python dictionary to NSDictionary.
|
|
if info is not None:
|
|
info = NSDictionary.dictionaryWithDictionary_(info)
|
|
# cause MSC.app to display the Forced Logout warning
|
|
dnc = NSDistributedNotificationCenter.defaultCenter()
|
|
dnc.postNotificationName_object_userInfo_options_(
|
|
'com.googlecode.munki.ManagedSoftwareUpdate.logoutwarn',
|
|
None, info,
|
|
NSNotificationDeliverImmediately + NSNotificationPostToAllSessions)
|
|
|
|
# make sure flag is in place to cause munki to install at logout
|
|
f = open('/private/tmp/com.googlecode.munki.installatlogout', 'w')
|
|
f.close()
|
|
|
|
|
|
def main():
|
|
'''Check for logged-in users and upcoming forced installs;
|
|
notify the user if needed; sleep a minute and do it again.'''
|
|
log('launched')
|
|
sent_notifications = []
|
|
logout_time_override = None
|
|
# datetime of now plus largest MANDATORY_NOTIFICATIONS value (with padding).
|
|
minimum_notifications_logout_time = NSDate.date().addTimeInterval_(
|
|
60 * max(MANDATORY_NOTIFICATIONS) + 30)
|
|
while True:
|
|
if not munkicommon.currentGUIusers():
|
|
# no-one is logged in, so bail
|
|
log('no-one logged in')
|
|
time.sleep(10) # makes launchd happy
|
|
log('exited')
|
|
exit(0)
|
|
|
|
# we check each time because items might have been added or removed
|
|
# from the list; or their install date may have been changed.
|
|
next_logout_time = earliestForceInstallDate()
|
|
if not next_logout_time:
|
|
# no forced logout needed, so bail
|
|
log('no forced installs found')
|
|
time.sleep(10) # makes launchd happy
|
|
log('exited')
|
|
exit(0)
|
|
|
|
if logout_time_override is None:
|
|
logout_time = next_logout_time
|
|
else:
|
|
# allow the new next_logout_time from InstallInfo to be used
|
|
# if it has changed to a later time since when we decided to
|
|
# override it.
|
|
if next_logout_time > logout_time_override:
|
|
logout_time = next_logout_time
|
|
log('reset logout_time to: %s' % logout_time)
|
|
logout_time_override = None
|
|
sent_notifications = []
|
|
|
|
# always give at least MANDATORY_NOTIFICATIONS warnings
|
|
if logout_time < minimum_notifications_logout_time:
|
|
for mandatory_notification in MANDATORY_NOTIFICATIONS:
|
|
if mandatory_notification not in sent_notifications:
|
|
# logout time is in the past, and a mandatory notification
|
|
# has not been sent, so reset the logout_time to the future.
|
|
log('%d minute notification not sent.'
|
|
% mandatory_notification)
|
|
logout_time = NSDate.date(
|
|
).addTimeInterval_(60 * mandatory_notification + 30)
|
|
log('reset logout_time to: %s' % logout_time)
|
|
logout_time_override = logout_time
|
|
break
|
|
|
|
minutes_until_logout = int(logout_time.timeIntervalSinceNow() / 60)
|
|
info = {'logout_time': logout_time}
|
|
if minutes_until_logout in NOTIFICATION_MINS:
|
|
sent_notifications.append(minutes_until_logout)
|
|
log('Warning user of %s minutes until forced logout'
|
|
% minutes_until_logout)
|
|
alertUserOfForcedLogout(info)
|
|
elif minutes_until_logout < 1:
|
|
log('Forced logout in 60 seconds')
|
|
alertUserOfForcedLogout(info)
|
|
|
|
time.sleep(60)
|
|
if minutes_until_logout < 1:
|
|
break
|
|
|
|
if munkicommon.currentGUIusers() and earliestForceInstallDate():
|
|
log('Beginning forced logout')
|
|
munkicommon.forceLogoutNow()
|
|
log('exited')
|
|
exit(0)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|