MacAdmin
Gem to assist in performing common systems administration tasks in OS X
About
MacAdmin endeavors to provide an OO programming interface for constituent OS X system resources. It's comprised of classes with the ability to parse Apple Property Lists (CFPropertyList) and manipulate them as native Ruby objects. The classes work directly with the Property Lists used to abstract OS X system resources -- users, groups, computers, computergroups, etc. -- bypassing the common utilities and APIs normally reserved for this kind of work.
This approach has trade-offs, but it does result in a very powerful and simple model for managing these resources (See Notes).
Notes:
Before forking/cloning/using/testing/etc MacAdmin, please read the license (LICENSE.txt).
One important trade-off worth mentioning when using this gem is that you must have root priviledge in order to access (read) any resources in the DSLocal domains or similarily protected directories and files. This is different from using utils like dscl
, but not unlike using defaults
. Naturally, as with any of these methods, you must also be root in order to make any changes. The code examples below will require root access when performing create operations.
Another important condition to mention is that it will often be necessary to restart OS X's directory service in order to see the changes you've made to any of the affected plists. This can be bothersome and susceptible to race conditions, but in general, it's a manageable issue.
Requirements
- Mac OS X 10.5 and up
- Xcode Command Line Tools
Installation
Simple:
RubyGems should install any dependencies:
$ sudo gem install macadmin
Manual:
Install the bundler gem:
$ sudo gem install bundler
Clone this repo:
$ cd ~/Downloads
$ git clone http://github.com/dayglojesus/macadmin.git
Install the gem dependencies:
$ cd macadmin
$ sudo bundle install
Run the tests:
$ rake
Install the gem:
$ sudo rake install
Test the installation:
Note the path parameter to #create -- use this for testing resource creation in arbitrary directories. Also works for #destroy.
$ cd ~/Downloads
$ irb -r 'macadmin'
>> foobar = User.new 'foobar'
=> {"passwd"=>["********"], "gid"=>["20"], "uid"=>["501"], "shell"=>["/bin/bash"], "name"=>["foobar"], "realname"=>["foobar"], "generateduid"=>["4871DD7C-5C55-47DB-8A7B-B38CBD6DA5A9"], "comment"=>[""], "home"=>["/Users/foobar"]}
>> foobar.exists?
=> false
>> foobar.create "./foobar.plist"
>> foobar.destroy "./foobar.plist"
>> exit
Usage
Load the gems:
require 'rubygems'
require 'macadmin'
Create a new node:
# Here's something cool:
# DSLocalNode will automatically add this custom node to OpenDirectory sandbox when running on 10.8 and up
my_custom_node = DSLocalNode.new 'MCX'
my_custom_node.create_and_activate
Create a computer record on that node:
computer = Computer.new :name => `hostname -s`.chomp, :node => 'MCX'
computer.create
Create a computer group, add the computer record as a member, and apply some policy (on that node):
# Here's a bnuch of policy written as XML, but the mcximport method can also take a file path parameter and load it that way
raw_xml_policy = <<-RAW_XML_CONTENT
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.SoftwareUpdate</key>
<dict>
<key>CatalogURL</key>
<dict>
<key>state</key>
<string>always</string>
<key>value</key>
<string>http://foo.bar.com/reposado/html/content/catalogs/index.sucatalog</string>
</dict>
</dict>
<key>com.apple.screensaver</key>
<dict>
<key>askForPassword</key>
<dict>
<key>state</key>
<string>once</string>
<key>value</key>
<integer>1</integer>
</dict>
</dict>
</dict>
</plist>
RAW_XML_CONTENT
computer_group = ComputerGroup.new :name => 'mcx', :realname => 'MCX', :node => 'MCX'
computer_group.add_user `hostname -s`.chomp
computer_group.mcximport raw_xml_policy
computer_group.create
Create an administrator for your new node:
# Generate a platform appropriate password from a plaintext string
password = Password.apropos "secret_passphrase"
administrator = User.new :name => 'mcxadmin', :password => password, :gid => 80, :node => 'MCX'
administrator.create
Restart the directory services to seal the deal:
restart_directoryservice
Explore a bit...
require 'pp'
# Show the User object
admin = User.new :name => 'mcxadmin', :node => 'MCX'
pp admin.record if admin.exists?
# Show the MCX policy attached to the ComputerGroup object
comp_grp = ComputerGroup.new :name => 'mcx', :node => 'MCX'
puts "Does this local computer group have MCX policy?"
puts comp_grp.has_mcx? ? "Sweet!" : "Bummer..."
puts comp_grp.mcxexport
Tear it down...
# This will take down the entire node we just created and populated: user, computers, computer groups, etc.
mcx_node = DSLocalNode.new 'MCX'
mcx_node.destroy_and_deactivate
restart_directoryservice
Acknowledgments
This gem would not be possible without ckuse's CFPropertyList. Thanks, Christian.