Skip to content

Commit

Permalink
Remove DEPNotify support, fix up some things (#82)
Browse files Browse the repository at this point in the history
- Remove DEPNotify entirely from the codebase
- Update the README to direct people to the demo github
- Update the copyright dates for the scripts
- Update the launchdaemon to remove DEPNotify and add all the current features as commented out examples
  • Loading branch information
erikng committed Dec 4, 2020
1 parent 82969c4 commit a783791
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 230 deletions.
60 changes: 3 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ Note that you cannot use a `Mac Developer:` signing identity as that is used for
`An installer signing identity (not an application signing identity) is required for signing flat-style products.)`

### Downloading and running scripts
InstallApplications can now handle downloading and running scripts. Please see below for how to specify the json structure.
InstallApplications can handle downloading and running scripts. Please see below for how to specify the json structure.

For user scripts, you **must** set the folder path to the `userscripts` sub folder. This is due to the folder having world-wide permissions, allowing the LaunchAgent/User to delete the scripts when finished.

Expand Down Expand Up @@ -200,63 +200,9 @@ If your webserver needs to redirect InstallApplictions to fetch content from ano
```

### DEPNotify
InstallApplications can work in conjunction with DEPNotify to automatically create and manipulate the progress bar.
As of InstallApplications v2.0.2, the built in support for DEPNotify has been removed.

InstallApplications will do the following automatically:
- Determine the progress bar based on the amount of packages in the json (excluding setupassistant)

#### Notes about argument behavior
If you would like to pass more options to DEPNotify, simply pass string arguments exactly as they would be passed to DEPNotify. The `--depnotify` option can be passed an *unlimited* amount of arguments.

```
installapplications.py --depnotify "Command: WindowTitle: InstallApplications is Awesome!" "Command: Quit: Thanks for using InstallApplications and DEPNotify!"
```

If you pass arguments for `Quit` or `Restart`, InstallApplications will ignore these commands until the end of the run.

#### Opening DEPNotify with InstallApplications
If you would like to open DEPNotify, simply pass the `DEPNotifyPath:` argument to the `--depnotify` option.

```
installapplications.py --depnotify "DEPNotifyPath: /path/to/DEPNotify.app"
```

If you need additional arguments to pass to DEPNotify, add `DEPNotifyArguments:` to the `--depnotify` option.

```
installapplications.py --depnotify "DEPNotifyPath: /path/to/DEPNotify.app" "DEPNotifyArguments: -munki"
```

InstallApplications will wait until `userland` to open DEPNotify as the `setupassistant` is used for SetupAssistant.

You can also pass unlimited arguments to DEPNotify.

```
installapplications.py --depnotify "DEPNotifyPath: /path/to/DEPNotify.app" "DEPNotifyArguments: -munki -fullScreen"
```

**By default** InstallApplications will create a `determinate` and show a status for each item in your stages. If you would like to skip this behavior, pass `DEPNotifySkipStatus` to the `--depnotify` options
```
installapplications.py --depnotify "DEPNotifySkipStatus"`
```

#### DEPNotify LaunchDaemon
You can pass unlimited options to DEPNotify that will allow you to set it's various options.

```xml
<string>--depnotify</string>
<string>DEPNotifySkipStatus</string>
<string>Command: WindowTitle: InstallApplications is Awesome!</string>
<string>Command: NotificationOn:</string>
<string>Command: Quit: Thanks for using InstallApplications and DEPNotify!</string>
<string>Command: WindowStyle: ActivateOnStep</string>
<string>DEPNotifyPath: /Applications/Utilities/DEPNotify.app</string>
<string>DEPNotifyArguments: -munki</string>
```

For a list of all DEPNotify options, please go [here](https://gitlab.com/Mactroll/DEPNotify).

Please note that `DEPNotifyPath` and `DEPNotifyArguments` are custom options for this tool only and are not available in DEPNotify.
Big Sur makes this code less stable. If you would like an example on how to launch DEPNotify with a user script, please see [depnotify_user_launcher.py](https://github.com/erikng/installapplicationsdemo/blob/master/installapplications/scripts/user/depnotify_user_launcher.py) at the installapplications demo GitHub.

### Logging
All root actions are logged at `/private/var/log/installapplications.log` as well as through NSLog. You can open up Console.app and search for `InstallApplications` to bring up all of the events.
Expand Down
15 changes: 5 additions & 10 deletions payload/Library/LaunchDaemons/com.erikng.installapplications.plist
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,16 @@
<string>/Library/installapplications/installapplications.py</string>
<string>--jsonurl</string>
<string>https://domain.tld</string>
<!-- <string>--dry-run</string> -->
<!-- <string>--follow-redirects</string> -->
<!-- <string>--headers</string> -->
<!-- <<string>Basic ZXhhbXBsZV91c2VyOmV4YW1wbGVfcGFzc3dvcmQ=</string> -->
<!-- <string>--iapath</string> -->
<!-- <string>/Library/Application Support/installapplications</string> -->
<!-- <string>/Library/installapplications/installapplications</string> -->
<!-- <string>--laidentifier</string> -->
<!-- <string>com.erikng.installapplications</string> -->
<!-- <string>--ldidentifier</string> -->
<!-- <string>com.erikng.installapplications</string> -->
<!-- <string>--depnotify</string> -->
<!-- <string>DEPNotifySkipStatus</string> -->
<!-- <string>Command: WindowTitle: InstallApplications is Awesome!</string> -->
<!-- <string>Command: NotificationOn:</string> -->
<!-- <string>Command: Quit: Thanks for using InstallApplications and DEPNotify.</string> -->
<!-- <string>Command: WindowStyle: ActivateOnStep</string> -->
<!-- <string>DEPNotifyPath: /Applications/Utilities/DEPNotify.app</string> -->
<!-- <string>DEPNotifyArguments: -munki</string> -->
<!-- <string>DEPNotifyArguments: -munki -fullScreen</string> -->
<!-- <string>--reboot</string> -->
<!-- <string>--skip-validation</string> -->
</array>
Expand Down
182 changes: 21 additions & 161 deletions payload/Library/installapplications/installapplications.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/Library/installapplications/Python.framework/Versions/Current/bin/python3
# encoding: utf-8
#
# Copyright 2009-Present Erik Gomez.
# Copyright 2017-Present Erik Gomez.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -47,12 +47,6 @@
g_dry_run = False


def deplog(text):
depnotify = '/private/var/tmp/depnotify.log'
with open(depnotify, 'a+') as log:
log.write(text + '\n')


def iaslog(text):
try:
NSLog('[InstallApplications] %s' % text)
Expand Down Expand Up @@ -275,7 +269,7 @@ def runuserscript(iauserscriptpath):
return False


def download_if_needed(item, stage, type, opts, depnotifystatus):
def download_if_needed(item, stage, type, opts):
# Check if the file exists and matches the expected hash.
path = item['file']
name = item['name']
Expand All @@ -292,13 +286,6 @@ def download_if_needed(item, stage, type, opts, depnotifystatus):
item.update({'follow_redirects': True})
# Download the file once:
iaslog('Starting download: %s' % urllib.parse.unquote(itemurl))
if opts.depnotify:
if stage == 'setupassistant':
iaslog('Skipping DEPNotify notification due to setupassistant.'
)
else:
if depnotifystatus:
deplog('Status: Downloading %s' % (name))
downloadfile(item)
# Wait half a second to process
time.sleep(0.5)
Expand Down Expand Up @@ -388,35 +375,31 @@ def main():
# Options
usage = '%prog [options]'
o = optparse.OptionParser(usage=usage)
o.add_option('--depnotify', default=None,
dest="depnotify",
action="callback",
callback=vararg_callback,
help=('Optional: Utilize DEPNotify and pass options to it.'))
o.add_option('--headers', help=('Optional: Auth headers'))
o.add_option('--jsonurl', help=('Required: URL to json file.'))
o.add_option('--iapath',
default='/Library/installapplications',
o.add_option('--jsonurl', default=None,
help=('Required: URL to json file.'))
o.add_option('--dry-run', default=False,
help=('Optional: Dry run (for testing).'),
action='store_true')
o.add_option('--follow-redirects', default=False,
help=('Optional: Follow HTTP redirects.'),
action='store_true')
o.add_option('--headers', default=None,
help=('Optional: Auth headers'))
o.add_option('--iapath', default='/Library/installapplications',
help=('Optional: Specify InstallApplications package path.'))
o.add_option('--ldidentifier',
default='com.erikng.installapplications',
help=('Optional: Specify LaunchDaemon identifier.'))
o.add_option('--laidentifier',
default='com.erikng.installapplications',
o.add_option('--laidentifier', default='com.erikng.installapplications',
help=('Optional: Specify LaunchAgent identifier.'))
o.add_option('--ldidentifier', default='com.erikng.installapplications',
help=('Optional: Specify LaunchDaemon identifier.'))
o.add_option('--reboot', default=False,
help=('Optional: Trigger a reboot.'), action='store_true')
o.add_option('--dry-run', help=('Optional: Dry run (for testing).'),
help=('Optional: Trigger a reboot.'),
action='store_true')
o.add_option('--skip-validation', default=False,
help=('Optional: Skip bootstrap.json validation.'),
action='store_true')
o.add_option('--userscript', default=None,
help=('Optional: Trigger a user script run.'),
action='store_true')
o.add_option('--follow-redirects', default=False,
help=('Optional: Follow HTTP redirects.'),
action='store_true')

opts, args = o.parse_args()

Expand Down Expand Up @@ -460,7 +443,7 @@ def main():
global ialapath
ialapath = os.path.join('/Library/LaunchAgents', laidentifierplist)
iaslog('InstallApplications LaunchAgent path: %s' % ialapath)
depnotifystatus = True

global userid
userid = str(getconsoleuser()[1])
global reboot
Expand Down Expand Up @@ -495,23 +478,6 @@ def main():
os.makedirs(path)
os.chmod(path, 0o777)

# DEPNotify trigger commands that need to happen at the end of a run
deptriggers = ['Command: Quit', 'Command: Restart', 'Command: Logout',
'DEPNotifyPath', 'DEPNotifyArguments',
'DEPNotifySkipStatus']

# Look for all the DEPNotify options but skip the ones that are usually
# done after a full run.
if opts.depnotify:
for varg in opts.depnotify:
notification = str(varg)
if any(x in notification for x in deptriggers):
if 'DEPNotifySkipStatus' in notification:
depnotifystatus = False
else:
iaslog('Sending %s to DEPNotify' % notification)
deplog(notification)

# Make the temporary folder
try:
os.makedirs(iapath)
Expand Down Expand Up @@ -553,22 +519,6 @@ def main():
# Set the stages
stages = ['preflight', 'setupassistant', 'userland']

# Get the number of items for DEPNotify
if opts.depnotify:
numberofitems = 0
for stage in stages:
if stage == 'setupassistant':
iaslog('Skipping DEPNotify item count due to setupassistant.')
else:
# catch if there is a missing stage. mostly for preflight.
try:
numberofitems += int(len(iajson[stage]))
except KeyError:
iaslog('Malformed JSON - missing %s stage key' % stage)
# Mulitply by two for download and installation status messages
if depnotifystatus:
deplog('Command: Determinate: %d' % (numberofitems*2))

# Process all stages
for stage in stages:
iaslog('Beginning %s' % stage)
Expand All @@ -579,68 +529,6 @@ def main():
except KeyError:
iaslog('No preflight stage found: skipping.')
continue
if stage == 'userland':
# Open DEPNotify for the admin if they pass
# condition.
depnotifypath = None
depnotifyarguments = None
if opts.depnotify:
for varg in opts.depnotify:
depnstr = str(varg)
if 'DEPNotifyPath:' in depnstr:
depnotifypath = depnstr.split(' ', 1)[-1]
if 'DEPNotifyArguments:' in depnstr:
depnotifyarguments = depnstr.split(' ', 1)[-1]
if depnotifypath:
while (getconsoleuser()[0] is None
or getconsoleuser()[0] == 'loginwindow'
or getconsoleuser()[0] == '_mbsetupuser'):
iaslog('Detected SetupAssistant in userland stage - '
'delaying DEPNotify launch until user session.')
time.sleep(1)
iaslog('Creating DEPNotify Launcher')
depnotifyscriptpath = os.path.join(
iauserscriptpath,
'depnotifylauncher.py')
if depnotifyarguments:
if '-munki' in depnotifyarguments:
# Touch Munki Logs if they do not exist so DEPNotify
# can show them.
mlogpath = '/Library/Managed Installs/Logs'
mlogfile = os.path.join(mlogpath,
'ManagedSoftwareUpdate.log')
if not os.path.isdir(mlogpath):
os.makedirs(mlogpath, 0o755)
if not os.path.isfile(mlogfile):
touch(mlogfile)
if len(depnotifyarguments) >= 2:
totalarguments = []
splitarguments = depnotifyarguments.split(' ')
for x in splitarguments:
totalarguments.append(x)
depnotifystring = 'depnotifycmd = ' \
"""['/usr/bin/open', '""" + depnotifypath + "', '"\
+ '--args' + "', '" + \
"""', '""".join(map(str, totalarguments)) + "']"
else:
depnotifystring = 'depnotifycmd = ' \
"""['/usr/bin/open', '""" + depnotifypath + "', '"\
+ '--args' + """', '""" + depnotifyarguments + "']"
else:
depnotifystring = 'depnotifycmd = ' \
"""['/usr/bin/open', '""" + depnotifypath + "']"
iaslog('Launching DEPNotify with: %s' % depnotifystring)
depnotifyscript = "#!/Library/installapplications/Python.framework/Versions/Current/bin/python3"
depnotifyscript += '\n' + "import subprocess"
depnotifyscript += '\n' + depnotifystring
depnotifyscript += '\n' + 'subprocess.call(depnotifycmd)'
with open(depnotifyscriptpath, 'w') as f:
f.write(depnotifyscript)
os.chmod(depnotifyscriptpath, 0o777)
touch(userscripttouchpath)
while os.path.isfile(userscripttouchpath):
iaslog('Waiting for DEPNotify script to complete')
time.sleep(0.5)
# Loop through the items and download/install/run them.
for item in iajson[stage]:
# Set the filepath, name and type.
Expand Down Expand Up @@ -679,32 +567,19 @@ def main():
iaslog('Skipping %s - already installed.' % name)
else:
# Download the package if it isn't already on disk.
download_if_needed(item, stage, type, opts,
depnotifystatus)
download_if_needed(item, stage, type, opts)

iaslog('Installing %s from %s' % (name, path))
if opts.depnotify:
if stage == 'setupassistant':
iaslog(
'Skipping DEPNotify notification due to '
'setupassistant.')
else:
if depnotifystatus:
deplog('Status: Installing: %s' % (name))
# Install the package
installpackage(item['file'])
elif type == 'rootscript':
if 'url' in item:
download_if_needed(item, stage, type, opts,
depnotifystatus)
download_if_needed(item, stage, type, opts)
iaslog('Starting root script: %s' % path)
try:
donotwait = item['donotwait']
except KeyError as e:
donotwait = False
if opts.depnotify:
if depnotifystatus:
deplog('Status: Installing: %s' % (name))
if stage == 'preflight':
preflightrun = runrootscript(path, donotwait)
if preflightrun:
Expand All @@ -719,8 +594,7 @@ def main():
runrootscript(path, donotwait)
elif type == 'userscript':
if 'url' in item:
download_if_needed(item, stage, type, opts,
depnotifystatus)
download_if_needed(item, stage, type, opts)
if stage == 'setupassistant':
iaslog('Detected setupassistant and user script. '
'User scripts cannot work in setupassistant stage! '
Expand All @@ -729,24 +603,10 @@ def main():
continue
iaslog('Triggering LaunchAgent for user script: %s' % path)
touch(userscripttouchpath)
if opts.depnotify:
if depnotifystatus:
deplog('Status: Installing: %s' % name)
while os.path.isfile(userscripttouchpath):
iaslog('Waiting for user script to complete: %s' % path)
time.sleep(0.5)

# Trigger the final DEPNotify events
if opts.depnotify:
for varg in opts.depnotify:
notification = str(varg)
if any(x in notification for x in deptriggers):
iaslog('Sending %s to DEPNotify' % notification)
deplog(notification)
else:
iaslog(
'Skipping DEPNotify notification event due to completion.')

# Cleanup and send good exit status
cleanup(0)

Expand Down
Loading

0 comments on commit a783791

Please sign in to comment.