Let's build Github's missing webhook
Using Jenkins with Fastlane, then buddybuild, but before going back to Jenkins (which features this) because buddybuild was indeed too good to be true, I could never quite find a way to get notified every time a Super Important File changed...
You know, that file that's a little unconventional but critical. One that makes your teammates step back from their sit-stand desks to contemplate searching for your name on Slack because you're not cached on anyone's sidebar, Donnie.
The file may have some hacky way to present a view or handle core data models or both++ but the point is that bugs fester in humid environments and this file is hot.

Your steps to get an email every time a file has changed:
  • Register for a github API key
  • Create a daemon to periodically pull the file
  • Use a bash script to diff the remote file with your local version
  • Create other bash scripts to destroy and execute daemons in situ
  • Create a log directory for both our file's status, and as a way to ensure the program is working
  • Use AppleScript and Mail to notify us if the diff is not empty
  • Make a Mac app to automate it all
First, register for a Github token. Add a description but don't check any boxes - you only need the most basic auth.
To create a daemon on your Mac, you must generate a plist in User/Library/LaunchAgents/where all daemons live. In the plist XML, you'll specify the Label, Program (the path to our bash script), and StartInterval (the frequency with which you'll pull from Github). You also need to toggle KeepAlive > NetworkState and RunAtLoad to true.

Generated plist

RunAtLoad starts the daemon immediately but we can also start it from the terminal with launchctl load, and stop it with launchctl unload. So every 60 * 60 * 3 = 3 hours, the daemon runs the bash script at the path specified by "Program". This script uses curl to pull our special file from Github like so:

            if diff -q $SERVERFILE $LOCKFILE; then
            echo 'no change between server and lock in /lokal/master/ViewController.swift on' $TIMENOW >> $LOGFILE
            cat $SERVERFILE > $LOCKFILE
            if diff -q $SERVERFILE $LOCALFILE; then
            echo 'no change between server and local in /lokal/master/ViewController.swift on' $TIMENOW >> $LOGFILE
            sleep 1
            echo 'changes found in /lokal/master/ViewController.swift on' $TIMENOW >> $LOGFILE
            osascript $APPLESCRIPTFILE
            echo 'changes mailed to <your email>' >> $LOGFILE
Here you'll diff between the remote file and a lock file you'll create for your local file. Why a lock file? To prevent getting emails upon local changes. If the diff's not empty, you'll use osascript to run an AppleScript scpt file that emails you. The result is logged whether there are changes or not.
AppleScript uses Ruby-like syntax to automate pre-installed apps. It’s underused really. Your generated AppleScript file looks like:

            set recipientAddress to "<your email>"
            set theSubject to "A Super Important File Changed - lokal-master/ViewController.swift"
            set dateVar to the current date
            set theCombinedContent to "We caught this change on " & dateVar
            tell application "Mail"
                set theMessage to make new outgoing message with properties {subject:theSubject, content:theCombinedContent, visible:true}
                    tell theMessage
                        make new to recipient with properties {name:recipientAddress, address:recipientAddress}
                    end tell
            end tell
Here you specify a subject line, date, and body. Mail is then instructed to alloc-and-init the email object and send it immediately.
For the Mac app, an initial view controller creates the User - saving all user properties to Core Data except for the Github token which is saved to Keychain. After this, our app creates a directory in Documents with AppleScript, bash, log, and the tracked_files directories.

Mac app launch view

The Mac app has more view controllers for:
  • Tracked files - like a nerve center for managing my daemons
  • A daemon detail view allowing me to stop, restart, and destroy daemons
  • A change credentials view allowing me to update my github email and username
  • A new daemon view

List of currently running daemons and the New daemon UI

After creating a new daemon, you can check any running daemons by running sudo launchctl list in iTerm.

        I call the app Tracker, here's my favorite implimentation detail:

    static func runCommandWith(args:[String]) -> (output:String, exitCode:Int32){
            let pipe = Pipe()
            let task = Process()
            task.launchPath = "/bin/sh"
            task.arguments = args
            task.standardOutput = pipe
            let file = pipe.fileHandleForReading
            var output = AppStrings.runCommandError
            if let result = NSString(data: file.readDataToEndOfFile(), encoding: String.Encoding.utf8.rawValue) {
                output = result as String
            let status = task.terminationStatus
            return (output, status)
This Swift will execute any terminal command - use it to force some bash. You should really optional chain for the other two encodings here.
I use this app to catch code that passes code review and CI but still needs my attention.
To Wit
Curl is not secure. Use this app only on a personal machine and change permissions for the bash files containing your Github token to be read-writable only by you the user (use chmod 600).

I was going to open source the Mac app but it writes Github keys to your filesystem and, despite strict perms, SSL and Keychain usage, I want to be able to sleep at night.
Copyright © 2011–2018 Mike Leveton