Git Quick-start

Git it easy. It’s a command with some verbs. Let’s start with the init verb, which creates an empty git repository in the working directory (or supply a path after the verb)

git init

Now let’s touch a file in that directory. Once a new file is there, which that new repo as your working directory, run git  with the status verb:

git status

Oh look, you now see that you’re “On branch master” – we’ll talk branching later. You see “No commits yet” and hey, what’s that, an Untracked file! Run git with the add verb, and this time you need to specify a file or path (I’ll use . assuming you’re working directory is still the directory of your path).

git add .

Now let’s run that status command again. And hey, it sees that you now have a staged file (or files). 

Now let’s run our first commit. This takes the tracked and staged file that we just created and commits it. Until we do this we can always revert back to the previous state of that file (which in this simple little walkthrough would be for the file to no longer exist). 

git commit -m “test”

Now let’s edit our file and this time let’s run git with the diff verb:

git diff

Hey, you can now see what changed between your file(s). Easy, right? Check out the logs to see what you’ve been doing to poor git:

git log

Hey look, there’s a commit listed there, along with an author, a date and time stamp, as well as a name of the file(s) in the commit. Now, let’s run a reset to revert to our last commit. This will overwrite the changes we just made prior to doing the diff (you can use a specific commit by using it as the next position after —hard or you can just leave it for the latest actual commit:

git reset —hard

Now this resets all files back to the way it was before you started mucking around with those poor files. OK, so we’ve been working off in our own little world. Before we explore the wide world that is cloning a repository off the interwebs, first we’re going to do a quick look at branches. You know how we reset all of our files in the previous command? What if we had 30 files and we just wanted to reset one? You shouldn’t work in your master branch for a number of reasons. So let’s look at existing branches by running git with the branch verb:

git branch

You see that you have one branch, the “* master” branch. To create a new branch, simply type git followed by the name of the branch you wish to create (in this case it will be called myspiffychanges1):

git branch myspiffychanges1

Run git with the branch verb again and you’ll see that below master, your new branch appears. The asterisk is always used so you know which branch you’re working in. To switch between branches, use the checkout verb along with the name of the branch:

git checkout myspiffychanges1

I could have done both of the previous steps in one command, by using the -b flag with the checkout verb:

git checkout -b myspiffychanges1

OK now, the asterisk should be on your new branch and you should be able to make changes. Let’s edit that file from earlier. Then let’s run another git status and note that your modifications can be seen. Let’s add them to the list of tracked changes using the git add  for the working directory again:

git add .

Now let’s commit those changes:

git commit -m "some changes"

And now we have two branches, a little different from one another. Let’s merge the changes into the master branch next. First, let’s switch back to the master branch:

git checkout master

And then let’s merge those changes:

git merge myspiffychanges1

OK – so now you know how to init a project, branch, and merge. Before we go on the interwebs let’s first setup your name. Notice in the logs that the Author field displays a name and an email address. Let’s see where that comes from:

git config --list

This is initially populated by ~/.gitconfig so you can edit that. Or, let’s remove what is in that list:

git config --unset-all

And then we can add a new set of information to the key we’d like to edit:

git config "Charles Edge" --global

You might as well set an email address too, so people can yell at you for your crappy code some day:

git config “” --global

OK, optics aside, let’s clone an existing repository onto our computer. The clone verb allows you to, —insert suspense here— clone a repository into your home directory:

git clone

The remote verb allows you to make a local copy of a branch. But it takes a couple of steps. First, init a project with the appropriate name and then cd into it. Then we’re going to grab the url from GitHub:

And add it using the remote verb:

git remote add AutoPkg

Now let’s fetch a branch of that project, in this case, master:

git fetch test myspiffychanges1

Now we’ll want to download the contents of that branch:

git pull myspiffychanges1

And once we’ve made some changes, let’s push our changes:

git push test myspiffychanges1


A framework is a type of bundle that packages dynamic shared libraries with the resources that the library requires, including files (nibs and images), localized strings, header files, and maybe documentation. The .framework is an Apple structure that contains all of the files that make up a framework.

Frameworks are stored in the following location (where the * is the name of an app or framework):

  • /Applications/*contents/Frameworks
  • /Library/*/
  • /Library/Application Support/*/*.app/Contents/
  • /Library/Developer/CommandLineTools/
  • /Library/Developer/
  • /Library/Frameworks
  • /Library/Printers/
  • /System/iOSSupport/System/Library/PrivateFrameworks
  • /System/iOSSupport/System/Library/Frameworks
  • /System/Library/CoreServices
  • /System/Library/Frameworks
  • /System/Library/PrivateFrameworks
  • /usr/local/Frameworks 

If you just browse through these directories, you’ll see so many things you can use in apps. You can easily add an import followed by the name in your view controllers in Swift. For example, in /System/Library/Frameworks you’ll find the Foundation.framework. Foundation is pretty common as it contains a number of APIs such as NSObject (NSDate, NSString, and NSDateFormatter). 

You can import this into a script using the following line:

import Foundation

As with importing frameworks/modules/whatever (according to the language) – you can then consume the methods/variables/etc in your code (e.g.  let url = NSURL(fileURLWithPath: “names.plist”).

The Cloud Kit Key State Controller

Each key in the Cloud Kit Key State Controller is considered a “View” with a number of metadata values. Views include AutoUnlock, Engram, Health, Home, ApplePay, and Manatee views. The /usr/sbin/ckksctl binary can be used to reset keys, useful when troubleshooting sync issues. 

To start, let’s run a status:

sudo /usr/sbin/ckksctl status

To reset, run the command again, with a reset verb:

sudo /usr/sbin/ckksctl reset

Episode 107 of the MacAdmins Podcast: Sweet Rootkits, a Year in Review

It’s been a great year for the MacAdmins Podcast. And a special thank you to Tom, Marcus, Emily, James, and the former co-host Pepijn for continuing to allow me to be a part of something special. The last episode of the year is available at, using the below embedded link, or wherever you get your podcasts!

Download Older Versions of macOS and Mac OS X

I’ve posted a few old links in my time (as I near 4,000 posts it would be hard not to have some that are broken). But Apple App Store downloads seem to do better with not breaking links. So here are some to old versions of macOS and OS X, in case like me, you always seem to need some old thing for testing:

Old versions of server are actually easier. You can download OS X Lion Server: or macOS Server:  and most versions are available on the developer portal at

A Bit About Python Module Imports

The first lines of practically every python script ever begin with the word import. Per the python docs at a module is “a file containing Python definitions and statements.” To open a python environment, simply open Terminal and type Python


To import a module just type import followed by the name of the module. A common one is os, so let’s import that:

import os

Now let’s use str to see where it is:


You’ll then see what the script was that is actually the os module and where it can be found/edited/viewed. Now let’s do a couple of basic tasks with os. First, let’s grab the user who’s running the script’s id (still from a standard python interpreter):


Here, we’re using a function that’s built into that os script, and the () is the parameters we’re passing to the function. When run in that interactive mode, you can use os.environ to see what environment variables your python script has access to (e.g. if you’re shelling out a command).


Now let’s use that os to actually shell out a bash command. Here, we’ll set a variable in the python interpreter:

bashCommand="cat /test.log"

Basically, we just set a variable called bashCommand to contain a simple cat of a log file (quoting it since it has special characters and all that). Next, we’ll use os.system with the variable of the command as the parameter we’re sending into the command:


Now I can clear the contents of that bashCommand variable using the del command, still from within that python console:

del bashCommand

When a module is imported, the interpreter first searches for a built-in module with the name you supply. If the interpreter can’t find a module, it will then search through the current working directory, then the PYTHONPATH wet in sys.path and . If not found, it then searches for a file named in a list of directories given by the variable sys.path. Now, let’s import urllib and check out what functions it has:

import urllib dir(urllib)

Then let’s ask for help for one of them:


The response will look something like this (ymmv):

urljoin(base, url, allow_fragments=True)

    Join a base URL and a possibly relative URL to form an absolute

    interpretation of the latter.

Now you know what goes in the parenthesis when you actually call the function from within your scripts.

Looping cURLy Braces

No, this article is not about my daughter’s curly hair and that expander thingie she had in her mouth for awhile (that sucked for her, booo). Instead we’re talking about expanding braces in shell scripts so we can expand braces in the script.

What’s a brace? As with grammar, a brace is a type of bracket meant to denote a series of characters. The bash interpreter will then allow for the expansion to include a series, such as 1..100 or 1 to 100 or a..z or a through z, useful in loops where the variable name (num in the below example) when invoked in the loop as $num will expand to be the number in the series in the loop. In the below example, we start with a set command and use -B to expand the curly braces:

set -B
for num in {1..100}; do
echo `curl -s -k 'GET' -H 'header info' ''$num`
echo ''$num

Note: In the above example, I did an echo just as a debugging line. I’d remove that before actually running the script (or more to the point automating it to run).

If we hadn’t of used the brace, we’d just be typing a lot more to get a 1 to 100 series to expand (e.g. for (( i = 0; i < 20; i++ ))) You can also use curly braces to do some much cooler expansion than a series of numbers. For example, {a,b{1..100},c,d{1…100},e,f{1…100},g} would do a then b1 through b100 then c, etc. In the above e script, once we’ve enabled brace expansion with that -B for set, we can then simply echo a curl onto the screen that loops through a series of 100 log files, using the $i within the loop.

A simpler example might be to use the following , which uses a series of letters instead:

set -B
for letter in {a..z}
echo $letter

If you actually wanted to learn more about palatal expanders then I’m sorry to bore you with everything else – here’s a better place for learning about that:

Updated My Apple Admin Conferences Page

I’ve been keeping a list of Apple Admin conferences for a few years now. I probably should have versioned it and kept each iteration, but… no need to pollute the interwebs with more outdated stuffs than I already have. So here’s the link for the latest version, updated with all the event dates announced thus far:

Hope to see you at some!