<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Brian&apos;s Blog</title>
    <description>The personal blog of Brian John. You&apos;re likely to find information related to programming and random other musings here. All opinions are my own and do not represent the opinions of my employer or any other organization or person. Use of this website is strictly at your own risk.
</description>
    <link>https://blog.brianjohn.com/</link>
    <atom:link href="https://blog.brianjohn.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 01 Jul 2026 08:18:46 +0000</pubDate>
    <lastBuildDate>Wed, 01 Jul 2026 08:18:46 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>My OpenClaw Setup</title>
        <description>&lt;h2 id=&quot;why-am-i-writing-this&quot;&gt;Why am I writing this?&lt;/h2&gt;

&lt;p&gt;Like many people, I hesitated initially to install OpenClaw because I was worried about security, but then eventually gave in after I saw all of the cool stuff my friends were doing. The FOMO is strong with OpenClaw right now, and I was unable to resist it. I also &lt;em&gt;really&lt;/em&gt; wanted to build something useful for my wife with AI, so that she could experience the magic too. What I ended up with I think is fairly different from what I’ve seen other people do, so I decided this is a good opportunity to dust off the old blog and share something that is (hopefully) genuinely useful to others.&lt;/p&gt;

&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;

&lt;p&gt;My requirements were pretty simple:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I need 3 agents: one for me, one for my wife, and one for the family.&lt;/li&gt;
  &lt;li&gt;All communication needs to be over iMessage because that’s the channel that everyone uses and is most comfortable with. We could have used Telegram or Slack or something like that, but it wouldn’t feel as “real” as talking to an agent the same way we do to other humans.&lt;/li&gt;
  &lt;li&gt;The agents have to do actual useful stuff. Things like reading emails, drafting responses, writing code, checking homework, buying groceries, etc. Stuff that requires a level of access that makes me uncomfortable.&lt;/li&gt;
  &lt;li&gt;I have to be able to sleep at night.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;

&lt;p&gt;OpenClaw easily satisfies the first 3 requirements, it’s the 4th where things get complicated.&lt;/p&gt;

&lt;p&gt;What I’ve seen a lot of people do is to just buy a Mac Mini and deploy OpenClaw and let it YOLO to its heart’s content. I think that’s probably going to work out fine most of the time for most people. But I’m paranoid, and I’ve seen the horror stories. If OpenClaw starts deleting my family’s emails, the “magic” of AI can quickly turn to panic and rage.&lt;/p&gt;

&lt;p&gt;Another thing I’ve seen people do is to lock down the network on the OpenClaw gateway so that it can only talk to specific IP addresses or domains. This approach does make OpenClaw much safer, but there are a couple of reasons it doesn’t work great for me:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Certain domains, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com&lt;/code&gt;, can absolutely be vectors for prompt injection and exfiltrating sensitive data.&lt;/li&gt;
  &lt;li&gt;I wanted more granular control. For example, if I want my agent to be able to read and draft emails but not send them, giving the agent my IMAP password and locking the network down to my email provider domain isn’t going to prevent that.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I needed to do something different than what I was seeing other people do.&lt;/p&gt;

&lt;h2 id=&quot;imessage&quot;&gt;iMessage&lt;/h2&gt;

&lt;p&gt;I did some research and it appears that the only way you can use OpenClaw with iMessage is to run it on a macOS device (if there is another way please lmk!). So, begrudgingly, like everyone else I went out and bought a Mac Mini.&lt;/p&gt;

&lt;p&gt;OpenClaw has a really nice integration with a project called &lt;a href=&quot;https://bluebubbles.app/&quot;&gt;BlueBubbles&lt;/a&gt;, which basically acts like a web HTTP server and passes messages back and forth through iMessage via webhooks. It works incredibly well, but there was one problem: it only supports one set of webhooks for all iMessage conversations. This meant that I could only run a single OpenClaw gateway, which meant all of my agents had to have the same level of access. That wasn’t going to work for me, I didn’t want the possibility of the family agent doing things like reading my wife’s emails. So I did what any reasonable person would do, and &lt;a href=&quot;https://github.com/BlueBubblesApp/bluebubbles-server/pull/780&quot;&gt;forked the project to add the functionality I needed&lt;/a&gt;. With that patch, the OpenClaw on my Mac Mini can now support separate webhooks for separate iMessage conversations, which allows me to run isolated gateways for each agent. I’m crossing my fingers that one gets merged so I don’t have to keep manually patching it.&lt;/p&gt;

