From 8d01e168c8214df0d7e94753864ece8a60055831 Mon Sep 17 00:00:00 2001 From: Gerd Date: Sun, 11 Mar 2018 21:59:05 +0100 Subject: [PATCH 1/3] Added JPEGPhoto and createhomedir --- createuserpkg | 47 +++++++++++++++++++++++--------- locallibs/imageutil.py | 62 ++++++++++++++++++++++++++++++++++++++++++ locallibs/userpkg.py | 9 ++++++ 3 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 locallibs/imageutil.py diff --git a/createuserpkg b/createuserpkg index 59ac2ec..76c8abd 100755 --- a/createuserpkg +++ b/createuserpkg @@ -27,7 +27,6 @@ from locallibs import shadowhash from locallibs import userplist from locallibs import userpkg - def main(): '''Main''' usage = "usage: %prog [options] /path/to/output.pkg" @@ -36,16 +35,20 @@ def main(): required_user_options = optparse.OptionGroup( parser, 'Required User Options') required_user_options.add_option( - '--name', '-n', help='User shortname. REQUIRED.') + '--name', '-n', + help='User shortname. REQUIRED.') required_user_options.add_option( - '--uid', '-u', help='User uid. REQUIRED.') + '--uid', '-u', + help='User uid. REQUIRED.') required_package_options = optparse.OptionGroup( parser, 'Required Package Options') required_package_options.add_option( - '--version', '-V', help='Package version number. REQUIRED.') + '--version', '-V', + help='Package version number. REQUIRED.') required_package_options.add_option( - '--identifier', '-i', help='Package identifier. REQUIRED.') + '--identifier', '-i', + help='Package identifier. REQUIRED.') optional_user_options = optparse.OptionGroup( parser, 'Optional User Options') @@ -54,22 +57,35 @@ def main(): help='User password. If this is not provided, interactively prompt for ' 'password.') optional_user_options.add_option( - '--fullname', '-f', help='User full name. Optional.') - optional_user_options.add_option('--gid', '-g', help='User gid. Optional.') + '--fullname', '-f', + help='User full name. Optional.') + optional_user_options.add_option( + '--gid', '-g', + help='User gid. Optional.') optional_user_options.add_option( - '--generateduid', '-G', help='GeneratedUID (UUID). Optional.') + '--generateduid', '-G', + help='GeneratedUID (UUID). Optional.') optional_user_options.add_option( - '--home', '-H', help='Path to user home directory. Optional.') + '--home', '-H', + help='Path to user home directory. Optional.') optional_user_options.add_option( - '--shell', '-s', help='User shell path. Optional.') + '--shell', '-s', + help='User shell path. Optional.') optional_user_options.add_option( '--admin', '-a', action='store_true', help='User account should be added to admin group.') optional_user_options.add_option( '--autologin', '-A', action='store_true', help='User account should automatically login.') - optional_user_options.add_option('--hidden', action='store_true', - help='User account should be hidden.') + optional_user_options.add_option( + '--hidden', action='store_true', + help='User account should be hidden.') + optional_user_options.add_option( + '--picture', '-P', + help='Change user picture.') + optional_user_options.add_option( + '--createhomedir', '-c', action='store_true', + help='Create and populate home directories on the local computer.') parser.add_option_group(required_user_options) parser.add_option_group(required_package_options) @@ -119,7 +135,10 @@ def main(): user_data['shell'] = options.shell if options.hidden: user_data['IsHidden'] = u'YES' - + if options.picture: + from locallibs import imageutil + user_data['image_data'] = imageutil.generate(options.picture) + user_data['image_path'] = options.picture user_plist = userplist.generate(user_data) @@ -132,6 +151,8 @@ def main(): pkg_data['kcpassword'] = kcpassword.generate(password) if options.admin: pkg_data['is_admin'] = True + if options.createhomedir: + pkg_data['createhomedir'] = True # build the package userpkg.generate(pkg_data) diff --git a/locallibs/imageutil.py b/locallibs/imageutil.py new file mode 100644 index 0000000..cc4bafa --- /dev/null +++ b/locallibs/imageutil.py @@ -0,0 +1,62 @@ +# Copyright 2018 Gerd Niemetz. +# +# 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 +# +# http://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. + +'''Generates a base64 encoded JPEG from an image file''' + +import imghdr +import base64 +import subprocess +import os +from Foundation import NSData + +class ImageUtilException(Exception): + '''Error when generating jpeg''' + pass + +def is_non_zero_file(fpath): + return os.path.isfile(fpath) and os.path.getsize(fpath) > 0 + +def generate(image_path): + try: + # check if image_path is an image + if imghdr.what(image_path) is None: + raise ImageUtilException(u'"{0}" is not a valid picture'.format(image_path)) + # build temporary image file + temp_image = os.path.join(os.path.sep, 'private/var/tmp', + os.path.splitext(os.path.basename(image_path))[0] + '.jpg') + # convert image_path to jpeg with 72 dpi and max. width/height 512 px + cmd = ('''/usr/bin/sips -s format jpeg "{0}" ''' + '''-s formatOptions high -s dpiHeight 72 -s dpiWidth 72 --resampleHeightWidthMax 512 ''' + '''--out "{1}"''').format( + image_path, + temp_image) + try: + subprocess.call(cmd, shell=True) + # check if temporary image exists and is not blank + if is_non_zero_file(temp_image): + with open(temp_image, 'rb') as image_file: + # convert temporary image to a base64 encoded string + return NSData.alloc().initWithBase64EncodedString_options_( + base64.b64encode(image_file.read()), + 0) + else: + raise ImageUtilException(u'Could not convert "{0}" to a jpeg file)'.format(image_path)) + finally: + if os.path.exists(temp_image): + os.unlink(temp_image) + except IOError as e: + raise ImageUtilException(u'Cannot open "{0}" (I/O error {1}: {2})'.format( + image_path, + e.errno, + e.strerror)) diff --git a/locallibs/userpkg.py b/locallibs/userpkg.py index 1787397..3fbc3ea 100644 --- a/locallibs/userpkg.py +++ b/locallibs/userpkg.py @@ -33,6 +33,7 @@ # we're operating on the boot volume _POSTINSTALL_LIVE_ACTIONS_ fi +_POSTINSTALL_CREATEHOMEDIR_ exit 0 """ @@ -88,6 +89,9 @@ /usr/bin/killall DirectoryService 2>/dev/null || /usr/bin/killall opendirectoryd 2>/dev/null """ +PI_CREATEHOMEDIR = """ +/usr/sbin/createhomedir -c +""" def make_postinstall_script(scripts_path, pkg_info): # Create postinstall script. @@ -97,12 +101,15 @@ def make_postinstall_script(scripts_path, pkg_info): pi_reqs = set() pi_actions = set() pi_live_actions = set() + pi_createhomedir = set() pi_live_actions.add(PI_LIVE_KILLDS) if pkg_info.get('is_admin'): pi_actions.add(PI_ADD_ADMIN_GROUPS) pi_reqs.add(PI_REQ_PLIST_FUNCS) if pkg_info.get('kcpassword'): pi_actions.add(PI_ENABLE_AUTOLOGIN) + if pkg_info.get('createhomedir'): + pi_createhomedir.add(PI_CREATEHOMEDIR) postinstall = POSTINSTALL_TEMPLATE postinstall = postinstall.replace( '_POSTINSTALL_REQUIREMENTS_', '\n'.join(pi_reqs)) @@ -110,6 +117,8 @@ def make_postinstall_script(scripts_path, pkg_info): '_POSTINSTALL_ACTIONS_', '\n'.join(pi_actions)) postinstall = postinstall.replace( '_POSTINSTALL_LIVE_ACTIONS_', '\n'.join(pi_live_actions)) + postinstall = postinstall.replace( + '_POSTINSTALL_CREATEHOMEDIR_', '\n'.join(pi_createhomedir)) postinstall = postinstall.replace( '_USERNAME_', user_plist[u'name'][0].encode('utf-8')) postinstall = postinstall.replace( From 8ad050cf99aa9ebb95b388bf80281b0b3fdd575b Mon Sep 17 00:00:00 2001 From: Gerd Date: Mon, 12 Mar 2018 09:20:10 +0100 Subject: [PATCH 2/3] Adjusted according Greg Neagles comments - Imports of modules on top - Changed to subprocess call - Put the createhomedir into the if-block --- createuserpkg | 2 +- locallibs/imageutil.py | 17 +++++++++++------ locallibs/userpkg.py | 8 ++------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/createuserpkg b/createuserpkg index 76c8abd..f4fb16b 100755 --- a/createuserpkg +++ b/createuserpkg @@ -26,6 +26,7 @@ from locallibs import kcpassword from locallibs import shadowhash from locallibs import userplist from locallibs import userpkg +from locallibs import imageutil def main(): '''Main''' @@ -136,7 +137,6 @@ def main(): if options.hidden: user_data['IsHidden'] = u'YES' if options.picture: - from locallibs import imageutil user_data['image_data'] = imageutil.generate(options.picture) user_data['image_path'] = options.picture user_plist = userplist.generate(user_data) diff --git a/locallibs/imageutil.py b/locallibs/imageutil.py index cc4bafa..7708277 100644 --- a/locallibs/imageutil.py +++ b/locallibs/imageutil.py @@ -36,13 +36,18 @@ def generate(image_path): temp_image = os.path.join(os.path.sep, 'private/var/tmp', os.path.splitext(os.path.basename(image_path))[0] + '.jpg') # convert image_path to jpeg with 72 dpi and max. width/height 512 px - cmd = ('''/usr/bin/sips -s format jpeg "{0}" ''' - '''-s formatOptions high -s dpiHeight 72 -s dpiWidth 72 --resampleHeightWidthMax 512 ''' - '''--out "{1}"''').format( - image_path, - temp_image) + cmd = ['/usr/bin/sips', + '-s', 'format', 'jpeg', image_path, + '-s', 'formatOptions', 'high', + '-s', 'dpiHeight', '72', + '-s', 'dpiWidth', '72', + '--resampleHeightWidthMax', '512', + '--out', temp_image + ] try: - subprocess.call(cmd, shell=True) + retcode = subprocess.call(cmd) + if retcode: + raise ImageUtilException(u'sips command failed') # check if temporary image exists and is not blank if is_non_zero_file(temp_image): with open(temp_image, 'rb') as image_file: diff --git a/locallibs/userpkg.py b/locallibs/userpkg.py index 3fbc3ea..57903b0 100644 --- a/locallibs/userpkg.py +++ b/locallibs/userpkg.py @@ -33,7 +33,6 @@ # we're operating on the boot volume _POSTINSTALL_LIVE_ACTIONS_ fi -_POSTINSTALL_CREATEHOMEDIR_ exit 0 """ @@ -90,7 +89,7 @@ """ PI_CREATEHOMEDIR = """ -/usr/sbin/createhomedir -c + /usr/sbin/createhomedir -c """ def make_postinstall_script(scripts_path, pkg_info): @@ -101,7 +100,6 @@ def make_postinstall_script(scripts_path, pkg_info): pi_reqs = set() pi_actions = set() pi_live_actions = set() - pi_createhomedir = set() pi_live_actions.add(PI_LIVE_KILLDS) if pkg_info.get('is_admin'): pi_actions.add(PI_ADD_ADMIN_GROUPS) @@ -109,7 +107,7 @@ def make_postinstall_script(scripts_path, pkg_info): if pkg_info.get('kcpassword'): pi_actions.add(PI_ENABLE_AUTOLOGIN) if pkg_info.get('createhomedir'): - pi_createhomedir.add(PI_CREATEHOMEDIR) + pi_live_actions.add(PI_CREATEHOMEDIR) postinstall = POSTINSTALL_TEMPLATE postinstall = postinstall.replace( '_POSTINSTALL_REQUIREMENTS_', '\n'.join(pi_reqs)) @@ -117,8 +115,6 @@ def make_postinstall_script(scripts_path, pkg_info): '_POSTINSTALL_ACTIONS_', '\n'.join(pi_actions)) postinstall = postinstall.replace( '_POSTINSTALL_LIVE_ACTIONS_', '\n'.join(pi_live_actions)) - postinstall = postinstall.replace( - '_POSTINSTALL_CREATEHOMEDIR_', '\n'.join(pi_createhomedir)) postinstall = postinstall.replace( '_USERNAME_', user_plist[u'name'][0].encode('utf-8')) postinstall = postinstall.replace( From 71cf74b6b0dd303645049baf41ed60c5de585463 Mon Sep 17 00:00:00 2001 From: Gerd Date: Mon, 12 Mar 2018 22:38:17 +0100 Subject: [PATCH 3/3] Refined help message for createhomedir --- createuserpkg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/createuserpkg b/createuserpkg index f4fb16b..b3a09b3 100755 --- a/createuserpkg +++ b/createuserpkg @@ -86,7 +86,8 @@ def main(): help='Change user picture.') optional_user_options.add_option( '--createhomedir', '-c', action='store_true', - help='Create and populate home directories on the local computer.') + help='Create and populate home directories on the local computer ' + '(ATTENTION: works only when installing on the boot volume).') parser.add_option_group(required_user_options) parser.add_option_group(required_package_options)