From 201a751f0a0da9fa0427d683cb56725a40a00d89 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 3 Aug 2017 21:01:34 -0700 Subject: [pkg] automate flat package --- Makefile | 3 + pkg/osx/installer/Bitmask.pkgproj | 755 ------------------------- pkg/osx/installer/README.rst | 2 - pkg/osx/installer/post-inst.sh | 4 - pkg/osx/installer/pre-inst.sh | 3 - pkg/osx/installer/se.leap.bitmask-helper.plist | 29 - pkg/osx/quickpkg | 557 ++++++++++++++++++ pkg/osx/scripts/postinstall | 6 + pkg/osx/scripts/preinstall | 5 + pkg/osx/scripts/se.leap.bitmask-helper.plist | 29 + pkg/pyinst/build.mk | 2 +- 11 files changed, 601 insertions(+), 794 deletions(-) delete mode 100755 pkg/osx/installer/Bitmask.pkgproj delete mode 100644 pkg/osx/installer/README.rst delete mode 100755 pkg/osx/installer/post-inst.sh delete mode 100755 pkg/osx/installer/pre-inst.sh delete mode 100644 pkg/osx/installer/se.leap.bitmask-helper.plist create mode 100755 pkg/osx/quickpkg create mode 100755 pkg/osx/scripts/postinstall create mode 100755 pkg/osx/scripts/preinstall create mode 100644 pkg/osx/scripts/se.leap.bitmask-helper.plist diff --git a/Makefile b/Makefile index eac906c8..d4f3afe7 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,9 @@ bundle_in_docker: docker_container: cd pkg/docker_bundle && docker build -t mybundle . +osx_pkg: + pkg/osx/quickpkg --output dist/Bitmask-$(NEXT_VERSION).pkg --scripts pkg/osx/scripts/ dist/Bitmask.app/ + cleanpkg: rm -rf dist build diff --git a/pkg/osx/installer/Bitmask.pkgproj b/pkg/osx/installer/Bitmask.pkgproj deleted file mode 100755 index f42ffc87..00000000 --- a/pkg/osx/installer/Bitmask.pkgproj +++ /dev/null @@ -1,755 +0,0 @@ - - - - - PROJECT - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - /Applications - HIERARCHY - - CHILDREN - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Bitmask.app - PATH_TYPE - 3 - PERMISSIONS - 493 - TYPE - 3 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Utilities - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Automator - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - System - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - VERSION - 3 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PATH - ../../bitmask-dev/pkg/osx/installer/post-inst.sh - PATH_TYPE - 3 - - PREINSTALL_PATH - - PATH - ../pkg/osx/installer/pre-inst.sh - PATH_TYPE - 3 - - RESOURCES - - - CHILDREN - - GID - 0 - PATH - se.leap.bitmask-helper.plist - PATH_TYPE - 1 - PERMISSIONS - 420 - TYPE - 3 - UID - 0 - - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - IDENTIFIER - se.leap.pkg.Bitmask - OVERWRITE_PERMISSIONS - - RELOCATABLE - - USE_HFS+_COMPRESSION - - VERSION - 0.10a1 - - PROJECT_COMMENTS - - NOTES - - PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M - IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv - c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l - cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 - IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 - ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp - dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u - dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD - b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjEyNjUuMjEiPgo8c3R5bGUg - dHlwZT0idGV4dC9jc3MiPgpwLnAxIHttYXJnaW46IDAuMHB4IDAu - MHB4IDAuMHB4IDAuMHB4OyBmb250OiAxMi4wcHggSGVsdmV0aWNh - fQo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5Pgo8cCBjbGFzcz0icDEi - Pk1heSB0aGUgT1NYIGJ1bmRsZXMgYmUgd2l0aCB5b3U8L3A+Cjwv - Ym9keT4KPC9odG1sPgo= - - - PROJECT_SETTINGS - - BUILD_PATH - - PATH - ../../../../../Bitmask/build - PATH_TYPE - 1 - - CERTIFICATE - - NAME - Developer ID Installer: LEAP Encryption Access Project (SB5RR8K33W) - PATH - /Users/user/Library/Keychains/login.keychain - - EXCLUDED_FILES - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - .DS_Store - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove .DS_Store files - PROXY_TOOLTIP - Remove ".DS_Store" files created by the Finder. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - .pbdevelopment - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove .pbdevelopment files - PROXY_TOOLTIP - Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - CVS - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .cvsignore - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - .cvspass - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - .svn - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .git - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .gitignore - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove SCM metadata - PROXY_TOOLTIP - Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - classes.nib - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - designable.db - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - info.nib - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Optimize nib files - PROXY_TOOLTIP - Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - Resources Disabled - TYPE - 1 - - - PROTECTED - - PROXY_NAME - Remove Resources Disabled folders - PROXY_TOOLTIP - Remove "Resources Disabled" folders. - STATE - - - - SEPARATOR - - - - NAME - Bitmask - REFERENCE_FOLDER_PATH - /Users/user/leap/bitmask-dev/dist - - - TYPE - 1 - VERSION - 2 - - diff --git a/pkg/osx/installer/README.rst b/pkg/osx/installer/README.rst deleted file mode 100644 index ff5676e3..00000000 --- a/pkg/osx/installer/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -This is a project to generate Bitmask.pgk, using the program 'Packages'. -That will sign the installer with LEAP's developer certificates. diff --git a/pkg/osx/installer/post-inst.sh b/pkg/osx/installer/post-inst.sh deleted file mode 100755 index 0202323a..00000000 --- a/pkg/osx/installer/post-inst.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -# Bitmask Post-Instalation script -cp se.leap.bitmask-helper.plist /Library/LaunchDaemons/ -launchctl load /Library/LaunchDaemons/se.leap.bitmask-helper.plist || echo "Already loaded, skipping..." diff --git a/pkg/osx/installer/pre-inst.sh b/pkg/osx/installer/pre-inst.sh deleted file mode 100755 index 1651a221..00000000 --- a/pkg/osx/installer/pre-inst.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -# Bitmask Post-Instalation script -[[ -f /Library/LaunchDaemons/se.leap.bitmask-helper.plist ]] && launchctl unload /Library/LaunchDaemons/se.leap.bitmask-helper.plist diff --git a/pkg/osx/installer/se.leap.bitmask-helper.plist b/pkg/osx/installer/se.leap.bitmask-helper.plist deleted file mode 100644 index 4428f131..00000000 --- a/pkg/osx/installer/se.leap.bitmask-helper.plist +++ /dev/null @@ -1,29 +0,0 @@ - - - - - StandardOutPath - bitmask-helper.log - StandardErrorPath - bitmask-helper-err.log - GroupName - daemon - KeepAlive - - SuccessfulExit - - - Label - se.leap.bitmask-helper - ProgramArguments - - /Applications/Bitmask.app/Contents/Resources/bitmask-helper/bitmask-helper - - RunAtLoad - - WorkingDirectory - /Applications/Bitmask.app/Contents/Resources/bitmask-helper/ - SessionCreate - - - diff --git a/pkg/osx/quickpkg b/pkg/osx/quickpkg new file mode 100755 index 00000000..a8d04982 --- /dev/null +++ b/pkg/osx/quickpkg @@ -0,0 +1,557 @@ +#!/usr/bin/python + +import argparse +import string +import os +import subprocess +import tempfile +import shutil +import stat + +# includes FoundationPlist since some apps store their Info.plist +# as binary PropertyLists + +# FoundationPlist: + +# Copyright 2009-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 +# +# 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. +"""FoundationPlist.py -- a tool to generate and parse MacOSX .plist files. + +This is intended as a drop-in replacement for Python's included plistlib, +with a few caveats: + - readPlist() and writePlist() operate only on a filepath, + not a file object. + - there is no support for the deprecated functions: + readPlistFromResource() + writePlistToResource() + - there is no support for the deprecated Plist class. + +The Property List (.plist) file format is a simple XML pickle supporting +basic object types, like dictionaries, lists, numbers and strings. +Usually the top level object is a dictionary. + +To write out a plist file, use the writePlist(rootObject, filepath) +function. 'rootObject' is the top level object, 'filepath' is a +filename. + +To parse a plist from a file, use the readPlist(filepath) function, +with a file name. It returns the top level object (again, usually a +dictionary). + +To work with plist data in strings, you can use readPlistFromString() +and writePlistToString(). +""" + +from Foundation import NSData, \ + NSPropertyListSerialization, \ + NSPropertyListMutableContainersAndLeaves, \ + NSPropertyListXMLFormat_v1_0 + + +class FoundationPlistException(Exception): + '''Base error for this module''' + pass + + +class NSPropertyListSerializationException(FoundationPlistException): + '''Read error for this module''' + pass + + +class NSPropertyListWriteException(FoundationPlistException): + '''Write error for this module''' + pass + + +# private functions +def _dataToPlist(data): + '''low-level function that parses a data object into a propertyList object''' + darwin_vers = int(os.uname()[2].split('.')[0]) + if darwin_vers > 10: + (plistObject, plistFormat, error) = ( + NSPropertyListSerialization.propertyListWithData_options_format_error_( + data, NSPropertyListMutableContainersAndLeaves, None, None)) + else: + # 10.5 doesn't support propertyListWithData:options:format:error: + # 10.6's PyObjC wrapper for propertyListWithData:options:format:error: + # is broken + # so use the older NSPropertyListSerialization function + (plistObject, plistFormat, error) = ( + NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_( + data, NSPropertyListMutableContainersAndLeaves, None, None)) + if plistObject is None: + if error is None: + error = "Plist data is invalid and could not be deserialized." + raise NSPropertyListSerializationException(error) + else: + return plistObject + + +def _plistToData(plistObject): + '''low-level function that creates NSData from a plist object''' + darwin_vers = int(os.uname()[2].split('.')[0]) + if darwin_vers > 10: + (data, error) = ( + NSPropertyListSerialization.dataWithPropertyList_format_options_error_( + plistObject, NSPropertyListXMLFormat_v1_0, 0, None)) + else: + # use the older NSPropertyListSerialization function on 10.6 and 10.5 + (data, error) = ( + NSPropertyListSerialization.dataFromPropertyList_format_errorDescription_( + plistObject, NSPropertyListXMLFormat_v1_0, None)) + if data is None: + if error is None: + error = "Property list invalid for format." + raise NSPropertyListSerializationException(error) + return data + + +# public functions +def readPlist(filepath): + '''Read a .plist file from filepath. Return the unpacked root object + (which is usually a dictionary).''' + try: + data = NSData.dataWithContentsOfFile_(filepath) + except NSPropertyListSerializationException, error: + # insert filepath info into error message + errmsg = (u'%s in %s' % (error, filepath)) + raise NSPropertyListSerializationException(errmsg) + return _dataToPlist(data) + + +def readPlistFromString(aString): + '''Read a plist data from a string. Return the root object.''' + data = buffer(aString) + return _dataToPlist(data) + + +def writePlist(plistObject, filepath): + '''Write 'plistObject' as a plist to filepath.''' + plistData = _plistToData(plistObject) + if plistData.writeToFile_atomically_(filepath, True): + return + else: + raise NSPropertyListWriteException( + u"Failed to write plist data to %s" % filepath) + + +def writePlistToString(plistObject): + '''Create a plist-formatted string from plistObject.''' + return str(_plistToData(plistObject)) + + +# +# quickpkg +# + + +quickpkg_version = '0.5' +supported_extensions = ['dmg', 'app', 'zip'] + + +# modeled after munkiimport but to build a pkg + + +def logger(log, v=0): + if args.verbosity >= v: + print log + + +def cmdexec(command, stdin=''): + """Execute a command.""" + # if 'command' is a string, split the string into components + if isinstance(command, str): + command = command.split() + + proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) + (stdout, stderr) = proc.communicate(stdin) + + logger("cmdexec: %s, result: %s, error: %s" % (command, stdout, stderr), 3) + + # strip trailing whitespace, which would mess with string comparisons + return {"return_code": proc.returncode, "stderr": stderr.rstrip(), "stdout": stdout.rstrip()} + + +# from munkicommons.py +def getFirstPlist(textString): + """Gets the next plist from a text string that may contain one or + more text-style plists. + Returns a tuple - the first plist (if any) and the remaining + string after the plist""" + plist_header = '' + plist_start_index = textString.find(plist_header) + if plist_start_index == -1: + # not found + return ("", textString) + plist_end_index = textString.find( + plist_footer, plist_start_index + len(plist_header)) + if plist_end_index == -1: + # not found + return ("", textString) + # adjust end value + plist_end_index = plist_end_index + len(plist_footer) + return (textString[plist_start_index:plist_end_index], + textString[plist_end_index:]) + + +def dmg_has_sla(dmgpath): + has_sla = False + imageinfo_cmd = ['/usr/bin/hdiutil', 'imageinfo', dmgpath, '-plist'] + result = cmdexec(imageinfo_cmd) + if result["return_code"] != 0: + print "error getting imageinfo! %s, %s" % (result["return_code"], result["stderr"]) + return False + result_plist = result["stdout"] + imageinfo_dict = readPlistFromString(result_plist) + properties = imageinfo_dict.get('Properties') + if properties is not None: + has_sla = properties.get('Software License Agreement', False) + return has_sla + + +def attachdmg(dmgpath): + global dmg_was_mounted + info_cmd = ["hdiutil", "info", "-plist"] + info_result = cmdexec(info_cmd) + if info_result["return_code"] == 0: + # parse the plist output + (theplist, alltext) = getFirstPlist(info_result["stdout"]) + info_dict = readPlistFromString(theplist) + volpaths = [] + if "images" in info_dict.keys(): + for y in info_dict["images"]: + if "image-path" in y.keys(): + if os.path.samefile(y["image-path"], dmgpath): + for x in y.get("system-entities"): + if "mount-point" in x.keys(): + volpaths.append(x["mount-point"]) + dmg_was_mounted = True + return volpaths + else: + print "error getting hdiutil info" + print "(%d, %s)" % (info_result["returncode"], info_result["stderr"]) + cleanup_and_exit(1) + + attachcmd = ["/usr/bin/hdiutil", + "attach", + dmgpath, + "-mountrandom", + "/private/tmp", + "-plist", + "-nobrowse"] + if dmg_has_sla(dmgpath): + stdin = "Y\n" + print "NOTE: Disk image %s has a license agreement!" % dmgpath + else: + stdin = '' + result = cmdexec(attachcmd, stdin) + if result["return_code"] == 0: + # parse the plist output + (theplist, alltext) = getFirstPlist(result["stdout"]) + resultdict = readPlistFromString(theplist) + volpaths = [] + for x in resultdict["system-entities"]: + if x["potentially-mountable"]: + if x["volume-kind"] == 'hfs': + volpaths.append(x["mount-point"]) + # return the paths to mounted volume + return volpaths + else: + print "error mounting disk image" + print "(%d, %s)" % (result["returncode"], result["stderr"]) + cleanup_and_exit(1) + + +def detachpaths(volpaths): + for x in volpaths: + if os.path.exists(x): + if os.path.ismount(x): + detachcmd = ["/usr/bin/hdiutil", "detach", x] + cmdexec(detachcmd) + + +def finditemswithextension(dirpath, item_extension): + foundapps = [] + if os.path.exists(dirpath): + for x in os.listdir(dirpath): + (item_basename, item_extension) = os.path.splitext(x) + item_extension = string.lstrip(item_extension, '.') + if item_extension == 'app': + foundapps.append(os.path.join(dirpath, x)) + else: + print "path %s does not exist" % dirpath + cleanup_and_exit(1) + return foundapps + + +def appNameAndVersion(app_path): + info_path = os.path.join(app_path, "Contents/Info.plist") + if not os.path.exists(info_path): + print "Application at path %s does not have Info.plist" % app_path + # TODO: cleanup volumes here + cleanup_and_exit(1) + info_plist = readPlist(info_path) + app_name = info_plist.get("CFBundleName") + if app_name is None: + app_name = info_plist.get("CFBundleDisplayName") + if app_name is None: + (app_name, app_ext) = os.path.splitext(os.path.basename(app_path)) + app_identifier = info_plist.get("CFBundleIdentifier") + app_version = info_plist.get("CFBundleShortVersionString") + if app_version is None: + app_version = info_plist.get("CFBundleVersion") + return (app_name, app_identifier, app_version) + + +def cleanup_and_exit(returncode): + global dmgvolumepaths + global dmg_was_mounted + global tmp_path + + if args.clean: + if not dmg_was_mounted: + detachpaths(dmgvolumepaths) + if tmp_path is not None: + shutil.rmtree(tmp_path) + exit(returncode) + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description="""Attempts to build a pkg from the input. + Installer item can be a dmg, zip, or app.""", + epilog="""Example: quickpkg /path/to/installer_item""") + + # takes a path as input + parser.add_argument('item_path', help="path to the installer item") + + scripts_group = parser.add_argument_group('Installation Scripts', + '''These options will set the installation scripts. You pass an entire folder of scripts, + just like the option of `pkgbuild` or you can give a file for the preinstall or postinstall + scripts respectively. If you give both the --scripts and either one or both of --preinstall + and --postinstall, quickpkg will attempt to merge, but throw an error if it cannot.''') + scripts_group.add_argument('--scripts', help="path to a folder with scripts") + scripts_group.add_argument('--preinstall', '--pre', help="path to the preinstall script") + scripts_group.add_argument('--postinstall', '--post', help="path to the postinstall script") + + parser.add_argument('--ownership', choices=['recommended', 'preserve', 'preserve-other'], + help="will be passed through to pkgbuild") + parser.add_argument('--output', '--out', '-o', + help='''path where the package file will be created. If you give the full filename + then you can use '{name}', '{version}' and '{identifier}' as placeholders. + If this is a directory, then the + package will be created with the default filename {name}-{version}.pkg''') + + parser.add_argument('--clean', dest='clean', action='store_true', help="clean up temp files (DEFAULT)") + parser.add_argument('--no-clean', dest='clean', action='store_false', help=" do NOT clean up temp files") + parser.set_defaults(clean=True) + + parser.add_argument('--relocatable', dest='relocatable', action='store_true', + help="sets BundleIsRelocatable in the PackageInfo to true") + parser.add_argument('--no-relocatable', dest='relocatable', action='store_false', + help="sets BundleIsRelocatable in the PackageInfo (DEFAULT is false)") + parser.set_defaults(relocatable=False) + + + parser.add_argument("-v", "--verbosity", action="count", default=0, help="controls amount of logging output (max -vvv)") + parser.add_argument('--version', help='prints the version', action='version', version=quickpkg_version) + + args = parser.parse_args() + + # remove trailing '/' from path + item_path = string.rstrip(args.item_path, '/') + + if item_path.startswith('~'): + item_path = os.path.expanduser(item_path) + item_path = os.path.abspath(item_path) + + # get file extension + (item_basename, item_extension) = os.path.splitext(item_path) + item_extension = string.lstrip(item_extension, '.') + + # is extension supported + if item_extension not in supported_extensions: + print ".%s is not a supported extension!" % item_extension + exit(1) + + foundapps = [] + + # if item is an app, just pass it on + if item_extension == 'app': + if not os.path.exists(item_path): + print "This does not seem to be an Application!" + exit(1) + foundapps.append(item_path) + + dmgvolumepaths = [] + tmp_path = None + dmg_was_mounted = False + tmp_scripts_path = None + tmp_path = tempfile.mkdtemp() + payload_path = os.path.join(tmp_path, "payload") + os.makedirs(payload_path) + + # if item is a dmg, mount it and find useful contents + if item_extension == 'dmg': + dmgvolumepaths = attachdmg(item_path) + for x in dmgvolumepaths: + moreapps = finditemswithextension(x, 'app') + foundapps.extend(moreapps) + if len(foundapps) == 0: + print "Could not find an application!" + cleanup_and_exit(1) + elif len(foundapps) > 1: + print "Found too many Applications! Can't decide!" + print foundapps + cleanup_and_exit(1) + + # if item is zip, unzip to tmp location and find useful contents + if item_extension == 'zip': + unarchive_path = os.path.join(tmp_path, "unarchive") + unzip_cmd = ["/usr/bin/unzip", "-d", unarchive_path, item_path] + result = cmdexec(unzip_cmd) + if result["return_code"] != 0: + print "An error occured while unzipping:" + print "%d, %s" % (result["return_code"], result["stderr"]) + cleanup_and_exit(1) + foundapps = finditemswithextension(unarchive_path, 'app') + if len(foundapps) == 0: + print "Could not find an application!" + cleanup_and_exit(1) + elif len(foundapps) > 1: + print "Found too many Applications! Can't decide!" + print foundapps + cleanup_and_exit(1) + + logger("Found application: %s" % foundapps[0], 1) + + # copy found app to payload folder + app_name = os.path.basename(foundapps[0]) + app_path = os.path.join(payload_path, app_name) + shutil.copytree(foundapps[0], app_path) + + # extract version and other metadata + (app_name, app_identifier, app_version) = appNameAndVersion(app_path) + + logger("Name: %s, ID: %s, Version: %s" % (app_name, app_identifier, app_version), 1) + + # create the component plist + component_plist = os.path.join(tmp_path, app_identifier) + ".plist" + analyzecmd = ["/usr/bin/pkgbuild", + "--analyze", + "--root", payload_path, + "--identifier", app_identifier, + "--version", app_version, + "--install-location", "/Applications", + component_plist] + result = cmdexec(analyzecmd) + + logger(result["stdout"], 1) + if result["return_code"] != 0: + print "Error Code: %d " % result["return_code"] + print result["stderr"] + cleanup_and_exit(1) + + if not args.relocatable: + # read and change component plist + components = readPlist(component_plist) + # component plist is an array of components + for bundle in components: + if "BundleIsRelocatable" in bundle.keys(): + bundle["BundleIsRelocatable"] = False + writePlist(components, component_plist) + + pkg_name = "{name}-{version}.pkg" + if args.output: + if os.path.isdir(args.output): + pkg_path = os.path.join(args.output, pkg_name) + else: + pkg_path = args.output + else: + pkg_path = pkg_name + nospace_app_name = app_name.replace(' ', '') # remove spaces + pkg_path = pkg_path.format(name=nospace_app_name, version=app_version, identifier=app_identifier) + + if not pkg_path.endswith('pkg'): + pkg_path += '.pkg' + + # run pkgutil to build result + pkgcmd = ["/usr/bin/pkgbuild", + "--root", payload_path, + "--component-plist", component_plist, + "--identifier", app_identifier, + "--version", app_version, + "--install-location", "/Applications", + pkg_path] + + if args.scripts and not os.path.exists(args.scripts): + print "scripts folder %s does not exist!" % args.scripts + cleanup_and_exit(1) + + if args.postinstall or args.preinstall: + tmp_scripts_path = os.path.join(tmp_dir, "scripts") + os.makedirs(tmp_scripts_path) + + if args.scripts: + logger("copying %s to tmp scripts folder %s" % (args.scripts, tmp_scripts_path), 1) + shutil.rmtree(tmp_scripts_path) + shutil.copytree(args.scripts, tmp_scripts_path) + if args.postinstall: + if not os.path.exists(args.postinstall): + print "postinstall file %s does not exist!" % args.postinstall + cleanup_and_exit(1) + postinstall_path = os.path.join(tmp_scripts_path, "postinstall") + if os.path.exists(postinstall_path): + print "postinstall script already exists in %s" % args.scripts + cleanup_and_exit(1) + logger("copying %s to %s" % (args.postinstall, postinstall_path), 1) + shutil.copy2(args.postinstall, postinstall_path) + os.chmod(postinstall_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | + stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + if args.preinstall: + if not os.path.exists(args.preinstall): + print "preinstall file %s does not exist!" % args.preinstall + cleanup_and_exit(1) + preinstall_path = os.path.join(tmp_scripts_path, "preinstall") + if os.path.exists(preinstall_path): + print "preinstall script already exists in %s" % args.scripts + cleanup_and_exit(1) + logger("copying %s to %s" % (args.preinstall, preinstall_path), 1) + shutil.copy2(args.preinstall, preinstall_path) + os.chmod(preinstall_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | + stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + + if tmp_scripts_path: + logger("scripts path: %s" % tmp_scripts_path, 1) + pkgcmd.extend(["--scripts", tmp_scripts_path]) + elif args.scripts: + logger("scripts path: %s" % args.scripts, 1) + pkgcmd.extend(["--scripts", args.scripts]) + + if args.ownership: + pkgcmd.extend(["--ownership", args.ownership]) + + result = cmdexec(pkgcmd) + + logger(result["stdout"], 1) + if result["return_code"] != 0: + print "Error Code: %d " % result["return_code"] + print result["stderr"] + else: + print pkg_path + + cleanup_and_exit(0) diff --git a/pkg/osx/scripts/postinstall b/pkg/osx/scripts/postinstall new file mode 100755 index 00000000..9c2176b2 --- /dev/null +++ b/pkg/osx/scripts/postinstall @@ -0,0 +1,6 @@ +#!/bin/sh +# Bitmask Post-Instalation script +cp se.leap.bitmask-helper.plist /Library/LaunchDaemons/ +launchctl load /Library/LaunchDaemons/se.leap.bitmask-helper.plist || echo "Already loaded, skipping..." +echo `date` "::Bitmask post-installation script succeeded." >> /tmp/bitmask-install.log +exit 0 diff --git a/pkg/osx/scripts/preinstall b/pkg/osx/scripts/preinstall new file mode 100755 index 00000000..222922cf --- /dev/null +++ b/pkg/osx/scripts/preinstall @@ -0,0 +1,5 @@ +#!/bin/sh +# Bitmask Pre-Instalation script +[[ -f /Library/LaunchDaemons/se.leap.bitmask-helper.plist ]] && launchctl unload /Library/LaunchDaemons/se.leap.bitmask-helper.plist +echo `date` "::Bitmask pre-installation script succeeded." >> /tmp/bitmask-install.log +exit 0 diff --git a/pkg/osx/scripts/se.leap.bitmask-helper.plist b/pkg/osx/scripts/se.leap.bitmask-helper.plist new file mode 100644 index 00000000..4428f131 --- /dev/null +++ b/pkg/osx/scripts/se.leap.bitmask-helper.plist @@ -0,0 +1,29 @@ + + + + + StandardOutPath + bitmask-helper.log + StandardErrorPath + bitmask-helper-err.log + GroupName + daemon + KeepAlive + + SuccessfulExit + + + Label + se.leap.bitmask-helper + ProgramArguments + + /Applications/Bitmask.app/Contents/Resources/bitmask-helper/bitmask-helper + + RunAtLoad + + WorkingDirectory + /Applications/Bitmask.app/Contents/Resources/bitmask-helper/ + SessionCreate + + + diff --git a/pkg/pyinst/build.mk b/pkg/pyinst/build.mk index b6dea151..2ad224b4 100644 --- a/pkg/pyinst/build.mk +++ b/pkg/pyinst/build.mk @@ -47,7 +47,7 @@ bundle_osx_helpers: mkdir -p $(DIST_VERSION)/apps/helpers cp src/leap/bitmask/vpn/helpers/osx/bitmask-helper $(DIST_VERSION)/apps/helpers/ cp src/leap/bitmask/vpn/helpers/osx/bitmask.pf.conf $(DIST_VERSION)/apps/helpers/ - cp pkg/osx/installer/se.leap.bitmask-helper.plist $(DIST_VERSION)/apps/helpers/ + cp pkg/osx/scripts/se.leap.bitmask-helper.plist $(DIST_VERSION)/apps/helpers/ cp -r pkg/osx/daemon $(DIST_VERSION)/apps/helpers/ cp -r pkg/osx/openvpn $(DIST_VERSION)/apps/helpers/ -- cgit v1.2.3