aboutsummaryrefslogtreecommitdiffstats
path: root/machammer
diff options
context:
space:
mode:
authorFilipp Lepalaan <filipp@mac.com>2016-09-23 17:39:08 +0300
committerFilipp Lepalaan <filipp@mac.com>2016-09-23 17:39:08 +0300
commit712f5bb479b7183a49feb1ee6cf279d1dbd39f6b (patch)
treea571c468d90f6937c6840e40efc75e4ca58a616b /machammer
parent932e24d6187daea8b63baec6a19d0319f9270980 (diff)
downloadmachammer-712f5bb479b7183a49feb1ee6cf279d1dbd39f6b.tar.gz
machammer-712f5bb479b7183a49feb1ee6cf279d1dbd39f6b.tar.bz2
machammer-712f5bb479b7183a49feb1ee6cf279d1dbd39f6b.zip
Return None as path to EULA-image volumes
Diffstat (limited to 'machammer')
-rw-r--r--machammer/__init__.py6
-rw-r--r--machammer/functions.py229
-rw-r--r--machammer/printers.py39
-rw-r--r--machammer/system_profiler.py93
-rwxr-xr-xmachammer/tests.py74
5 files changed, 441 insertions, 0 deletions
diff --git a/machammer/__init__.py b/machammer/__init__.py
new file mode 100644
index 0000000..645ecea
--- /dev/null
+++ b/machammer/__init__.py
@@ -0,0 +1,6 @@
+__all__ = ['']
+__title__ = 'machammer'
+__author__ = 'Filipp Lepalaan'
+__version__ = '0.1'
+__license__ = 'MIT'
+__copyright__ = 'Copyright 2016 Filipp Lepalaan'
diff --git a/machammer/functions.py b/machammer/functions.py
new file mode 100644
index 0000000..b7f13b1
--- /dev/null
+++ b/machammer/functions.py
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+
+import os
+import plistlib
+import subprocess
+import sys
+import tempfile
+
+from xml.parsers.expat import ExpatError
+from system_profiler import SystemProfile
+
+
+SERVICEDIR = '/Library/Services'
+
+
+def call(*args):
+ """Shorthand for subprocess.call.
+
+ > call('ls', '/Users')
+ """
+ subprocess.call(args)
+
+
+def rmdir(path):
+ """Shortcut for deleting a directory."""
+ call('rm', '-r', path)
+
+
+def display_notification(msg, title='', subtitle=''):
+ """Display notification with optional title and subtitle.
+
+ display_notification('Mymessage')
+ """
+ msg = msg.replace('"', '\\"')
+ title = title.replace('"', '\\"')
+ subtitle = subtitle.replace('"', '\\"')
+ cmd = 'display notification "{0}" with title "{1}" subtitle "{2}"'
+ osascript(cmd.format(msg, title, subtitle))
+
+
+def ditto(src, dst):
+ """Shortcut for ditto."""
+ subprocess.call(['/usr/bin/ditto', src, dst])
+
+
+def rsync(src, dst, flags='auE'):
+ """Shortcut for rsync."""
+ subprocess.call(['/usr/bin/rsync', '-' + flags, src, dst])
+
+
+def dscl(domain='.', *args):
+ """Shortcut for dscl."""
+ subprocess.call(['/usr/bin/dscl', domain] + args)
+
+
+def exec_jar(path, user):
+ javapath = '/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java'
+ if not os.path.exists(javapath):
+ raise ValueError('Looks like your machine does not have Java installed')
+
+ subprocess.call(['/bin/launchctl', 'asuser', user, javapath, '-jar', path, '-silent'])
+
+
+def osascript(s):
+ subprocess.call(['/usr/bin/osascript', '-e', s])
+
+
+def tell_app(app, s):
+ osascript('tell application "%s" to %s' % (app, s))
+
+
+def quit_app(app):
+ tell_app(app, 'quit')
+
+
+def copy_app(path):
+ """Copy path to /Applications folder."""
+ rsync(path.rstrip('/'), '/Applications/')
+
+
+def add_login_item(path, name=None, hidden=True):
+ """Add login item to the current user."""
+ if not name:
+ name = os.path.basename(path)
+
+ hidden = 'true' if hidden else 'false'
+ tell_app('System Events',
+ 'make login item at end with properties {path:"%s", hidden:%s, name:"%s"}' % (path, hidden, name))
+
+
+def remove_login_item(**kwargs):
+ """Remove login item from the current user."""
+ if not (kwargs.get('name') or kwargs.get('path')):
+ raise ValueError('Need either path or name')
+
+ if kwargs.get('name') and kwargs.get('path'):
+ raise ValueError('Please specify only path OR name')
+
+ k, v = kwargs.items()[0]
+ tell_app('System Events', 'delete every login item whose %s is "%s"' % (k, v))
+
+
+def create_user(username, realname, password):
+ """Create a user account."""
+ os.system("""dscl . create /Users/{0}
+ dscl . create /Users/{0} RealName "{1}"
+ dscl . passwd /Users/{0} {2}
+ dscl . create /Users/{0} UniqueID 501
+ dscl . create /Users/{0} PrimaryGroupID 80
+ dscl . create /Users/{0} UserShell /bin/bash
+ dscl . create /Users/{0} NFSHomeDirectory /Users/{0}
+ cp -R /System/Library/User\ Template/English.lproj /Users/{0}
+ chown -R {0}:staff /Users/{0}""".format(username, realname, password))
+
+
+def hide_user(username, hide_home=True):
+ """Hide a user account."""
+ path = '/Users/%s' % username
+ subprocess.call(['dscl', '.', 'create', path, 'IsHidden', '1'])
+
+ if hide_home:
+ subprocess.call(['/usr/bin/chflags', 'hidden', path])
+
+
+def delete_user(username, delete_home=True):
+ """Delete a user account."""
+ path = '/Users/' + username
+ dscl = subprocess.check_output(['dscl', '-plist', '.', 'read', path])
+ userinfo = plistlib.readPlistFromString(dscl)
+
+ subprocess.call(['dscl', '.', 'delete', path])
+
+ if delete_home:
+ homedir = userinfo['dsAttrTypeStandard:NFSHomeDirectory'][0]
+ os.rmdir(homedir)
+
+
+def make_admin(username):
+ """Give admin rights to username."""
+ subprocess.call(['dscl', '.', '-append', '/Groups/admin', 'users', username])
+
+
+def enable_ard(username):
+ """Enable Apple Remote Desktop for username."""
+ subprocess.call(['/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart',
+ '-activate', '-configure',
+ '-access', '-on',
+ '-users', username,
+ '-privs', '-all',
+ '-restart', '-agent'])
+
+
+def is_laptop():
+ profile = SystemProfile('Hardware')
+ return 'Book' in profile.machine_model
+
+
+def is_desktop():
+ return not is_laptop()
+
+
+def mount_image(dmg):
+ """Mount disk image and return path to mountpoint."""
+ r = subprocess.check_output(['/usr/bin/hdiutil', 'mount', '-plist', '-nobrowse', dmg])
+
+ try:
+ plist = plistlib.readPlistFromString(r)
+ except ExpatError: # probably a EULA-image, return None instead of breaking
+ return None
+
+ for p in [p.get('mount-point') for p in plist.get('system-entities')]:
+ if p and os.path.exists(p):
+ return p
+
+ raise ValueError('Failed to mount %s' % dmg)
+
+
+def mount_and_install(dmg, pkg):
+ """Mountsthe DMG and installs the PKG."""
+ p = mount_image(dmg)
+ install_pkg(os.path.join(p, pkg))
+
+
+def install_profile(path):
+ """Install a configuration profile."""
+ subprocess.call(['/usr/bin/profiles', '-I', '-F', path])
+
+
+def install_pkg(pkg, target='/'):
+ """Install a package."""
+ subprocess.call(['/usr/sbin/installer', '-pkg', pkg, '-target', target])
+
+
+def mount_afp(url, username, password, mountpoint=None):
+ """Mount AFP share."""
+ if mountpoint is None:
+ mountpoint = tempfile.mkdtemp()
+ subprocess.call(['/sbin/mount_afp', 'afp://%s:%s@%s' % (username, password, url), mountpoint])
+ return mountpoint
+
+
+def umount(path):
+ """Unmount path."""
+ subprocess.call(['/sbin/umount', path])
+
+
+def install_su(restart=True):
+ """Install all available Apple software Updates, restart if update requires it."""
+ su_results = subprocess.check_output(['/usr/sbin/softwareupdate', '-ia'])
+ if restart and ('restart' in su_results):
+ tell_app('Finder', 'restart')
+ sys.exit(0)
+
+
+def disable_wifi(port='en1'):
+ subprocess.call(['/usr/sbin/networksetup', '-setairportpower', port, 'off'])
+ subprocess.call(['/usr/sbin/networksetup', '-setnetworkserviceenabled', 'Wi-Fi', 'off'])
+
+
+def log(msg):
+ print('*** %s...' % msg)
+
+
+def install_service(src):
+ """Copy .service to systemwide Services folder."""
+ if not os.path.exists(SERVICEDIR):
+ os.mkdir(SERVICEDIR)
+
+ ditto(src, SERVICEDIR)
diff --git a/machammer/printers.py b/machammer/printers.py
new file mode 100644
index 0000000..d3c7539
--- /dev/null
+++ b/machammer/printers.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import subprocess
+
+
+def delete_printers():
+ for p in subprocess.check_output(['lpstat', '-p']).strip().split("\n"):
+ subprocess.call(['lpadmin', '-x', p[1]])
+
+
+def add_printer(printer, options={}):
+ """
+ Add a printer
+ A printer is a tuple (name, PPD path, LPD address)
+ """
+ cmd = ['/usr/sbin/lpadmin', '-x', printer[1]]
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (lpadminxOut, lpadminxErr) = proc.communicate()
+
+ # Install the printer
+ cmd = ['/usr/sbin/lpadmin',
+ '-p', printer[0].replace(' ', '-'),
+ '-L', printer[0][0:2],
+ '-D', printer[0],
+ '-v', 'lpd://%s' % printer[2],
+ '-P', '/Library/Printers/PPDs/Contents/Resources/%s' % printer[1],
+ '-E',
+ '-o', 'printer-is-shared=false',
+ '-o', 'printer-error-policy=abort-job']
+
+ for option in options.keys():
+ cmd.append("-o")
+ cmd.append(str(option) + "=" + str(options[option]))
+
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (res, err) = proc.communicate()
+
+ if err:
+ raise Exception(err)
diff --git a/machammer/system_profiler.py b/machammer/system_profiler.py
new file mode 100644
index 0000000..81dfb69
--- /dev/null
+++ b/machammer/system_profiler.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+
+import json
+import shelve
+import logging
+import os.path
+import tempfile
+import datetime
+import plistlib
+import subprocess
+
+from datetime import datetime, timedelta
+
+
+DEFAULT_DT = 'Hardware'
+CACHE_EXPIRE = timedelta(seconds=60*60*1)
+PROFILER_PATH = '/usr/sbin/system_profiler'
+
+
+class SystemProfile(object):
+ def __init__(self, dt=DEFAULT_DT):
+ types = subprocess.check_output([PROFILER_PATH, '-listDataTypes']).strip()
+ self.types = [x[2:].replace('DataType', '') for x in types.split("\n") if x.startswith('SP')]
+ self.types.sort()
+
+ if dt not in self.types:
+ raise ValueError('Invalid type %s. Should be one of %s' % (dt, ', '.join(self.types)))
+
+ tmp = tempfile.mkstemp()
+ self.dt = 'SP%sDataType' % dt
+
+ shelf_path = os.path.join(tempfile.gettempdir(), '%s.system_profiler' % self.dt)
+ shelf = shelve.open(shelf_path)
+
+ if shelf.get(dt) and shelf.get('expires'):
+ if shelf.get('expires') > datetime.now():
+ self._data = shelf[dt]
+ return
+
+ try:
+ subprocess.call([PROFILER_PATH, self.dt, '-detaillevel', 'full', '-xml'], stdout=tmp[0])
+ except Exception as e:
+ raise Exception('Failed to fetch system profile: %s' % e)
+
+ xml = plistlib.readPlist(tmp[1])
+ self._data = xml[0]['_items']
+ logging.debug(self._data)
+
+ shelf['expires'] = datetime.now() + CACHE_EXPIRE
+ shelf[dt] = self._data
+ shelf.close()
+
+ def json(self):
+ return json.dumps(self._data)
+
+ def get_keys(self):
+ keys = self._data[0].keys()
+ keys.sort()
+ return keys
+
+ def get_types(self):
+ return self.types
+
+ def find(self, k, v):
+ """
+ Return value(s) of property with key k containing v
+ """
+ return [x for x in self._data if v in x[k]]
+
+ def __str__(self):
+ return str(self._data)
+
+ def __getattr__(self, attr):
+ try:
+ return self._data[0][attr]
+ except KeyError as e:
+ raise ValueError('Property "%s" not found' % attr)
+
+ def __getitem__(self, attr):
+ return self._data[attr]
+
+
+def types():
+ return SystemProfile().get_types()
+
+def keys(dt=DEFAULT_DT):
+ return SystemProfile(dt).get_keys()
+
+def get(dt, param):
+ return getattr(SystemProfile(dt), param)
+
+def find(dt, k, v):
+ return SystemProfile(dt).find(k, v)
diff --git a/machammer/tests.py b/machammer/tests.py
new file mode 100755
index 0000000..6e9b28e
--- /dev/null
+++ b/machammer/tests.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import logging
+import subprocess
+from unittest import main, skip, TestCase
+
+import functions as mh
+import system_profiler
+
+
+class SystemProfilerTestCase(TestCase):
+ def testSerialNumber(self):
+ sn = system_profiler.get('Hardware', 'serial_number')
+ self.assertTrue(len(sn) > 8)
+
+ def testInvalidType(self):
+ with self.assertRaises(Exception):
+ system_profiler.SystemProfile('Whatever')
+
+ def testKeys(self):
+ self.assertTrue(len(system_profiler.keys()) > 3)
+
+ def testTypes(self):
+ self.assertIn('Hardware', system_profiler.types())
+
+ def testOsVersion(self):
+ """
+ Check that the OS version we get from SP is contained
+ in the output of sw_vers
+ """
+ build = subprocess.check_output(['sw_vers', '-buildVersion']).strip()
+ software = system_profiler.SystemProfile('Software')
+ self.assertIn(build, software.os_version)
+
+ def testOsVersionShortcut(self):
+ build = subprocess.check_output(['sw_vers', '-buildVersion']).strip()
+ self.assertTrue(build in system_profiler.get('Software', 'os_version'))
+
+
+class AppsTestCase(TestCase):
+ def setUp(self):
+ self.profile = system_profiler.SystemProfile('Applications')
+
+ def testFindStickes(self):
+ results = self.profile.find('_name', 'Stickies')
+ self.assertTrue(len(results) > 0)
+
+ def testStickiesVersion(self):
+ results = self.profile.find('_name', 'Stickies')
+ self.assertEquals(results[0]['version'], '10.0')
+
+ def testFindApplications(self):
+ results = self.profile.find('path', '/Applications')
+ self.assertTrue(len(results) > 10)
+
+
+class FunctionsTestCase(TestCase):
+ def setUp(self):
+ self.stickes = '/Applications/Stickies.app'
+
+ def test_notification(self):
+ mh.display_notification('blaaa "lalala"')
+
+ def test_add_login_item(self):
+ mh.add_login_item(self.stickes)
+
+ def test_remove_login_item(self):
+ mh.remove_login_item(path=self.stickes)
+
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.DEBUG)
+ main()