&lt;h2 id=&quot;access-controls&quot;&gt;Access Controls&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. Like I mentioned above, locking down the network was not going to be sufficient for my needs. I needed a way to be extremely granular with the permissions that I gave the agent. What I ended up coming up with was a “command proxy” concept. The command proxy would hold the credentials, and would accept requests from the agents, authenticate them using an agent-specific API key, and then perform the request. For example, the request might be to read my email, or to draft an email response. As long as the agent was authenticated it would perform the action and return the results. I also added the concept of commands that can be requested but that require human approval to be performed. This can be used for things that can’t easily be reverted like actually sending emails or making social media posts. One nice side-effect of this approach is that the agent has no access to the actual secrets.&lt;/p&gt;

&lt;p&gt;At a high level, the wiring looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB
    BB[&quot;BlueBubbles&amp;lt;br/&amp;gt;single iMessage bridge&quot;]
    CR[&quot;command proxy&amp;lt;br/&amp;gt;shared command API&quot;]

    subgraph OC[&quot;OpenClaw instances&quot;]
        ME[&quot;me&quot;]
        SPOUSE[&quot;spouse&quot;]
        FAMILY[&quot;family&quot;]
    end

    ME --&amp;gt;|&quot;REST API calls&quot;| BB
    BB -.-&amp;gt;|&quot;webhooks&quot;| ME
    SPOUSE --&amp;gt;|&quot;REST API calls&quot;| BB
    BB -.-&amp;gt;|&quot;webhooks&quot;| SPOUSE
    FAMILY --&amp;gt;|&quot;REST API calls&quot;| BB
    BB -.-&amp;gt;|&quot;webhooks&quot;| FAMILY

    ME --&amp;gt;|&quot;run command&quot;| CR
    SPOUSE --&amp;gt;|&quot;run command&quot;| CR
    FAMILY --&amp;gt;|&quot;run command&quot;| CR
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In practice, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command-proxy&lt;/code&gt; side is just a narrow set of HTTP commands. It’s kind of a “SOAP” API which I guess kind of sucks but agents seem to “get” it really well so idk. The important part is that each agent gets a small, explicit set of operations instead of direct access to passwords or long-lived secrets.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;OpenClaw instance&lt;/th&gt;
      &lt;th&gt;Example command-proxy call&lt;/th&gt;
      &lt;th&gt;Example payload&lt;/th&gt;
      &lt;th&gt;What it does&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;me&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /commands/pull-amazon-transactions&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;days&quot;:60}&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Pulls recent Amazon purchases and lines them up with unapproved YNAB transactions&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;me&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /commands/forgejo-pr-status?state=open&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;none&lt;/td&gt;
      &lt;td&gt;Checks whether an agent job already opened a PR&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spouse&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /commands/gmail/search?account=&amp;lt;elided&amp;gt;&amp;amp;query=newer_than:7d&amp;amp;limit=5&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;none&lt;/td&gt;
      &lt;td&gt;Searches Gmail without exposing the underlying Google credentials&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;family&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /commands/inbox?limit=10&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;none&lt;/td&gt;
      &lt;td&gt;Polls the shared Wilson inbox for recent messages&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spouse&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /commands/gmail/draft&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;subject&quot;:&quot;SOAP API&quot;,&quot;body&quot;:&quot;Why did you build a SOAP API those are so lame?&quot;,&quot;to&quot;:&quot;brian@&amp;lt;elided&amp;gt;&quot;}&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Puts an email draft in a gmail account&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;family&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /commands/calendar/events&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;title&quot;:&quot;Soccer practice&quot;,&quot;start&quot;:&quot;2026-04-15T18:00:00&quot;,&quot;duration&quot;:&quot;PT1H&quot;}&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Creates a calendar event through the command-proxy&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;how-is-it-all-working&quot;&gt;How is it all working?&lt;/h2&gt;

&lt;p&gt;It has only been a few weeks, but so far really well! There have been a couple of hiccups, like when BlueBubbles broke during an OpenClaw upgrade, but otherwise everything is working pretty great. Of course a tradeoff of all this is that if I want to add more capabilities for the agents that it’s a bit of extra work, but I’m fine with that for the privilege of being able to sleep at night.&lt;/p&gt;
</description>
        <pubDate>Thu, 14 May 2026 22:48:26 +0000</pubDate>
        <link>https://blog.brianjohn.com/my-openclaw-setup.html</link>
        <guid isPermaLink="true">https://blog.brianjohn.com/my-openclaw-setup.html</guid>
        
        
        <category>ai</category>
        
      </item>
    
      <item>
        <title>New Network Host Alert</title>
        <description>&lt;p&gt;For a while now I’ve been wanting to know when a new host joins my home network. I’ve (rightly) been accused of 
being a bit paranoid at times but as the sole administrator of our family’s network really I just like to know 
what is going on so that I’m prepared when I get complaints about e.g. the Netflix not working. In any case, a couple
weeks ago I decided to spend an hour whipping up a script to help me do this.&lt;/p&gt;

&lt;h1 id=&quot;the-script&quot;&gt;The Script&lt;/h1&gt;

