- This topic has 11 replies, 4 voices, and was last updated 13 years, 6 months ago by
Chris George.
-
AuthorPosts
-
May 5, 2011 at 12:04 am #380695
Ben Urban
ParticipantI’ve been told I would instantly become very popular if I posted this script here. I wish to put that theory to the test. Enjoy.
This script should be able to handle installing to any volume (and it will figure out and fill the users already present on the target volume, if applicable). It can also compensate for unusual home directory locations (/Volumes/Macintosh HD/var/root comes to mind, where /Volumes/Macintosh HD/var points to /private/var; it can handle this situation correctly, though it skips the root user because its UID is below 500).
[code]
#!/usr/bin/python# Written by Ben Urban
“””
This script clones anything it finds in /private/tmp/fillUsers to all existing
non-hidden users, as well as all user template localizations. It is meant to be
used as a postflight script for packages that need this functionality, mimicking
the functionality available in JAMF’s Casper Suite for DMG packages.It requires Python 2.5 or higher (thus, Mac OS X 10.5 or later).
“””import os, subprocess, sys
fsroot = sys.argv[3] if len(sys.argv) > 3 else ‘/’
src = os.path.join(fsroot, ‘private/tmp/fillUsers’)defaultNode = os.path.join(fsroot, ‘private/var/db/dslocal/nodes/Default’)
def dscl(cmd, path, *args):
# Not sure why -f can’t be used directly with any commands…
path = os.path.join(‘/Local/Target/’, path.lstrip(os.path.sep))
cmd = ‘ ‘.join((cmd, path) + args)
result = subprocess.Popen(
[‘/usr/bin/dscl’, ‘-q’, ‘-f’, defaultNode],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
).communicate(cmd)[0]
result = result.rstrip(‘\n’).split(‘\n’)
del result[0] # ‘Entering interactive mode… (Type “help” for commands)’
return resultdef dsclread(path, prop):
result = dscl(‘read’, path, prop)
result = ”.join(result).split(‘%s: ‘ % (prop, ), 1)[1]
return resultdef makeAbsPath(path):
path = os.path.normpath(path).lstrip(os.path.sep).split(os.path.sep)
newpath = []
for part in path:
subpath = os.path.join(*([fsroot] + newpath + [part]))
if os.path.islink(subpath):
link = os.readlink(subpath)
if link.startswith(os.path.sep):
newpath = [os.path.normpath(link.lstrip(os.path.sep))]
else:
newpath.append(os.path.normpath(link))
else:
newpath.append(part)
return os.path.normpath(os.path.join(fsroot, *newpath))users = []
for line in dscl(‘ls’, ‘/Users’, ‘UniqueID’):
user, uid = line.split(‘ ‘, 1)[0], line.rsplit(‘ ‘, 1)[1]
if int(uid) > 500:
gid = dsclread(os.path.join(‘/Users’, user), ‘PrimaryGroupID’)
name = dsclread(os.path.join(‘/Users’, user), ‘RealName’)
home = dsclread(os.path.join(‘/Users’, user), ‘NFSHomeDirectory’)
users.append((uid, gid, name, home))for lang in os.listdir(os.path.join(fsroot, ‘System/Library/User Template’)):
users.append((
‘0’,
‘0’,
‘template (%s)’ % (lang, ), # This is cheating, but it works
os.path.join(‘/System/Library/User Template/’, lang),
))for uid, gid, name, home in users:
home = makeAbsPath(home)
print “Filling %s for user %s” % (home, name)
subprocess.check_call([‘/usr/sbin/chown’, ‘-R’, uid + ‘:’ + gid, src])
subprocess.check_call([‘/usr/bin/ditto’, src, home])print “Cleaning up %s” % (src, )
subprocess.check_call([‘/bin/rm’, ‘-rf’, src])
[/code]May 5, 2011 at 9:59 pm #380699dead2sin
ParticipantVery Nice! Its always a pain to put stuff into existing user’s folders, but that looks like it’ll do the trick 🙂
Nate
May 20, 2011 at 7:23 pm #380746Chris George
ParticipantSo, if I understand this correctly… say you want something to go into all users /LIbrary/Application Support/blah folder – you create /private/tmp/fillUsers/Library/Application Support/blah/, put everything into that folder, then run this as a postflight?
May 20, 2011 at 7:58 pm #380747Ben Urban
ParticipantYup!
I was working on a new version of this that can fill user home folders the next time they log in (using a LaunchAgent), but I haven’t had time to work on it recently. (It’s actually a lot more complicated than you would think to do this – you’ll see why when you see the new version of the script.)
May 20, 2011 at 8:04 pm #380748Rusty Myers
ParticipantVery nice! Thanks for sharing. Do you have your scripts in a version control system we can follow? Githib? Thanks!
May 20, 2011 at 8:30 pm #380749Ben Urban
ParticipantI’m afraid I don’t. I should start using one sometime soon…
May 20, 2011 at 9:28 pm #380750Ben Urban
ParticipantI took another look at the script, and I think I got it working, but I am not in a position to reasonably test it. Anyone care to try?
Note that currently all it does is output what it would do; it doesn’t actually do anything. You’ll have to edit out the extra return statements to make it actually do stuff.
I also went nuts with refactoring, partly to allow me to nullify the actual effects in favor of print statements.
[code]
#!/usr/bin/python# Written by Ben Urban
“””
This script clones anything it finds in /tmp/fillUsers to all existing
non-hidden users, as well as all user template localizations. It also sets up a
LaunchAgent to automatically extract the data for all users that don’t already
have it. It is meant to be used as a postflight script for packages that need
this functionality, mimicking and surpassing the functionality available in
JAMF’s Casper Suite for DMG packages.It requires Python 2.5 or higher (thus, Mac OS X 10.5 or later).
“””import os, subprocess
srcDir = ‘/tmp/fillUsers’
defaultNode = ‘/var/db/dslocal/nodes/Default’
userTemplateRoot = ‘/System/Library/User Template’
autofillDir = ‘/Library/Autofill’
dataDir = os.path.join(autofillDir, ‘Data’)
launchAgentsDir = ‘/Library/LaunchAgents’
autofillReceiptsDir = ‘Library/Autofill/Receipts’
autofiller_sh = ”’#!/bin/shfor f in “%(dataDir)s”/*
do
if ! [ -f “${HOME}/%(autofillReceiptsDir)s/${f%%%%.*}” ]
then
/usr/bin/tar -xpf “${f}” -C “${HOME}” –strip-components 1
fi
done
”’
autofiller_plist = ”’
”’
Label
autofiller
ProgramArguments
%(scriptPath)s
RunAtLoad
def writeFile(path, contents):
prepDirTree(os.path.dirname(path))
print ‘%s:’ % dest(path)
print ‘\t’ + contents.replace(‘\n’, ‘\n\t’)
return
f = open(dest(path), ‘w’)
f.write(contents)
f.close()def prepDirTree(path, mode = 0777):
print ‘os.mkdirs(%s, 0%o)’ % (dest(path), mode)
return
os.makedirs(dest(path), mode)def tarCreate(archive, srcFolder):
print [
‘/usr/bin/tar’, ‘-cj’,
‘-f’, dest(archive),
‘-C’, dest(os.path.dirname(srcFolder)),
os.path.basename(srcFolder),
]
return
subprocess.check_call([
‘/usr/bin/tar’, ‘-cj’,
‘-f’, dest(archive),
‘-C’, dest(os.path.dirname(srcFolder)),
os.path.basename(srcFolder),
])def tarExtract(archive, dstFolder, uid, gid):
print [
‘/usr/bin/tar’, ‘-x’,
‘-f’, dest(archive),
‘-C’, dest(dstFolder),
‘–strip-components’, ‘1’,
], (uid, gid)
return
subprocess.check_call(
[
‘/usr/bin/tar’, ‘-x’,
‘-f’, dest(archive),
‘-C’, dest(dstFolder),
‘–strip-components’, ‘1’,
],
preexec_fn = lambda: (os.setuid(uid), os.setgid(gid)),
)def chmod(item, mode):
print ‘chmod(%s, 0%o)’ % (dest(item), mode)
return
os.chmod(dest(item), mode)def chown_R(item, uid, gid):
print [‘/usr/sbin/chown’, ‘-R’, ‘%s:%s’ % (uid, gid), dest(item)]
return
subprocess.check_call([‘/usr/sbin/chown’, ‘-R’, ‘%s:%s’ % (uid, gid), dest(item)])def rm_rf(item):
print [‘/bin/rm’, ‘-rf’, dest(item)]
return
subprocess.check_call([‘/bin/rm’, ‘-rf’, dest(item)])def dest(path):
“””
Convert an absolute path into an absolute path within the mountpoint
specified by destroot. Symlinks in the path are followed properly.If you pass a recursive link to this function, enjoy your infinite loop!
“””
path = os.path.normpath(path)
if path.startswith(destroot):
path = path.split(destroot, 1)[1]
path = path.lstrip(os.path.sep).split(os.path.sep)
while True:
newpath = []
noLinks = True
for part in path:
subpath = os.path.join(*([destroot] + newpath + [part]))
if os.path.islink(subpath):
noLinks = False
link = os.readlink(subpath)
if link.startswith(os.path.sep):
newpath = [os.path.normpath(link.lstrip(os.path.sep))]
else:
newpath.append(os.path.normpath(link))
else:
newpath.append(part)
if noLinks:
break
path = os.path.join(*newpath).lstrip(os.path.sep).split(os.path.sep)
return os.path.normpath(os.path.join(destroot, *newpath))def dscl(cmd, path, *args):
# Not sure why -f can’t be used directly with any commands…
path = os.path.join(‘/Local/Target/’, path.lstrip(os.path.sep))
cmd = ‘ ‘.join((cmd, path) + args)
result = subprocess.Popen(
[‘/usr/bin/dscl’, ‘-q’, ‘-f’, defaultNode],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
).communicate(cmd)[0]
result = result.rstrip(‘\n’).split(‘\n’)
del result[0] # ‘Entering interactive mode… (Type “help” for commands)’
return resultdef dsclread(path, prop):
result = dscl(‘read’, path, prop)
result = ”.join(result).split(‘%s: ‘ % (prop, ), 1)[1]
return resultdef collectReachableHomeDirs(uidFilter = lambda uid: uid > 500):
for line in dscl(‘ls’, ‘/Users’, ‘UniqueID’):
user, uid = line.split(‘ ‘, 1)[0], int(line.rsplit(‘ ‘, 1)[1])
if uidFilter(uid):
gid = int(dsclread(os.path.join(‘/Users’, user), ‘PrimaryGroupID’))
name = dsclread(os.path.join(‘/Users’, user), ‘RealName’)
home = dest(dsclread(os.path.join(‘/Users’, user), ‘NFSHomeDirectory’))
if os.path.exists(home):
yield (int(uid), int(gid), name, home)def collectUserTemplateDirs(languages = None): # None actually means all
for lang in os.listdir(dest(userTemplateRoot)):
if languages is None or lang in languages:
yield (
0,
0,
‘template (%s)’ % (lang, ), # This is cheating, but it works
os.path.join(userTemplateRoot, lang),
)def setupAutofiller(launchAgentsDir, autofillDir, autofillReceiptsDir, dataDir):
print “Setting up LaunchAgent for deferred fill”
scriptPath = os.path.join(autofillDir, ‘autofiller.sh’)
writeFile(scriptPath, autofiller_sh % locals())
chmod(scriptPath, 0755)
writeFile(os.path.join(launchAgentsDir, ‘autofiller.plist’), autofiller_plist % locals())def makeArchive(srcDir, archive):
print “Archiving autofill data”
chown_R(srcDir, 0, 0)
prepDirTree(dataDir)
tarCreate(archive, srcDir)
return archivedef cleanUp(srcDir):
print “Cleaning up %s” % (dest(srcDir), )
rm_rf(srcDir)def setupDeferredFill(srcDir, pkgid):
setupAutofiller(launchAgentsDir, autofillDir, autofillReceiptsDir, dataDir)
writeFile(os.path.join(srcDir, autofillReceiptsDir, pkgid), ”) # Create empty file
archive = makeArchive(srcDir, os.path.join(dataDir, pkgid + ‘.tar.bz2’))
cleanUp(srcDir)
return archivedef fillDirs(src, dirs):
for uid, gid, name, home in dirs:
print “Filling %s for user %s” % (dest(home), name)
tarExtract(src, home, uid, gid)def getPkgID():
import time, uuid
return time.strftime(‘%Y-%m-%d %H:%M:%S ‘) + str(uuid.uuid1())def main(dest):
global destroot
destroot = destpkgid = getPkgID()
src = setupDeferredFill(srcDir, pkgid)dirs = []
dirs += collectReachableHomeDirs()
dirs += collectUserTemplateDirs()fillDirs(src, dirs)
if __name__ == ‘__main__’:
import sys
main(sys.argv[3] if len(sys.argv) > 3 else ‘/’)
[/code]May 21, 2011 at 5:08 am #380751Ben Urban
ParticipantHere’s a version you can use to test it:
[code]
#!/usr/bin/python# Written by Ben Urban
from __future__ import with_statement # in case we’re in Python 2.5
“””
This script clones anything it finds in /tmp/fillUsers to all existing
non-hidden users, as well as all user template localizations. It also sets up a
LaunchAgent to automatically extract the data for all users that don’t already
have it. It is meant to be used as a postflight script for packages that need
this functionality, mimicking and surpassing the functionality available in
JAMF’s Casper Suite for DMG packages.It requires Python 2.5 or higher (thus, Mac OS X 10.5 or later).
“””import os, subprocess
srcDir = ‘/tmp/fillUsers’
defaultNode = ‘/var/db/dslocal/nodes/Default’
userTemplateRoot = ‘/System/Library/User Template’
autofillDir = ‘/Library/Autofill’
dataDir = os.path.join(autofillDir, ‘Data’)
launchAgentsDir = ‘/Library/LaunchAgents’
autofillReceiptsDir = ‘Library/Autofill/Receipts’
autofiller_sh = ”’#!/bin/shfor f in “%(dataDir)s”/*
do
if ! [ -f “${HOME}/%(autofillReceiptsDir)s/${f%%%%.*}” ]
then
/usr/bin/tar -xpf “${f}” -C “${HOME}” –strip-components 1
fi
done
”’
autofiller_plist = ”’
”’
Label
autofiller
ProgramArguments
%(scriptPath)s
RunAtLoad
def writeFile(path, contents):
path = dest(path)
os.makedirs(os.path.dirname(path))
with open(path, ‘w’) as f:
f.write(contents)def tarCreate(archive, srcFolder):
os.makedirs(dest(os.path.dirname(archive)))
subprocess.check_call([
‘/usr/bin/tar’, ‘-cj’,
‘-f’, dest(archive),
‘-C’, dest(os.path.dirname(srcFolder)),
os.path.basename(srcFolder),
])def tarExtract(archive, dstFolder, uid, gid):
subprocess.check_call(
[
‘/usr/bin/tar’, ‘-x’,
‘-f’, dest(archive),
‘-C’, dest(dstFolder),
‘–strip-components’, ‘1’,
],
preexec_fn = lambda: (os.setuid(uid), os.setgid(gid)),
)def dest(path):
“””
Convert an absolute path into an absolute path within the mountpoint
specified by destroot. Symlinks in the path are followed properly.If you pass a recursive link to this function, enjoy your infinite loop!
“””
path = os.path.normpath(path)
if path.startswith(destroot):
path = path.split(destroot, 1)[1]
path = path.lstrip(os.path.sep).split(os.path.sep)
while True:
newpath = []
noLinks = True
for part in path:
subpath = os.path.join(*([destroot] + newpath + [part]))
if os.path.islink(subpath):
noLinks = False
link = os.readlink(subpath)
if link.startswith(os.path.sep):
newpath = [os.path.normpath(link.lstrip(os.path.sep))]
else:
newpath.append(os.path.normpath(link))
else:
newpath.append(part)
if noLinks:
break
path = os.path.join(*newpath).lstrip(os.path.sep).split(os.path.sep)
return os.path.normpath(os.path.join(destroot, *newpath))def dscl(cmd, path, *args):
# Not sure why -f can’t be used directly with any commands…
path = os.path.join(‘/Local/Target/’, path.lstrip(os.path.sep))
cmd = ‘ ‘.join((cmd, path) + args)
result = subprocess.Popen(
[‘/usr/bin/dscl’, ‘-q’, ‘-f’, defaultNode],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
).communicate(cmd)[0]
result = result.rstrip(‘\n’).split(‘\n’)
del result[0] # ‘Entering interactive mode… (Type “help” for commands)’
return resultdef dsclread(path, prop):
result = dscl(‘read’, path, prop)
result = ”.join(result).split(‘%s: ‘ % (prop, ), 1)[1]
return resultdef collectReachableHomeDirs(uidFilter = lambda uid: uid > 500):
for line in dscl(‘ls’, ‘/Users’, ‘UniqueID’):
user, uid = line.split(‘ ‘, 1)[0], int(line.rsplit(‘ ‘, 1)[1])
if uidFilter(uid):
gid = int(dsclread(os.path.join(‘/Users’, user), ‘PrimaryGroupID’))
name = dsclread(os.path.join(‘/Users’, user), ‘RealName’)
home = dest(dsclread(os.path.join(‘/Users’, user), ‘NFSHomeDirectory’))
if os.path.exists(home):
yield (uid, gid, name, home)def collectUserTemplateDirs(languages = None): # None actually means all
for lang in os.listdir(dest(userTemplateRoot)):
if languages is None or lang in languages:
yield (
0,
0,
‘template (%s)’ % (lang, ), # This is cheating, but it works
dest(os.path.join(userTemplateRoot, lang)),
)def setupAutofiller(**kwargs):
print “Setting up LaunchAgent for deferred fill”
scriptPath = os.path.join(kwargs[‘autofillDir’], ‘autofiller.sh’)
writeFile(kwargs[‘scriptPath’], autofiller_sh % kwargs)
os.chmod(dest(kwargs[‘scriptPath’]), 0755)
writeFile(os.path.join(kwargs[‘launchAgentsDir’], ‘autofiller.plist’), autofiller_plist % kwargs)def makeArchive(srcDir, archive):
print “Archiving autofill data”
subprocess.check_call([‘/usr/sbin/chown’, ‘-R’, ‘0:0’, dest(srcDir)])
tarCreate(archive, srcDir)
return archivedef cleanUp(srcDir):
srcDir = dest(srcDir)
print “Cleaning up %s” % (srcDir, )
subprocess.check_call([‘/bin/rm’, ‘-rf’, srcDir])def setupDeferredFill(srcDir, pkgid):
setupAutofiller(
launchAgentsDir = launchAgentsDir,
autofillDir = autoFillDir,
autofillReceiptsDir = autofillReceiptsDir,
dataDir = dataDir,
)
writeFile(os.path.join(srcDir, autofillReceiptsDir, pkgid), ”) # Create empty file
archive = makeArchive(srcDir, os.path.join(dataDir, pkgid + ‘.tar.bz2’))
cleanUp(srcDir)
return archivedef fillDirs(src, dirs):
for uid, gid, name, home in dirs:
print “Filling %s for user %s” % (dest(home), name)
tarExtract(src, home, uid, gid)def getPkgID():
import time, uuid
return time.strftime(‘%Y-%m-%d %H:%M:%S ‘) + str(uuid.uuid1())def main(_destroot):
global destroot
destroot = _destrootpkgid = getPkgID()
src = setupDeferredFill(srcDir, pkgid)dirs = []
dirs += collectReachableHomeDirs()
dirs += collectUserTemplateDirs()fillDirs(src, dirs)
if __name__ == ‘__main__’:
import sys
main(sys.argv[3] if len(sys.argv) > 3 else ‘/’)
[/code]May 21, 2011 at 11:38 pm #380752Chris George
ParticipantMaybe I’m not following, but I don’t get the point of this. If the files get put into all the existing home directories and the user template localizations – what does the launchagent do?
May 23, 2011 at 11:23 am #380761Ben Urban
ParticipantThe LaunchAgent takes care of those home directories that are not reachable when the package is installed. This includes home directories protected by FileVault, as well as network home directories. (Thanks to Greg Neagle for the suggestion, by the way.)
I expect the LaunchAgent’s impact once the template is already installed (by the script or the LaunchAgent itself) should be negligible. Unfortunately, I can’t test this easily, and I’m not that familiar with writing a LaunchAgent by hand (I copied one that seemed to run with similar frequency). Feedback on that part would be appreciated.
I’m also not sure this is the best way to generate the package IDs, or to store them (empty files are okay, but there must be some useful information that can go in there…). Any thoughts?
May 25, 2011 at 2:53 am #380779Ben Urban
ParticipantHmm, this forum doesn’t allow edits?
This version fixes a bug that would cause the previous version of the script to consistently crash when it is run. I also renamed autofiller.plist to com.osxdeployment.autofiller.plist (I’m told “Nate won’t mind”), and added a description.
[code]
#!/usr/bin/python# Written by Ben Urban
from __future__ import with_statement # in case we’re in Python 2.5
“””
This script clones anything it finds in /tmp/fillUsers to all existing
non-hidden users, as well as all user template localizations. It also sets up a
LaunchAgent to automatically extract the data for all users that don’t already
have it. It is meant to be used as a postflight script for packages that need
this functionality, mimicking and surpassing the functionality available in
JAMF’s Casper Suite for DMG packages.It requires Python 2.5 or higher (thus, Mac OS X 10.5 or later).
“””import os, subprocess
srcDir = ‘/tmp/fillUsers’
defaultNode = ‘/var/db/dslocal/nodes/Default’
userTemplateRoot = ‘/System/Library/User Template’
autofillDir = ‘/Library/Autofill’
dataDir = os.path.join(autofillDir, ‘Data’)
launchAgentsDir = ‘/Library/LaunchAgents’
autofillReceiptsDir = ‘Library/Autofill/Receipts’
autofiller_sh = ”’#!/bin/shfor f in “%(dataDir)s”/*
do
if ! [ -f “${HOME}/%(autofillReceiptsDir)s/${f%%%%.*}” ]
then
/usr/bin/tar -xpf “${f}” -C “${HOME}” –strip-components 1
fi
done
”’
autofiller_plist = ”’
”’
Label
com.osxdeployment.autofiller
ProgramArguments
%(scriptPath)s
RunAtLoad
ServiceDescription
Automatically fills the home folder at login, using the tarballs in from %(dataDir).
def writeFile(path, contents):
path = dest(path)
os.makedirs(os.path.dirname(path))
with open(path, ‘w’) as f:
f.write(contents)def tarCreate(archive, srcFolder):
os.makedirs(dest(os.path.dirname(archive)))
subprocess.check_call([
‘/usr/bin/tar’, ‘-cj’,
‘-f’, dest(archive),
‘-C’, dest(os.path.dirname(srcFolder)),
os.path.basename(srcFolder),
])def tarExtract(archive, dstFolder, uid, gid):
subprocess.check_call(
[
‘/usr/bin/tar’, ‘-x’,
‘-f’, dest(archive),
‘-C’, dest(dstFolder),
‘–strip-components’, ‘1’,
],
preexec_fn = lambda: (os.setuid(uid), os.setgid(gid)),
)def dest(path):
“””
Convert an absolute path into an absolute path within the mountpoint
specified by destroot. Symlinks in the path are followed properly.If you pass a recursive link to this function, enjoy your infinite loop!
“””
path = os.path.normpath(path)
if path.startswith(destroot):
path = path.split(destroot, 1)[1]
path = path.lstrip(os.path.sep).split(os.path.sep)
while True:
newpath = []
noLinks = True
for part in path:
subpath = os.path.join(*([destroot] + newpath + [part]))
if os.path.islink(subpath):
noLinks = False
link = os.readlink(subpath)
if link.startswith(os.path.sep):
newpath = [os.path.normpath(link.lstrip(os.path.sep))]
else:
newpath.append(os.path.normpath(link))
else:
newpath.append(part)
if noLinks:
break
path = os.path.join(*newpath).lstrip(os.path.sep).split(os.path.sep)
return os.path.normpath(os.path.join(destroot, *newpath))def dscl(cmd, path, *args):
# Not sure why -f can’t be used directly with any commands…
path = os.path.join(‘/Local/Target/’, path.lstrip(os.path.sep))
cmd = ‘ ‘.join((cmd, path) + args)
result = subprocess.Popen(
[‘/usr/bin/dscl’, ‘-q’, ‘-f’, defaultNode],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
).communicate(cmd)[0]
result = result.rstrip(‘\n’).split(‘\n’)
del result[0] # ‘Entering interactive mode… (Type “help” for commands)’
return resultdef dsclread(path, prop):
result = dscl(‘read’, path, prop)
result = ”.join(result).split(‘%s: ‘ % (prop, ), 1)[1]
return resultdef collectReachableHomeDirs(uidFilter = lambda uid: uid > 500):
for line in dscl(‘ls’, ‘/Users’, ‘UniqueID’):
user, uid = line.split(‘ ‘, 1)[0], int(line.rsplit(‘ ‘, 1)[1])
if uidFilter(uid):
gid = int(dsclread(os.path.join(‘/Users’, user), ‘PrimaryGroupID’))
name = dsclread(os.path.join(‘/Users’, user), ‘RealName’)
home = dest(dsclread(os.path.join(‘/Users’, user), ‘NFSHomeDirectory’))
if os.path.exists(home):
yield (uid, gid, name, home)def collectUserTemplateDirs(languages = None): # None actually means all
for lang in os.listdir(dest(userTemplateRoot)):
if languages is None or lang in languages:
yield (
0,
0,
‘template (%s)’ % (lang, ), # This is cheating, but it works
dest(os.path.join(userTemplateRoot, lang)),
)def setupAutofiller(**kwargs):
print “Setting up LaunchAgent for deferred fill”
kwargs[‘scriptPath’] = os.path.join(kwargs[‘autofillDir’], ‘autofiller.sh’)
writeFile(kwargs[‘scriptPath’], autofiller_sh % kwargs)
os.chmod(dest(kwargs[‘scriptPath’]), 0755)
writeFile(os.path.join(kwargs[‘launchAgentsDir’], ‘com.osxdeployment.autofiller.plist’), autofiller_plist % kwargs)def makeArchive(srcDir, archive):
print “Archiving autofill data”
subprocess.check_call([‘/usr/sbin/chown’, ‘-R’, ‘0:0’, dest(srcDir)])
tarCreate(archive, srcDir)
return archivedef cleanUp(srcDir):
srcDir = dest(srcDir)
print “Cleaning up %s” % (srcDir, )
subprocess.check_call([‘/bin/rm’, ‘-rf’, srcDir])def setupDeferredFill(srcDir, pkgid):
setupAutofiller(
launchAgentsDir = launchAgentsDir,
autofillDir = autoFillDir,
autofillReceiptsDir = autofillReceiptsDir,
dataDir = dataDir,
)
writeFile(os.path.join(srcDir, autofillReceiptsDir, pkgid), ”) # Create empty file
archive = makeArchive(srcDir, os.path.join(dataDir, pkgid + ‘.tar.bz2’))
cleanUp(srcDir)
return archivedef fillDirs(src, dirs):
for uid, gid, name, home in dirs:
print “Filling %s for user %s” % (dest(home), name)
tarExtract(src, home, uid, gid)def getPkgID():
import time, uuid
return time.strftime(‘%Y-%m-%d %H:%M:%S ‘) + str(uuid.uuid1())def main(_destroot):
global destroot
destroot = _destrootpkgid = getPkgID()
src = setupDeferredFill(srcDir, pkgid)dirs = []
dirs += collectReachableHomeDirs()
dirs += collectUserTemplateDirs()fillDirs(src, dirs)
if __name__ == ‘__main__’:
import sys
main(sys.argv[3] if len(sys.argv) > 3 else ‘/’)
[/code]September 26, 2011 at 1:42 pm #381228Chris George
ParticipantHmm. Old thread bump time!
I just realized that, unless I’m wrong, this script doesn’t account for network users, such as those from Active Directory/Open Directory, that have local home directories, as it is reading the list of users from dscl on the local machine and only applying the changes to those users. I couldn’t figure out why an installer I made using this postflight didn’t work in our labs, and I think now that’s because we don’t have any local users aside from the admin account.
Am I wrong?
-
AuthorPosts
- You must be logged in to reply to this topic.
Comments are closed