-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathdeferralsExample.sh
executable file
·278 lines (226 loc) · 10.7 KB
/
deferralsExample.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#!/bin/zsh
#set -x
# deferralsExample.sh
# v.1.0
# Written by Trevor Sysock @BigMacAdmin
# Please see the software license in the associated GitHub repo:
# https://github.com/SecondSonConsulting/swiftDialogExamples
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
# Overview:
# This script is meant to be an example framework for how to prompt users
# to complete an action and offer deferrals.
# The concept here is that you can call a script repeatedly on whatever
# schedule suits your needs. The script will exit clean and quiet if a
# deferral is active, or if the "check_the_things" function decides no action
# is needed.
######################
# How to Configure #
######################
# All time/date calculations are given in seconds
# Admins will want to at least configure everything within the "User Configuration Variables"
# and the "User Configuration Functions" section of the script.
#########
# Tools #
#########
# Path to SwiftDialog
dialogPath="/usr/local/bin/dialog"
# Path to PlistBuddy. We'll use this to write to our config file
pBuddy="/usr/libexec/PlistBuddy"
################################
# User Configuration Variables #
################################
# How long until a deferral expires? Value in seconds. Set this low for testing.
deferralDuration="10"
# How many deferrals until you no longer offer them? In this example, the DeferralCount value is reset
# when the "do_the_things" action completes successfully.
deferralMaximum="3"
# Deadline Date. This is a unix epoch time value.
# To get this value converted from a human readable date you can use a "unix epoch time"
# calculator like this one: https://www.epochconverter.com/
# If the script runs after this deadline date, no deferrals will be offered.
# Leave empty if you don't want to use this feature
deadlineDate=""
# Full file path to your configuration profile.
# For this example, we'll just stay in the current working directory.
#
# You will want to consider "Is this a root daemon or a user agent?" as well as
# whether you want separate deferral files for multiple users or one file
# for the system.
deferralPlist="com.bigmacadmin.deferralexample.plist"
#################################
# User Configuration Functions #
#################################
function check_the_things()
{
# Use this if you need to add a check to see if the purpose of this script needs to be actioned on.
# For example, if you wanted this script to take action when the "uptime" of a device has exceeded a
# certain value, here is where you would put that check.
# If "check the things" exits true, then the script continues on. If it exits false (non-zero exit/return code) then
# the thing doesn't need to happen and the script exits.
# You can omit this function entirely if you want the script to take action always.
# For our example, we'll use "If true" which always returns true (or exit code zero) Change to "if false" for
# testing the opposite.
if true; then
log_message "Conditions met. Script will continue"
else
cleanup_and_exit 0 "Script is not needed. Exiting"
fi
}
function do_the_things()
{
# This is where you put the actual action you want the script to take. This is executed when the user consents by
# clicking "OK" on the Dialog window
# For our example, we'll just use "true". To test behavior on function failure, change to "false".
true
# Since we did the things, we'll set the deferral count back to 0.
# You may want to move this elsewhere in the script, or do things differently. For our example, it makes sense
# to put this here.
$pBuddy -c "Set DeferralCount 0" $deferralPlist
}
function dialog_prompt_with_deferral()
{
# This is where we define the dialog window options asking the user if they want to do the thing.
"$dialogPath" \
--title "Please do the thing" \
--message "Hey, we need you to do the thing. Is this an ok time? If not, we'll bug you again later." \
--icon "SF=bolt.circle color1=pink color2=blue" \
--button2text "Not Now"
}
function dialog_prompt_no_deferral()
{
# This is where we define the dialog window options when we're no longer offering deferrals. "Aggressive mode"
# so to speak.
"$dialogPath" \
--title "We must now do the thing" \
--message "Hey, we can't put off the thing any longer. It's going to be done now." \
--icon "SF=bolt.circle color1=pink color2=blue" \
}
##################
# Core Functions #
##################
# Send a message to the log. For the example it just echos to standard out
function log_message()
{
echo "$(date): $@"
}
# This function exits the script. Takes two arguments. Argument 1 is the exit code
# and argument 2 is an optional log message
# Example: cleanup_and_exit 0 "We are done, no problems found."
function cleanup_and_exit()
{
# If you have temp folders/files that you want to delete as this script exits, this is the place to add that
log_message "${2}"
exit "${1}"
}
function verify_config_file()
{
# Check if we can write to the configuration file by writing something then deleting it.
if $pBuddy -c "Add Verification string Success" "$deferralPlist" > /dev/null 2>&1; then
$pBuddy -c "Delete Verification string Success" "$deferralPlist" > /dev/null 2>&1
else
# This should only happen if there's a permissions problem or if the deferralPlist value wasn't defined
cleanup_and_exit 1 "ERROR: Cannot write to the deferral file: $deferralPlist"
fi
# See below for what this is doing
verify_deferral_value "ActiveDeferral"
verify_deferral_value "DeferralCount"
}
function verify_deferral_value()
{
# Takes an argument to determine if the value exists in the deferral plist file.
# If the value doesn't exist, it writes a 0 to that value as an integer
# We always want some value in there so that PlistBuddy doesn't throw errors
# when trying to read data later
if ! $pBuddy -c "Print :$1" "$deferralPlist" > /dev/null 2>&1; then
$pBuddy -c "Add :$1 integer 0" "$deferralPlist" > /dev/null 2>&1
fi
}
function check_for_active_deferral()
{
# This function checks if there is an active deferral present. If there is, then it exits quietly.
# Get the current deferral value. This will be 0 if there is no active deferral
currentDeferral=$($pBuddy -c "Print :ActiveDeferral" "$deferralPlist")
# If unixEpochTime is less than the current deferral time, it means there is an active deferral and we exit
if [ "$unixEpochTime" -lt "$currentDeferral" ]; then
cleanup_and_exit 0 "Active deferral found. Exiting"
else
log_message "No active deferral."
# We'll delete the "human readable" deferral date value, if it exists.
$pBuddy -c "Delete :HumanReadableDeferralDate" "$deferralPlist" > /dev/null 2>&1
fi
}
function execute_deferral()
{
# This is where we define what happens when the user chooses to defer
# Setting deferral variables
# Set the date the deferral will expire. If the script runs again before this date, it exits quietly without
# bothering the user.
deferralDateSeconds=$((unixEpochTime + deferralDuration ))
# This is a human readable date format of the deferral date. This serves no function except to make it easy
# to tell when the deferral will expire.
deferralDateReadable=$(date -j -f %s $deferralDateSeconds)
# Increase the number of deferrals by 1. This gets checked against the maximum allowed deferrals next time
# the script runs.
deferralCount=$(( deferralCount + 1 ))
# Writing deferral values to the plist
$pBuddy -c "Set ActiveDeferral $deferralDateSeconds" $deferralPlist
$pBuddy -c "Set DeferralCount $deferralCount" $deferralPlist
$pBuddy -c "Add :HumanReadableDeferralDate string $deferralDateReadable" "$deferralPlist" > /dev/null 2>&1
# Deferral has been processed. Exit cleanly.
cleanup_and_exit 0 "User chose deferral $deferralCount of $deferralMaximum. Deferral date is $deferralDateReadable"
}
######################
# Script Starts Here #
######################
verify_config_file
# Get the current date in seconds (unix epoc time)
unixEpochTime=$(date +%s)
check_for_active_deferral
check_the_things
# Get the current deferral count
deferralCount=$($pBuddy -c "Print :DeferralCount" $deferralPlist)
# This next block does the logic to determine if we're going to allow deferrals or not
# Check if Deadline has been set, and if we are now past it
if [ ! -z "$deadlineDate" ] && [ "$deadlineDate" -lt "$unixEpochTime" ]; then
# Deadline has been configured, and we're past it.
allowDeferral="false"
# Check if the number of deferrals used is greater than the maximum allowed
elif [ "$deferralCount" -ge "$deferralMaximum" ]; then
allowDeferral="false"
else
# Deadline isn't past and the deferral count hasn't been exceeded, so we'll allow deferrals.
allowDeferral="true"
fi
# For the sake of this example the logic below is simplified in the following ways:
# - It assumes that exiting 0 is consent to do the thing
# - It assumes exiting Dialog with anything other than 0 is a deferral (so CMD+Q, or if they `killall Dialog`
# it will process a deferral)
# - If we're not offering a deferral, then "do_the_things" gets executed regardless of the dialog exit code
# If you'd like to do things differently, you can capture the exit code of the "dialog_prompt_with_deferral" function
# and do an if/elif/else or case statement to take action based on that exit code.
# If we're allowing deferrals, then
if [ "$allowDeferral" = "true" ]; then
# Prompt the user to ask for consent. If it exits 0, they clicked OK and we'll do the things
if dialog_prompt_with_deferral; then
# Here is where the actual things we want to do get executed
do_the_things
# Capture the exit code of our things, so we can exit the script with the same exit code
thingsExitCode=$?
cleanup_and_exit $thingsExitCode "Things were done. Exit code: $thingsExitCode"
else
execute_deferral
fi
else
# We are NOT allowing deferrals, so we'll continue with or without user consent
dialog_prompt_no_deferral
do_the_things
# Capture the exit code of our things, so we can exit the script with the same exit code
thingsExitCode=$?
cleanup_and_exit $thingsExitCode "Things were done. Exit code: $thingsExitCode"
fi