&lt;p&gt;Here is the script.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env ruby&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Scans the network and sends an email whenever new hosts are detected. Uses MAC address to identify hosts.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Published here: https://gist.github.com/f1sherman/1c6c5e5f31c1fab33bbd1028c15d88e1&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;EMAIL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;you@example.com&apos;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;set&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;yaml&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;net/smtp&apos;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arpscan_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nslookup_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;WARNING - A New Host Has Joined The Network&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arpscan_info&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nslookup_info&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SMTP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;localhost&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;smtp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mail_message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOS&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
      From: Notify New Hosts &amp;lt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EMAIL&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&amp;gt;
      To: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EMAIL&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
      Subject: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;    EOS&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;smtp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send_message&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mail_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;EMAIL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;EMAIL&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;data_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;HOME&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.notify-new-hosts.yml&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;known_hosts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exists?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YAML&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;arpscan_output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%x(arp-scan --localnet)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;arp-scan failed: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arpscan_output&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;host_lines&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arpscan_output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^\d+\.\d+\.\d+\.\d+/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;host_lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each_with_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mac&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;known_hosts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mac&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;nslookup_output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%x(nslookup &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;send_alert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nslookup_output&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;known_hosts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mac&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;w&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;YAML&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dump&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;known_hosts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;why-ruby&quot;&gt;Why Ruby?&lt;/h1&gt;

&lt;p&gt;First you’ll probably notice that the script is written in Ruby. Lately for any non-trivial script I’ve had to write
I’ve chosen Ruby over Bash. It’s what I know best and almost any system I’m going to use comes with a relatively modern 
version out of the box. I find Ruby much more readable than Bash and then when I have to come back to tweak it I’m 
not wasting time brushing up on obscure Bash syntax.&lt;/p&gt;

&lt;h1 id=&quot;requirements&quot;&gt;Requirements&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;Modern Ruby version&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/royhills/arp-scan&quot;&gt;arp-scan&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Nslookup&quot;&gt;nslookup&lt;/a&gt; installed&lt;/li&gt;
  &lt;li&gt;A working local mail server listening on port 25&lt;/li&gt;
  &lt;li&gt;Some kind of scheduling software (e.g. cron)&lt;/li&gt;
  &lt;li&gt;Root privileges&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;how-it-works&quot;&gt;How it works&lt;/h1&gt;

&lt;p&gt;The heavy lifting is handled by &lt;a href=&quot;https://github.com/royhills/arp-scan&quot;&gt;arp-scan&lt;/a&gt;. This utility scans the local network
and reports any hosts that it finds. The result is parsed by the script and a set of known MAC addresses is serialized to a YAML 
file so that the script can remember hosts that have been seen before. When a new host is found, &lt;a href=&quot;https://en.wikipedia.org/wiki/Nslookup&quot;&gt;nslookup&lt;/a&gt; 
is used to try to find the hostname and then the script sends an email alert to a predefined email address.&lt;/p&gt;

&lt;h1 id=&quot;how-to-run-it&quot;&gt;How to run it&lt;/h1&gt;

&lt;p&gt;First, the script needs to be edited to add the email address. Then the script just needs to be scheduled (e.g. via 
cron) and run as root because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arp-scan&lt;/code&gt; needs to run as root. It could probably be pretty easily setup to run it as 
a normal user with some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudoers&lt;/code&gt; tricks etc. but I chose to keep it simple. Here is my crontab entry which runs the 
script once an hour (notice how I had to tweak the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt; so that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arp-scan&lt;/code&gt; could be found):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# m h  dom mon dow   command
0 * * * * PATH=/usr/bin:$PATH /path/to/notify-new-hosts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;thats-it&quot;&gt;That’s it!&lt;/h1&gt;

&lt;p&gt;I’ve been running this script for a couple weeks now. It’s caught me by surprise a couple times, usually when some 
device that hasn’t been used in a long time is powered up and joins the network. No bad surprises yet though, thankfully! 
Enjoy!&lt;/p&gt;
</description>
        <pubDate>Mon, 17 Apr 2017 20:00:00 +0000</pubDate>
        <link>https://blog.brianjohn.com/new-network-host-alert.html</link>
        <guid isPermaLink="true">https://blog.brianjohn.com/new-network-host-alert.html</guid>
        
        
        <category>howto</category>
        
      </item>
    
      <item>
        <title>Forwarding Ports in OS X/MacOS</title>
        <description>&lt;p&gt;Update: I have tested this with MacOS Sierra and it works for me!&lt;/p&gt;

&lt;p&gt;I forward local ports on my OS X machines using &lt;a href=&quot;https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/pfctl.8.html&quot;&gt;pfctl&lt;/a&gt; so that I can use traditional ports for HTTP and HTTPS with &lt;a href=&quot;https://www.vagrantup.com&quot;&gt;Vagrant&lt;/a&gt;
without having to start vagrant with root privleges. Recently when I updated to OS X El Capitan (10.11) I noticed that
my port forwards stopped working.&lt;/p&gt;

&lt;p&gt;Previously I had followed some of the steps in &lt;a href=&quot;https://gist.github.com/kujohn/7209628&quot;&gt;this gist&lt;/a&gt;, but all of those changes had
been wiped out with the upgrade. When I tried to re-apply them, a new feature called &lt;a href=&quot;https://en.wikipedia.org/wiki/System_Integrity_Protection&quot;&gt;System Integrity
Protection (SIP)&lt;/a&gt; prevented me from editing some necessary files. Also, since all of my
changes had been wiped out with this upgrade, I wanted to try to keep my changes out of existing system files as much as
possible in the hopes that they won’t be wiped out with the next upgrade.&lt;/p&gt;

&lt;h1 id=&quot;creating-an-anchor-file&quot;&gt;Creating an anchor file&lt;/h1&gt;

&lt;p&gt;The first file we need to add is an anchor file. This defines the ports we want to forward. Create the file in
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/pf.anchors/&amp;lt;CUSTOM NAME&amp;gt;&lt;/code&gt;. You can add one or many lines of the following format:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;rdr pass on lo0 inet proto tcp from any to any port &amp;lt;SOURCE PORT&amp;gt; -&amp;gt; 127.0.0.1 port &amp;lt;DESTINATION PORT&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;testing-the-anchor-file&quot;&gt;Testing the anchor file&lt;/h1&gt;

&lt;p&gt;To test the anchor file, run the following command.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;pfctl &lt;span class=&quot;nt&quot;&gt;-vnf&lt;/span&gt; /etc/pf.anchors/&amp;lt;CUSTOM NAME&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The ports won’t actually be forwarded yet, this just checks the validity of your anchor file. If you see output that looks something like the below, with no errors, you’re good.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;fctl: Use of -f option, could result in flushing of rules
present in the main ruleset added by the system at startup.
See /etc/pf.conf for further details.

rdr pass on lo0 inet proto tcp from any to any port = &amp;lt;SOURCE PORT&amp;gt; -&amp;gt; 127.0.0.1 port &amp;lt;DESTINATION PORT&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;creating-a-pfctl-config-file&quot;&gt;Creating a pfctl config file&lt;/h1&gt;

&lt;p&gt;Once your anchor file checks out, you need to add a pfctl config file. Create this file under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/pf-&amp;lt;CUSTOM
NAME&amp;gt;.conf&lt;/code&gt; and add the following contents.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;rdr-anchor &quot;forwarding&quot;
load anchor &quot;forwarding&quot; from &quot;/etc/pf.anchors/&amp;lt;CUSTOM NAME&amp;gt;&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;testing-the-config-file&quot;&gt;Testing the config file&lt;/h1&gt;

&lt;p&gt;You can start pfctl using the below command. This will forward the ports according to your rules.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;pfctl &lt;span class=&quot;nt&quot;&gt;-ef&lt;/span&gt; /etc/pf-&amp;lt;CUSTOM NAME&amp;gt;.conf&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To stop forwarding ports run the same command, replacing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e&lt;/code&gt; option with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;pfctl &lt;span class=&quot;nt&quot;&gt;-df&lt;/span&gt; /etc/pf-&amp;lt;CUSTOM NAME&amp;gt;.conf&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;forwarding-ports-at-startup&quot;&gt;Forwarding ports at startup&lt;/h1&gt;

&lt;p&gt;You can use the commands above to start port forwarding on demand if you wish, otherwise if (like me) you want to
forward ports automatically at startup you can create a &lt;a href=&quot;https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html&quot;&gt;launchctl plist file&lt;/a&gt;. Create a file under
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Library/LaunchDaemons/com.apple.pfctl-&amp;lt;CUSTOM NAME&amp;gt;.plist&lt;/code&gt; with the following contents:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;plist&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.apple.pfctl-&lt;span class=&quot;nt&quot;&gt;&amp;lt;CUSTOM&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Program&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/sbin/pfctl&lt;span class=&quot;nt&quot;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;pfctl&lt;span class=&quot;nt&quot;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;-e&lt;span class=&quot;nt&quot;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;-f&lt;span class=&quot;nt&quot;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/etc/pf-&lt;span class=&quot;nt&quot;&gt;&amp;lt;CUSTOM&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;.conf&lt;span class=&quot;nt&quot;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;RunAtLoad&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;KeepAlive&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;&amp;lt;false/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Add the file to startup using the following command:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;launchctl load &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /Library/LaunchDaemons/com.apple.pfctl-&amp;lt;CUSTOM NAME&amp;gt;.plist&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;example&quot;&gt;Example&lt;/h1&gt;

&lt;p&gt;You can find an example &lt;a href=&quot;https://gist.github.com/f1sherman/843f85ea8e2cbcdb40af&quot;&gt;here&lt;/a&gt; that forwards port 80 to 4000 and 443 to 4001.&lt;/p&gt;

&lt;h1 id=&quot;credits&quot;&gt;Credits&lt;/h1&gt;

&lt;p&gt;Hopefully this was helpful. Thanks to &lt;a href=&quot;https://gist.github.com/kujohn&quot;&gt;kujohn&lt;/a&gt; for creating the excellent &lt;a href=&quot;https://gist.github.com/kujohn/7209628&quot;&gt;gist&lt;/a&gt; that
worked so well for me previously.&lt;/p&gt;

</description>
        <pubDate>Sat, 10 Oct 2015 17:00:00 +0000</pubDate>
        <link>https://blog.brianjohn.com/forwarding-ports-in-os-x-el-capitan.html</link>
        <guid isPermaLink="true">https://blog.brianjohn.com/forwarding-ports-in-os-x-el-capitan.html</guid>
        
        
        <category>howto</category>
        
      </item>
    
      <item>
        <title>Automating backups of VMware Fusion VMs</title>
        <description>&lt;p&gt;I use VMware Fusion VMs for a bunch of stuff at home: &lt;a href=&quot;https://www.vagrantup.com&quot;&gt;Vagrant&lt;/a&gt; VMs, running a MythTV backend, random Linux hacking, anything you might need a Windows machine for, etc. &lt;a href=&quot;http://pubs.vmware.com/fusion-6/index.jsp?topic=%2Fcom.vmware.fusion.help.doc%2FGUID-4C90933D-A31F-4A56-B5CA-58D3AE6E93CF.html&quot;&gt;Snapshots&lt;/a&gt; work great for quick, local online backups, but sometimes you want a backup that you can store offsite. This post will walk you through how to script and automate the backup of a VMware Fusion Virtual Machine to a file that can be easily transferred. Before I get into this post I want to make it clear that while I am an employee of VMware, this is a personal blog and this post is being written on my behalf, not that of my employer.&lt;/p&gt;

&lt;h1 id=&quot;shutting-down-the-virtual-machine&quot;&gt;Shutting down the Virtual Machine&lt;/h1&gt;

&lt;p&gt;Per the &lt;a href=&quot;http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&amp;amp;cmd=displayKC&amp;amp;externalId=1013628&quot;&gt;official documentation&lt;/a&gt;, to take a full backup of a Fusion VM the .vmwarevm bundle must be copied while the VM is shut down. Since directory copies are non-atomic, this ensures that the VM is in a valid state. To do this, the &lt;a href=&quot;http://www.vmware.com/pdf/vix180_vmrun_command.pdf&quot;&gt;vmrun&lt;/a&gt; command can be used. You can find this command inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VMware Fusion.app&lt;/code&gt; bundle, typically at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Applications/VMware Fusion.app/Contents/Library/vmrun&lt;/code&gt;. You will also need to know the path to the VM’s vmx file.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/Applications/VMware Fusion.app/Contents/Library/vmrun&apos;&lt;/span&gt; stop &lt;span class=&quot;s1&quot;&gt;&apos;/path/to/file.vmx&apos;&lt;/span&gt; soft&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This command will fail if the VM is already shut down, so when putting this in a script (which we’ll be doing) it helps to check to make sure the VM is running before issuing it.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;VMRUN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/Applications/VMware Fusion.app/Contents/Library/vmrun&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;VMX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/path/to/file.vmx&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMRUN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; list | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--quiet&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMX&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMRUN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; stop &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMX&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; soft
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;creating-the-backup-file&quot;&gt;Creating the backup file&lt;/h1&gt;

&lt;p&gt;Once the VM is backed up, the .vmwarevm bundle is ready to be copied. This is a good time to compress the bundle to a file to make it more portable. Note: I typically like to have my backup directory on a network volume to keep me from losing both the backup and the VM if the main disk has issues.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/path/to/bundle.vmwarevm&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-czpf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/path/to/backup/directory/fusion-backup-&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; +%Y%m%d-%H%M%S&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.tar.gz&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Since the VM must remain shut down until these files are done being copied, another option to minimize VM downtime would be to rsync the .vmwarevm bundle to an intermediate directory, start the VM, then compress the bundle.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# The following command must be run as root&lt;/span&gt;
rsync &lt;span class=&quot;nt&quot;&gt;--archive&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--delete&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/path/to/bundle.vmwarevm/&apos;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/path/to/intermediate/directory&apos;&lt;/span&gt; 
&lt;span class=&quot;c&quot;&gt;# Start the VM (we’ll cover this in the next section)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/path/to/intermediate/directory&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-czpf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/path/to/backup/directory/fusion-backup-&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; +%Y%m%d-%H%M%S&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.tar.gz&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The downside of this is that it can be a lot harder on your disks - since I use SSDs and I don’t really care if my VM is down for a few extra minutes I stick with the former.&lt;/p&gt;

&lt;h1 id=&quot;starting-the-vm&quot;&gt;Starting the VM&lt;/h1&gt;

&lt;p&gt;Similar to shutting down, we can start the VM with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vmrun&lt;/code&gt; utility.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/Applications/VMware Fusion.app/Contents/Library/vmrun&apos;&lt;/span&gt; start &lt;span class=&quot;s1&quot;&gt;&apos;/path/to/file.vmx&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It can be helpful to use the trap function to ensure that the VM gets started up even if something else in the script causes it to exit.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;trap &lt;/span&gt;startvm EXIT
&lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;startvm &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;/Applications/VMware Fusion.app/Contents/Library/vmrun&apos;&lt;/span&gt; start &lt;span class=&quot;s1&quot;&gt;&apos;/path/to/file.vmx&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;pruning-stale-backups&quot;&gt;Pruning stale backups&lt;/h1&gt;

&lt;p&gt;In order to preserve space, it is a good idea to prune old backups that are no longer needed. Note: the below assumes that the backup directory is used exclusively for backups of this VM. If for whatever reason you have other files in the backup directory this will not work as expected and may delete files that you don’t want deleted.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/path/to/backup/directory&apos;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Delete all but the 3 most recent backups&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; 3&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;uniq&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; | xargs &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;wrapping-it-up-into-a-script&quot;&gt;Wrapping it up into a script&lt;/h1&gt;

&lt;p&gt;Here is the full script for the above. Note: you’ll need to make the script executable to run it (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chmod +x /path/to/backup-fusion.sh&lt;/code&gt;).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Fail this script if any commands fail (-e) or if any uninitialized variables are referenced (-u)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Set the following variables based on your environment&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;VMX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/path/to/file.vmx&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;BACKUP_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/path/to/backup/directory&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;VMRUN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/Applications/VMware Fusion.app/Contents/Library/vmrun&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;NUM_BACKUPS_TO_KEEP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3

&lt;span class=&quot;c&quot;&gt;# Make sure the VM gets started even if the backup fails&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;trap &lt;/span&gt;startvm EXIT

&lt;span class=&quot;nv&quot;&gt;START_VM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;startvm &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$START_VM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# only start the VM if it was running when we began the backup&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMRUN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; start &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMX&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# *** Shut down the VM if it is running ***&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMRUN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; list | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--quiet&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMX&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;START_VM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMRUN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; stop &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMX&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; soft
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# *** Create the backup file ***&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;VMWAREVM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;VMX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%/*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Remove the .vmx filename to get the full path to the .vmwarevm bundle&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$VMWAREVM&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-czpf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;/fusion-backup-&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; +%Y%m%d-%H%M%S&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;.tar.gz &lt;span class=&quot;nt&quot;&gt;--exclude&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;*.vmem&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# *** Delete all but the most recent backup files ***&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$NUM_BACKUPS_TO_KEEP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;uniq&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; | xargs &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;scheduling-backups&quot;&gt;Scheduling backups&lt;/h1&gt;

&lt;p&gt;There are 2 built-in options for scheduling jobs in OS X, launchd and cron. Per the &lt;a href=&quot;https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ScheduledJobs.html&quot;&gt;documentation&lt;/a&gt;, cron is deprecated so we’ll use launchd. To schedule a launchd task, create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.plist&lt;/code&gt; file in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/LaunchAgents/&lt;/code&gt;, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/LaunchAgents/com.vmware.backupfusion.plist&lt;/code&gt;. Check the &lt;a href=&quot;https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html#//apple_ref/doc/uid/TP40001762-104142&quot;&gt;documentation&lt;/a&gt; for more information on the format. The following example schedules a task to run every Monday at 3am.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;plist&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.vmware.backupfusion&lt;span class=&quot;nt&quot;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/path/to/backup-fusion.sh&lt;span class=&quot;nt&quot;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StartCalendarInterval&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;3&lt;span class=&quot;nt&quot;&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;

        &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;

        &lt;span class=&quot;nt&quot;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Weekday&lt;span class=&quot;nt&quot;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Run the following command to add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.plist&lt;/code&gt; file to the scheduler.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;launchctl load ~/Library/LaunchAgents/com.vmware.backupfusion.plist&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;restoring-from-backup&quot;&gt;Restoring from backup&lt;/h1&gt;

&lt;p&gt;Restoring from backup is pretty easy. These are just files so you can extract them wherever you want, then point Fusion at them via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File --&amp;gt; Open...&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /path/to/target/directory
&lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-xzvf&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/path/to/backup/file.tar.gz&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
        <pubDate>Sun, 11 Jan 2015 17:00:00 +0000</pubDate>
        <link>https://blog.brianjohn.com/automating-backups-of-vmware-fusion-vms.html</link>
        <guid isPermaLink="true">https://blog.brianjohn.com/automating-backups-of-vmware-fusion-vms.html</guid>
        
        
        <category>howto</category>
        
      </item>
    
      <item>
        <title>Setting up my blog using Jekyll and GitHub Pages</title>
        <description>&lt;p&gt;Last week I was listening to a recent &lt;a href=&quot;http://rubyrogues.com&quot;&gt;Ruby Rogues&lt;/a&gt; podcast on &lt;a href=&quot;http://devchat.tv/ruby-rogues/187-marketing-yourself-as-a-software-developer-with-john-sonmez&quot;&gt;Marketing Yourself as a Software Developer&lt;/a&gt;. During this episode &lt;a href=&quot;https://twitter.com/jsonmez&quot;&gt;John Sonmez&lt;/a&gt;, who runs the site &lt;a href=&quot;http://simpleprogrammer.com&quot;&gt;Simple Programmer&lt;/a&gt; gives some great reasons as to why you should market yourself as a software developer, as well as some excellent ways to do that. One of the ideas was to create a blog. Sounds like a great idea to me. So, predictably, my first post is about creating my blog.&lt;/p&gt;

&lt;h1 id=&quot;github-pages&quot;&gt;GitHub Pages&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt; is a good choice for hosting a blog for a couple of reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Many developers (myself included) use GitHub daily and they will host your blog for free, so no messing around with a VPS or WordPress or anything like that&lt;/li&gt;
  &lt;li&gt;They have built-in &lt;a href=&quot;http://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt; support, which is a great platform for blogging&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The initial setup with Pages was pretty simple and only took a few minutes. You can read the guide &lt;a href=&quot;https://pages.github.com/&quot;&gt;here&lt;/a&gt; so I won’t bore you with the details. After pushing the initial commit to my repository and waiting a few minutes for the changes to show up, I had a working blog at f1sherman.github.io.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/hello-world.png&quot; alt=&quot;Hello World&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;custom-domain&quot;&gt;Custom Domain&lt;/h1&gt;

&lt;p&gt;Hosting your blog on your personal domain is a nice way to further personalize your blog. This is pretty easy and you can find instructions &lt;a href=&quot;https://help.github.com/articles/setting-up-a-custom-domain-with-github-pages/&quot;&gt;here&lt;/a&gt;. Basically it involves dropping a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CNAME&lt;/code&gt; file into your repository and setting up the CNAME with your DNS provider.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/cname.png&quot; alt=&quot;CNAME&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;jekyll&quot;&gt;Jekyll&lt;/h1&gt;

&lt;p&gt;Jekyll seemed like a good choice for a blogging platform because it is supported natively by GitHub Pages and supports &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt;, which I already use often. Initial setup with Jekyll was a combination of the steps listed in the &lt;a href=&quot;https://help.github.com/articles/using-jekyll-with-pages/&quot;&gt;GitHub Pages documentation&lt;/a&gt; as well as the &lt;a href=&quot;http://jekyllrb.com/docs/home/&quot;&gt;Jekyll documentation&lt;/a&gt;. After a few minutes I had some scaffolding to build on.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/scaffolding.png&quot; alt=&quot;Scaffolding&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;theme&quot;&gt;Theme&lt;/h1&gt;

&lt;p&gt;I decided to stick with the base Jekyll theme for now, mostly because I wanted to start generating content as soon as possible and this seemed like it could be a rabbit hole. At some point I’ll probably rip it out and try something more fancy.&lt;/p&gt;

&lt;h1 id=&quot;google-analytics&quot;&gt;Google Analytics&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;http://www.google.com/analytics/&quot;&gt;Google Analytics&lt;/a&gt; is a great, free way to track the popularity of your site. I created a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_includes/google-analytics.html&lt;/code&gt; fragment with the GA tracking code and added that to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_layouts/head.html&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;SNIP&amp;gt;&lt;/span&gt;
  {% include google-analytics.html %}
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;social-media-icons&quot;&gt;Social Media Icons&lt;/h1&gt;

&lt;p&gt;I wanted to add some social media links to my footer (in addition to the github and twitter links that are included with jekyll), but had a tough time finding SVGs that would fit in with the default theme. Eventually I stumbled upon &lt;a href=&quot;http://codepen.io/ruandre/pen/howFi&quot;&gt;this set of svgs&lt;/a&gt; which was helpful not only for the SVGs themselves, but also for the idea to get icons from &lt;a href=&quot;http://iconmonstr.com&quot;&gt;iconmonstr&lt;/a&gt; and compress them using &lt;a href=&quot;http://petercollingridge.appspot.com/svg-editor&quot;&gt;this SVG Editor&lt;/a&gt; for icons that weren’t included. See the &lt;a href=&quot;https://github.com/f1sherman/f1sherman.github.io/blob/1544d0076db82b050951f08fc7c70bb4f11ccd14/_includes/footer.html#L17-L68&quot;&gt;markup on github&lt;/a&gt; for the code.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/social-media-icons.png&quot; alt=&quot;Social Media Icons&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;comments&quot;&gt;Comments&lt;/h1&gt;

&lt;p&gt;I decided to use &lt;a href=&quot;https://disqus.com/&quot;&gt;disqus&lt;/a&gt; for commenting because it was familiar and easy to setup. I used the wizard on the disqus site to set up the commenting account, then added the code that it provided to &lt;a href=&quot;https://github.com/f1sherman/f1sherman.github.io/blob/f79093961e1eb5b434941d1b286f6baa0da381d3/_includes/disqus.html&quot;&gt;_includes/disqus.html&lt;/a&gt; and included it in &lt;a href=&quot;https://github.com/f1sherman/f1sherman.github.io/blob/f79093961e1eb5b434941d1b286f6baa0da381d3/_layouts/post.html#L17&quot;&gt;_layouts/post.html&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/disqus.png&quot; alt=&quot;Disqus Comments&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;about-page&quot;&gt;About Page&lt;/h1&gt;

&lt;p&gt;I &lt;a href=&quot;https://github.com/f1sherman/f1sherman.github.io/commit/125b5580d79988efc803772ed3a8e314e099ed46&quot;&gt;removed&lt;/a&gt; the About page that Jekyll creates because I wasn’t ready to tackle this yet and I wanted to get the site up as soon as possible. I may add this back later.&lt;/p&gt;

&lt;h1 id=&quot;tweet-button&quot;&gt;Tweet Button&lt;/h1&gt;

&lt;p&gt;Adding the &lt;a href=&quot;https://about.twitter.com/resources/buttons#tweet&quot;&gt;Tweet button resource from Twitter&lt;/a&gt; to the post layout will make it easy for readers to share my posts.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/tweet-button.png&quot; alt=&quot;Tweet Button&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;sitemaprobotstxt&quot;&gt;Sitemap/robots.txt&lt;/h1&gt;

&lt;p&gt;It’s a good idea to have a Sitemap and robots.txt to make it easier for search engines to crawl your site. I followed the &lt;a href=&quot;https://help.github.com/articles/sitemaps-for-github-pages/&quot;&gt;Github Pages Sitemap instructions&lt;/a&gt; to get a sitemap setup, then added a robots.txt file pointing to it.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;User-agent: *
Disallow:
Sitemap: http://blog.brianjohn.com/sitemap.xml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;submit-to-google-index&quot;&gt;Submit to Google index&lt;/h1&gt;

&lt;p&gt;I used &lt;a href=&quot;https://www.google.com/webmasters/tools/home?hl=en&quot;&gt;Google Webmaster Tools&lt;/a&gt; to submit my site to Google’s search index to make sure my site shows up in Google searches. I’ve seen this take up to a week to take affect, so don’t expect it to show up right away.&lt;/p&gt;

&lt;h1 id=&quot;redirect-from-brianjohncom-and-www&quot;&gt;Redirect from brianjohn.com and www&lt;/h1&gt;

&lt;p&gt;I wasn’t using my domain for anything important, so I set up brianjohn.com and www.brianjohn.com to redirect to blog.brianjohn.com in my DNS provider.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/redirect.png&quot; alt=&quot;Redirect&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;updating-social-media-sites&quot;&gt;Updating Social Media Sites&lt;/h1&gt;

&lt;p&gt;Many social media sites allow you to list your site in your profile. I went through as many sites as I could think of and added my blog. Here is a list of the ones I could think of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Facebook&lt;/li&gt;
  &lt;li&gt;Github&lt;/li&gt;
  &lt;li&gt;Google+&lt;/li&gt;
  &lt;li&gt;LinkedIn&lt;/li&gt;
  &lt;li&gt;Stack Overflow&lt;/li&gt;
  &lt;li&gt;Twitter&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;thats-it&quot;&gt;That’s It!&lt;/h1&gt;

&lt;p&gt;Wow, that felt like a lot of work but I had a lot of fun putting this together. Hopefully other folks with blogs (or that want to set one up) find this useful. Questions? Any other related tips or info? Leave a comment below! Thanks for reading!&lt;/p&gt;

</description>
        <pubDate>Thu, 01 Jan 2015 18:04:49 +0000</pubDate>
        <link>https://blog.brianjohn.com/setting-up-my-blog.html</link>
        <guid isPermaLink="true">https://blog.brianjohn.com/setting-up-my-blog.html</guid>
        
        
        <category>howto</category>
        
      </item>
    
  </channel>
</rss>
