<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Troy's Random Ramblings]]></title><description><![CDATA[Stuff about stuff, some of it actually useful. Maybe.]]></description><link>https://troy.dack.com.au/</link><image><url>https://troy.dack.com.au/favicon.png</url><title>Troy&apos;s Random Ramblings</title><link>https://troy.dack.com.au/</link></image><generator>Ghost 3.38</generator><lastBuildDate>Thu, 25 Feb 2021 07:49:00 GMT</lastBuildDate><atom:link href="https://troy.dack.com.au/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Computer longevity....]]></title><description><![CDATA[<p>So it appears that just over 5 years ago I put together a computer as my old one had finally given up on life. Specs available <a href="https://troy.dack.com.au/hackintosh-build">here</a>.</p><p>Well, 5 years later that same machine is still going well. It's had a couple of upgrades:</p><ul><li>an extra 16GB of RAM, for</li></ul>]]></description><link>https://troy.dack.com.au/computer-longevity/</link><guid isPermaLink="false">5e467f6f07dc3a76361fb925</guid><category><![CDATA[linux]]></category><category><![CDATA[computers]]></category><category><![CDATA[apple]]></category><category><![CDATA[hackintosh]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Fri, 14 Feb 2020 11:16:53 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1513366976578-e01c21fb9c76?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1513366976578-e01c21fb9c76?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Computer longevity...."><p>So it appears that just over 5 years ago I put together a computer as my old one had finally given up on life. Specs available <a href="https://troy.dack.com.au/hackintosh-build">here</a>.</p><p>Well, 5 years later that same machine is still going well. It's had a couple of upgrades:</p><ul><li>an extra 16GB of RAM, for a total of 32GB</li><li>1 x 480GB and 1 x 240GB SSDs to replace a couple of HDDs. These hold OS X and Windows respectively</li><li>A Radeon RX 580 8 GB video card driving 2 x 24" monitors and 1 x 27" monitor</li><li>macOS 10.15.3 (and all versions from 10.10 through to 10.14 at one stage or another)</li></ul><p>The Radeon is actually the third video card. The original NVidia GT740 was replaced with a GTX-1080 Ti, but when Apple decided to go Radeon again and wouldn't play nicely with NVidia (or vice-versa) the NVidia Web drivers wouldn't work with macOS 10.15. Plus it was about time for a new video card anyway.</p><p>Now the system has the following OS's configured to multi-boot using Clover V2.0 r5100</p><ul><li>Windows 10</li><li>macOS 10.15.3</li><li>Debian Buster</li></ul>]]></content:encoded></item><item><title><![CDATA[This is a test]]></title><description><![CDATA[<p>And here's another test</p><!--kg-card-begin: markdown--><p><img src="https://images.unsplash.com/photo-1558981359-219d6364c9c8?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" alt><br>
<small>Photo by <a href="https://unsplash.com/@harleydavidson?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Harley-Davidson</a> / <a href="https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Unsplash</a></small></p>
<!--kg-card-end: markdown-->]]></description><link>https://troy.dack.com.au/this-is-a-test/</link><guid isPermaLink="false">5dda7421615ace33b9036121</guid><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Sun, 24 Nov 2019 12:43:21 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1558980664-4d79c6e77b93?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1558980664-4d79c6e77b93?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="This is a test"><p>And here's another test</p><!--kg-card-begin: markdown--><p><img src="https://images.unsplash.com/photo-1558981359-219d6364c9c8?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" alt="This is a test"><br>
<small>Photo by <a href="https://unsplash.com/@harleydavidson?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Harley-Davidson</a> / <a href="https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Unsplash</a></small></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Custom Linux kernel for NextThing Co. C.H.I.P.]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Quick and dirty kernel build for the Next Thing Co's C.H.I.P. $9 single board computer.</p>
<p>This builds the kernel on the C.H.I.P. itself.</p>
<p>Alternatively grab this <a href="https://gist.github.com/tdack/5b87a20225d239a6240e8f3964b8d290">gist</a> and put it in a directory of it's own.  It will pull down the sources and build</p>]]></description><link>https://troy.dack.com.au/custom-linux-kernel-for-nextthing-co-c-h-i-p-2/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40cc</guid><category><![CDATA[linux]]></category><category><![CDATA[IoT]]></category><category><![CDATA[C.H.I.P.]]></category><category><![CDATA[embedded]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Tue, 12 Jul 2016 03:37:56 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Quick and dirty kernel build for the Next Thing Co's C.H.I.P. $9 single board computer.</p>
<p>This builds the kernel on the C.H.I.P. itself.</p>
<p>Alternatively grab this <a href="https://gist.github.com/tdack/5b87a20225d239a6240e8f3964b8d290">gist</a> and put it in a directory of it's own.  It will pull down the sources and build everything you need. To cross compile on a Debian or Ubuntu system <code>apt-get install crossbuild-essentials-armhf</code> first.</p>
<h1 id="installdevtools">Install dev tools</h1>
<pre><code>sudo apt-get update
sudo apt-get upgrade
sudo apt-get install build-essential zlib1g-dev libncurses5-dev loop
sudo apt-get clean
</code></pre>
<h1 id="kernelbuild">Kernel Build</h1>
<pre><code># Create a directory to hold the source files
cd ~
mkdir src

# Clone the NTC Kernel source
cd src
git clone --single-branch --branch debian/4.4.11-ntc-1 --depth 1 https://github.com/NextThingCo/CHIP-linux.git

# Configure the kernel
cp /boot/config-4.4.11-ntc CHIP-linux/.config
cd CHIP-linux
make oldconfig

# Add any extra modules you might want and kernel version here
make menuconf

# Build the kernel (this will take a loooong time)
make

# Install the modules
sudo make install modules_install
</code></pre>
<h1 id="rtl8723bswirelessbuild">RTL8723BS Wireless Build</h1>
<pre><code># Clone the source code
cd ~/src
git clone --single-branch --branch debian https://github.com/NextThingCo/RTL8723BS.git

# Apply patches
cd RTL8723BS/
for f in debian/patches/0*; do echo &quot;Applying ${f}&quot;; patch -p 1 &lt; $f; done

# Build the source
make CONFIG_PLATFORM_ARM_SUNxI=y -C /home/chip/src/CHIP-linux/ M=$PWD CONFIG_RTL8723BS=m

# Install the module
sudo make -C /home/chip/src/CHIP-linux/ M=$PWD modules_install
</code></pre>
<h1 id="installkernel">Install kernel</h1>
<p>Name the kernel file with the version that you built. If you added an extra version information in the kernel config then include this as well.</p>
<h2 id="copythenewkerneltoboot">Copy the new kernel to /boot</h2>
<pre><code>export KV=4.4.11-td1+
cd ~/src/CHIP-linux
sudo cp arch/arm/boot/zImage /boot/vmlinuz-$KV
sudo cp .config /boot/config-$KV
sudo cp System.map /boot/System.map-$KV
</code></pre>
<h2 id="copythedevicetreefilestoboot">Copy the device tree files to /boot</h2>
<pre><code>export KV=4.4.11-td1+
sudo mkdir /boot/dtbs/$KV
sudo cp arch/arm/boot/dts/sun5i-r8-chip.dtb /boot/dtbs/$KV/
</code></pre>
<h2 id="createsymlinks">Create symlinks</h2>
<p>This makes it easier to change your kernel, all you need to do is change the symlink that zImage points to.</p>
<pre><code>cd /boot

# Backup existing kernel and other files
for F in config zImage System.map dtb; do sudo mv $F $F-$(uname -r); done
sudo mkdir dtbs/$(uname -r)
sudo mv sun5i-r8-chip.dtb dtbs/$(uname -r)/

# Create links to new kernel and other files
export KV=4.4.11-td1+
for F in config zImage System.map; do sudo ln -sf $F-$KV $F; done
sudo ln -sf sun5i-r8-chip.dtb dtbs/$KV/sun5i-r8-chip.dtb
</code></pre>
<h3 id="checklinks">Check links</h3>
<pre><code>chip@chip-one:/boot$ ls -la config System.map zImage sun5i-r8-chip.dtb
lrwxrwxrwx 1 root root 18 Jul  5 21:33 config -&gt; config-4.4.11-td1+
lrwxrwxrwx 1 root root 15 Jul  5 21:32 sun5i-r8-chip.dtb -&gt; dtb-4.4.11-td1+
lrwxrwxrwx 1 root root 22 Jul  5 21:33 System.map -&gt; System.map-4.4.11-td1+
lrwxrwxrwx 1 root root 19 Jul  5 21:31 zImage -&gt; vmlinuz-4.4.11-td1+
</code></pre>
<h1 id="reboot">Reboot</h1>
<pre><code>systemctl reboot
</code></pre>
<p>Your C.H.I.P. should now reboot and be running your new kernel</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Custom Handlebars Helpers for Ghost]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Plugins, or apps, are still in their infancy for the Ghost blogging platform, as such people have resorted to adding custom Handlebars helpers using a variety of hacks.</p>
<p>One of these is shown at <a href="https://apatchofcode.com/adding-custom-handlebars-for-ghost-equals-awesome/">https://apatchofcode.com/adding-custom-handlebars-for-ghost-equals-awesome/</a></p>
<p>Here is an alternative implementation that makes use of the fledgling Ghost</p>]]></description><link>https://troy.dack.com.au/custom-handlebars-helpers-for-ghost/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40cb</guid><category><![CDATA[ghost]]></category><category><![CDATA[blog]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Tue, 14 Jun 2016 01:52:47 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2016/06/image.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2016/06/image.png" alt="Custom Handlebars Helpers for Ghost"><p>Plugins, or apps, are still in their infancy for the Ghost blogging platform, as such people have resorted to adding custom Handlebars helpers using a variety of hacks.</p>
<p>One of these is shown at <a href="https://apatchofcode.com/adding-custom-handlebars-for-ghost-equals-awesome/">https://apatchofcode.com/adding-custom-handlebars-for-ghost-equals-awesome/</a></p>
<p>Here is an alternative implementation that makes use of the fledgling Ghost Apps feature.</p>
<h2 id="installation">Installation</h2>
<p>Create a new directory in <code>contents/apps</code></p>
<p>eg:</p>
<blockquote>
<p>mkdir contents/apps/myhelpers</p>
</blockquote>
<p>Place <code>package.json</code> and <code>index.js</code> in the directory.</p>
<p>To enable the app you need to manually add it to the database as described at: <a href="https://github.com/TryGhost/Ghost/wiki/Apps-Getting-Started-for-Ghost-Devs">https://github.com/TryGhost/Ghost/wiki/Apps-Getting-Started-for-Ghost-Devs</a></p>
<p>Restart ghost</p>
<h2 id="usage">Usage</h2>
<p>Simply use the new helper in your template, eg:</p>
<pre><code>{{#compare 'apples' '===' 'oranges'}}
    &lt;p&gt;Totally not the case.&lt;p&gt;
{{/compare}}
{{#compare 'apples' 'typeof' 'string'}}
    &lt;p&gt;I think we're onto something.&lt;p&gt;
{{/compare}}
</code></pre>
<h3 id="packagejson">package.json</h3>
<pre><code>{
  &quot;name&quot;:&quot;my-helpers&quot;,
  &quot;version&quot;: &quot;0.0.1&quot;,
  &quot;dependencies&quot;: {
    &quot;ghost-app&quot;: &quot;0.0.2&quot;
  },
  &quot;ghost&quot;: {
      &quot;permissions&quot;: {
          &quot;helpers&quot;: [&quot;compare&quot;]
      }
  }
}
</code></pre>
<h3 id="indexjs">index.js</h3>
<pre><code>var App = require('ghost-app'),
    myHelpers;

myHelpers = App.extend({

    install: function() {},

    uninstall: function() {},

    activate: function() {
        this.app.helpers.register('compare', this.compareHelper)
    },

    deactivate: function() {},

    compareHelper: function (v1, operator, v2, options) {
        switch (operator) {
            case '==':
                return (v1 == v2)  ? options.fn(this) : options.inverse(this);
            case '===':
                return (v1 === v2) ? options.fn(this) : options.inverse(this);
            case '!=':
                return (v1 != v2)  ? options.fn(this) : options.inverse(this);
            case '!==':
                return (v1 !== v2) ? options.fn(this) : options.inverse(this);
            case '&lt;':
                return (v1 &lt; v2)   ? options.fn(this) : options.inverse(this);
            case '&lt;=':
                return (v1 &lt;= v2)  ? options.fn(this) : options.inverse(this);
            case '&gt;':
                return (v1 &gt; v2)   ? options.fn(this) : options.inverse(this);
            case '&gt;=':
                return (v1 &gt;= v2)  ? options.fn(this) : options.inverse(this);
            case '&amp;&amp;':
                return (v1 &amp;&amp; v2)  ? options.fn(this) : options.inverse(this);
            case '||':
                return (v1 || v2)  ? options.fn(this) : options.inverse(this);
            case 'typeof':
                return (typeof v1 == v2)  ? options.fn(this) : options.inverse(this);
            default:
                return options.inverse(this);
        }
    }
});

module.exports = myHelpers;
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Mosquitto MQTT]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I've recently started experimenting with MQTT, in particular the Mosquito broker. After quite a bit of trial &amp; error and plenty of man page reading I've managed to get things working nicely.</p>
<p>Features enabled:</p>
<ul>
<li>SSL/TLS connections</li>
<li>Websockets proxied through Apache (ws:// and wss://)</li>
<li>ACLs for anonymous access</li>
<li>ACLs for</li></ul>]]></description><link>https://troy.dack.com.au/mosquitto-mqtt/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40ca</guid><category><![CDATA[linux]]></category><category><![CDATA[IoT]]></category><category><![CDATA[MQTT]]></category><category><![CDATA[command line]]></category><category><![CDATA[admin]]></category><category><![CDATA[apache2]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Thu, 04 Feb 2016 11:29:03 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2016/02/Screen-Shot-2016-02-04-at-22-07-39--1.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2016/02/Screen-Shot-2016-02-04-at-22-07-39--1.png" alt="Mosquitto MQTT"><p>I've recently started experimenting with MQTT, in particular the Mosquito broker. After quite a bit of trial &amp; error and plenty of man page reading I've managed to get things working nicely.</p>
<p>Features enabled:</p>
<ul>
<li>SSL/TLS connections</li>
<li>Websockets proxied through Apache (ws:// and wss://)</li>
<li>ACLs for anonymous access</li>
<li>ACLs for username/password access</li>
<li>Bridging selected local topics to the <a href="http://io.adafruit.com">io.adafruit.com</a> service</li>
</ul>
<p>A visualisation of the topics I currently have is shown followed by configuration sections for the various options.</p>
<p>If you want to secure your connection using SSL/TLS then I recommend <a href="https://letsencrypt.org">Let's Encrypt</a>, to install the certificates see <a href="http://mosquitto.org/2015/12/using-lets-encrypt-certificates-with-mosquitto/">here</a></p>
<p><img src="https://troy.dack.com.au/content/images/2016/02/Screen-Shot-2016-02-04-at-22-07-39-.png" alt="Mosquitto MQTT"></p>
<blockquote>
<p>All configuration is based on an Ubuntu installation of Mosquitto.</p>
</blockquote>
<h3 id="basicserverconfigetcmosquittomosquittoconf">Basic Server Config <code>/etc/mosquitto/mosquitto.conf</code></h3>
<pre><code>pid_file /var/run/mosquitto.pid

persistence true
persistence_location /var/lib/mosquitto/

log_dest file /var/log/mosquitto/mosquitto.log
#log_type all

acl_file /etc/mosquitto/acl.conf
password_file /etc/mosquitto/passwords

include_dir /etc/mosquitto/conf.d
</code></pre>
<h3 id="aclsetcmosquittoaclconf">ACLs <code>/etc/mosquitto/acl.conf</code></h3>
<pre><code># only allow anonymous users specific access
topic read #
topic read $SYS/broker/messages/#

# Allow user web to read anywhere
user web
topic read #
topic read $SYS/#

# Allow user sensor to write anywhere
user sensor
topic readwrite #
</code></pre>
<p>Generate your password file using <code>mosquitto_passwd</code>.</p>
<p>Set permissions and group on the password file as:</p>
<p><code>-rw-r----- 1 root mosquitto 229 Feb  4 20:18 /etc/mosquitto/passwords</code></p>
<p>ie: <code>u+rw,g+r</code> for <code>root:mosquitto</code></p>
<h3 id="secureinsecuremqttconfigetcmosquittoconfd01defaultconf">Secure/Insecure MQTT config <code>/etc/mosquitto/conf.d/01-default.conf</code></h3>
<pre><code>listener 1883
listener 8883
cafile /path/to/chain-ca.pem
certfile /path/to/cert.pem
keyfile /path/to/privkey.pem
</code></pre>
<h3 id="secureinsecurewebsocketsconfigetcmosquittoconfd02websocketsconf">Secure/Insecure Websockets config <code>/etc/mosquitto/conf.d/02-websockets.conf</code></h3>
<pre><code>listener 8080
protocol websockets

listener 8083
protocol websockets
cafile /path/to/chain-ca.pem
certfile /path/to/cert.pem
keyfile /path/to/privkey.pem
</code></pre>
<h3 id="bridgeconfigetcmosquittoconfd03adafruitbridgeconf">Bridge config <code>/etc/mosquitto/conf.d/03-adafruit-bridge.conf</code></h3>
<pre><code>connection bridge_adafruit
address io.adafruit.com:8883
remote_username &lt;username&gt;
remote_password &lt;aio key&gt;
start_type automatic
bridge_protocol_version mqttv311
bridge_capath /etc/ssl/certs/

notifications false
try_private false

topic throttle in 0 adafruit.io/ &lt;username&gt;/feeds/
topic welcome-feed both 0 adafruit.io/ &lt;username&gt;/feeds/
</code></pre>
<h3 id="apacheconfigforproxying">Apache config for proxying</h3>
<p>Place this within your <code>&lt;Virtualhost *:80&gt;</code> or <code>&lt;Virtualhost *:443&gt;</code> directive.</p>
<p>If your VHost is a secure one then the SSL/TLS websocket connection will be handled by Apache and transparently proxied to the local MQTT broker.  All communication between the browser and Apache will be secured.</p>
<p>This requires the <code>mod_proxy_wstunnel</code> module to be enabled.</p>
<pre><code>        &lt;Location &quot;/mqtt&quot;&gt;
                ProxyPreserveHost On
                ProxyPass ws://localhost:8080/mqtt
                ProxyPassReverse ws://localhost:8080/mqtt
        &lt;/Location&gt;
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[OpenVPN Client/Server config for iOS devices]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>This should get you started configuring a Linux OpenVPN server, creating your own certificate authority and generating certificates and profiles for use with Apple iOS devices like the iPad and iPhone.</p>
<p>The following script will generate a .ovpn profile that can be imported straight into the OpenVPN iOS app.</p>
<blockquote>
<p>You</p></blockquote>]]></description><link>https://troy.dack.com.au/openvpn-client/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40c9</guid><category><![CDATA[linux]]></category><category><![CDATA[command line]]></category><category><![CDATA[vpn]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Mon, 01 Feb 2016 04:44:22 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2016/02/encryption.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2016/02/encryption.jpg" alt="OpenVPN Client/Server config for iOS devices"><p>This should get you started configuring a Linux OpenVPN server, creating your own certificate authority and generating certificates and profiles for use with Apple iOS devices like the iPad and iPhone.</p>
<p>The following script will generate a .ovpn profile that can be imported straight into the OpenVPN iOS app.</p>
<blockquote>
<p>You will need to perform the steps as the <code>root</code> user or execute the commands using <code>sudo</code>.</p>
</blockquote>
<h4 id="createcertificateauthorityandclientcerts">Create certificate authority and client certs</h4>
<p>For this we'll use <code>easy_rsa</code> that should be installed as part of your distributions OpenVPN install.</p>
<blockquote>
<p><em>These instructions were written with the server being an Ubuntu installation, paths may need to be adjusted to suit your distribution.</em></p>
</blockquote>
<pre><code>mkdir /etc/openvpn/easy-rsa/
cp -r /usr/share/easy-rsa/* /etc/openvpn/easy-rsa/
</code></pre>
<p>Next, edit <code>/etc/openvpn/easy-rsa/vars</code> adjusting the following to your environment:</p>
<pre><code>export KEY_COUNTRY=&quot;US&quot;
export KEY_PROVINCE=&quot;NC&quot;
export KEY_CITY=&quot;Winston-Salem&quot;
export KEY_ORG=&quot;Example Company&quot;
export KEY_EMAIL=&quot;steve@example.com&quot;
export KEY_CN=MyVPN
export KEY_NAME=MyVPN
export KEY_OU=MyVPN
</code></pre>
<p>Enter the following to generate the master Certificate Authority (CA) certificate and key:</p>
<pre><code>cd /etc/openvpn/easy-rsa/
source vars
./clean-all
./build-ca
</code></pre>
<h5 id="servercertificates">Server Certificates</h5>
<p>Next, we will generate a certificate and private key for the server:</p>
<pre><code>./build-key-server myservername
</code></pre>
<p>As in the previous step, most parameters can be defaulted. Two other queries require positive responses, &quot;Sign the certificate? [y/n]&quot; and &quot;1 out of 1 certificate requests certified, commit? [y/n]&quot;.</p>
<p>Diffie Hellman parameters must be generated for the OpenVPN server:</p>
<pre><code>./build-dh
</code></pre>
<p>All certificates and keys have been generated in the subdirectory <code>keys/</code>. Common practice is to copy them to <code>/etc/openvpn/</code>:</p>
<pre><code>cd keys/
cp myservername.crt myservername.key ca.crt dh2048.pem /etc/openvpn/
</code></pre>
<h5 id="clientcertificates">Client Certificates</h5>
<p>The VPN client will also need a certificate to authenticate itself to the server. Usually you create a different certificate for each client. To create the certificate, enter the following in a terminal while being user root:</p>
<pre><code>cd /etc/openvpn/easy-rsa/
source vars
./build-key client1
</code></pre>
<p>Now that you have generated server and client certificates its time to configure the server</p>
<h4 id="etcopenvpnserverconf"><code>/etc/openvpn/server.conf</code></h4>
<pre><code>port 1194
proto udp
dev tun
# Server CA and certificate
ca ca.crt
cert myservername.crt
dh dh2048.pem
# Hand out IPs 192.168.1.67-79
server 192.168.1.64 255.255.255.224
# make sure clients know to use this route
push &quot;route 192.168.1.0 255.255.255.0&quot;
ifconfig-pool-persist pp.txt
# Allow clients to see each other
client-to-client
keepalive 30 120
comp-lzo
user nobody
group nogroup
persist-key
persist-tun
status openvpn-status.log
verb 3
</code></pre>
<p>You should now be able to start the OpenVPN server.</p>
<p>Before you can connect your iOS device you need to generate a <code>.ovpn</code> file that contains the certificates for your client and some additional configuration options.</p>
<p>Create the following script and then use it to generate a <code>.ovpn</code> file.</p>
<h4 id="createovpnprofile"><code>create-ovpn-profile</code></h4>
<pre><code>#!/bin/bash
NAME=$1
KEYSTORE=/etc/openvpn/easy-rsa/keys
CA=$(&lt; ${KEYSTORE}/ca.crt)
CERT=$(&lt; ${KEYSTORE}/${NAME}.crt)
KEY=$(&lt; ${KEYSTORE}/${NAME}.key)
OUTPUT=./${NAME}.ovpn

cat &gt; ${OUTPUT} &lt;&lt; __EOF__
client
dev tun
proto udp
remote vpn.host.com 1194
comp-lzo
redirect-gateway
&lt;ca&gt;
${CA}
&lt;/ca&gt;
&lt;cert&gt;
${CERT}
&lt;/cert&gt;
&lt;key&gt;
${KEY}
&lt;/key&gt;
# other options (mostly defaults)
nobind
persist-key
persist-tun
user nobody
group nogroup
resolv-retry infinite
__EOF__
</code></pre>
<p>Usage <code>./create-ovpn-profile client1</code></p>
<p>The generated <code>.ovpn</code> file can then be copied to the device and imported into the OpenVPN app.</p>
<p>For more information see the <a href="https://docs.openvpn.net/docs/openvpn-connect/openvpn-connect-ios-faq.html">OpenVPN iOS FAQ</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Bridging Mosquitto MQTT to io.adafruit.com MQTT]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Adafruit have recently enabled MQTT as a transport for feeds on io.adafruit.com.</p>
<p>If you already have a MQTT broker configured and working you probably don't want to reconfigure your clients to publish to io.adafruit. Additionally the feeds only permit one topic level, so your carefully crafted hierarchy</p>]]></description><link>https://troy.dack.com.au/bridging-mosquitto-mqtt-to-io-adafruit-com-mqtt/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40c8</guid><category><![CDATA[linux]]></category><category><![CDATA[command line]]></category><category><![CDATA[admin]]></category><category><![CDATA[programming]]></category><category><![CDATA[IoT]]></category><category><![CDATA[MQTT]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Mon, 18 Jan 2016 00:44:45 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2016/01/Screen-Shot-2016-01-18-at-11-42-09-AM.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2016/01/Screen-Shot-2016-01-18-at-11-42-09-AM.png" alt="Bridging Mosquitto MQTT to io.adafruit.com MQTT"><p>Adafruit have recently enabled MQTT as a transport for feeds on io.adafruit.com.</p>
<p>If you already have a MQTT broker configured and working you probably don't want to reconfigure your clients to publish to io.adafruit. Additionally the feeds only permit one topic level, so your carefully crafted hierarchy of topics would have to be reworked as well.</p>
<p>Another option is to bridge your local MQTT broker to adafruit.io and select which topics you want to publish there.</p>
<p>Here's an example <a href="https://io.adafruit.com/tdack/temperatures">dashboard</a> that uses data from my bridged MQTT broker to display the temperatures in and around my beer fermenting fridge.</p>
<p>See the <a href="https://gist.github.com/tdack/45dd356d9271a87914ce">gist</a> below for all the details.</p>
<script src="https://gist.github.com/tdack/45dd356d9271a87914ce.js"></script><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Upgrading Ghost]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>If you have a self hosted installation of Ghost upgrading it can be a little bit daunting and possibly dangerous. The instructions <a href="http://support.ghost.org/how-to-upgrade/">here</a> are pretty good. Although sometimes all you want is one command to do the lot for you so you don't have to remember where those instructions are</p>]]></description><link>https://troy.dack.com.au/upgrading-ghost/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40c7</guid><category><![CDATA[linux]]></category><category><![CDATA[ghost]]></category><category><![CDATA[admin]]></category><category><![CDATA[command line]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Thu, 12 Nov 2015 23:53:00 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2016/01/photo-1453060113865-968cea1ad53a.jpeg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2016/01/photo-1453060113865-968cea1ad53a.jpeg" alt="Upgrading Ghost"><p>If you have a self hosted installation of Ghost upgrading it can be a little bit daunting and possibly dangerous. The instructions <a href="http://support.ghost.org/how-to-upgrade/">here</a> are pretty good. Although sometimes all you want is one command to do the lot for you so you don't have to remember where those instructions are each time.</p>
<p>So here's my dodgy bash script that will upgrade your Ghost installation automagically for you.  Be warned there's little in the way of error checking, so always have a backup.</p>
<pre><code class="language-language-bash">#!/bin/bash
# Make sure only root can run our script
if [[ ${EUID} -ne 0 ]]; then
  echo &quot;This script must be run as root&quot; 1&gt;&amp;2
  exit 1
fi

if [[ $# -eq 0 ]]; then
  echo &quot;Usage:&quot;
  echo &quot;    $0 &lt;systemd|sysv&gt;&quot;
  echo -e &quot;\n systemd : use systemd/systemctl to stop/start services&quot;
  echo &quot; sysv    : use /etc/init.d syvs init system to stop/start services&quot;
fi
 
GHOST_PARENT=/var/www
OLD_PWD=$(pwd)

case &quot;$1&quot; in
   systemd)
     INIT_START=&quot;sudo systemctl start ghost.service&quot;
     INIT_STOP =&quot;sudo systemctl stop ghost.service&quot;
     ;;
   sysv)
     INIT_START=&quot;sudo service ghost start&quot;
     INIT_STOP =&quot;sudo service ghost stop&quot;
     ;;
sac
     
cd ${GHOST_PARENT}

# Get current Ghost version
OLD_VERSION=$(node -pe &quot;JSON.parse(require('fs').readFileSync('/dev/stdin').toString()).version&quot; &lt; ghost/package.json)

echo &quot;Upgrading Ghost&quot;
echo -n &quot;Fetching ... &quot;
curl -LOk https://ghost.org/zip/ghost-latest.zip &gt; upgrade.log 2&gt;&amp;1 || exit
echo &quot;Unzipping ...&quot;
unzip ghost-latest.zip -d ghost-latest &gt;&gt; upgrade.log 2&gt;&amp;1 || exit

# Get new Ghost version
NEW_VERSION=$(node -pe &quot;JSON.parse(require('fs').readFileSync('/dev/stdin').toString()).version&quot; &lt; ghost-latest/package.json)

echo &quot;Upgrading from ${OLD_VERSION} to ${NEW_VERSION}.  Please wait ...&quot;

# Stop Ghost
${INIT_STOP}

# Backup the current version core files
mv -v ghost/core ghost-${OLD_VERSION}-core || exit

# Update with new files
cp -v ghost-latest/{*.js,*.json,*.md,LICENSE} ghost/ || exit
cp -R -v ghost-latest/core ghost/ || exit
cp -R -v ghost-latest/content/themes/casper ghost/content/themes || exit
cd ghost

# Update nodejs modules as required
npm install --production

# Fix permissions
chown -R ghost:www-data ./*

# Restart Ghost
${INIT_START}

cd ${OLD_PWD}

</code></pre>
<p>And for those using a systemd based init system here's a <code>.service</code> file for systemd</p>
<p><strong>/etc/systemd/system/ghost.service</strong></p>
<pre><code class="language-language-ini">[Unit]
Description=Ghost service
# Make sure we have functional network and logging available
After=syslog.target
After=network.target

[Service]
User=ghost
Group=www-data
ExecStart=/usr/bin/node /path/to/ghost/index.js
Environment=NODE_ENV=production
StandardOutput=null
TimeoutSec=10

[Install]
WantedBy=multi-user.target
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Calibre ebooks server Apache WSGI Application]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Once you've got a lot of ebooks in Calibre it is handy to have them available straight from your tablet.  An easy way to do this is using the content server that is part of Calibre.  If you have an Apache server it is fairly easy integrate the content server.</p>]]></description><link>https://troy.dack.com.au/calibre-ebooks-server-apache-wsgi-application/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40c6</guid><category><![CDATA[linux]]></category><category><![CDATA[admin]]></category><category><![CDATA[apache2]]></category><category><![CDATA[calibre]]></category><category><![CDATA[ebooks]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Tue, 30 Jun 2015 03:26:35 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2016/01/photo-1431608660976-4fe5bcc2112c.jpeg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2016/01/photo-1431608660976-4fe5bcc2112c.jpeg" alt="Calibre ebooks server Apache WSGI Application"><p>Once you've got a lot of ebooks in Calibre it is handy to have them available straight from your tablet.  An easy way to do this is using the content server that is part of Calibre.  If you have an Apache server it is fairly easy integrate the content server.  There are two methods that can be used to do this:</p>
<ol>
<li>Proxy the content server using <code>mod_proxy</code></li>
<li>Run as an in-process server using WSGI (<code>mod_wsgi</code> under Apache)</li>
</ol>
<p><code>mod_proxy</code> is fairly well documented and works well for most people.  I've used it for a few years but recently decided to give the <code>mod_wsgi</code> route a try.  It was surprisingly easy to get working.  Configuration files, wsgi script and notes are below.</p>
<h4 id="createwsgiapplicationscript">Create WSGI application script</h4>
<p>Firstly you'll need to create the WSGI application script that Apache will call when it needs to start an instance of the Calibre content server.  Copy the following into <code>/usr/lib/cgi-bin/calibre-wsgi-adapter.wsgi</code>:</p>
<p><strong>calibre-wsgi-adapter.wsgi</strong></p>
<pre><code class="language-language-python"># WSGI script file to run calibre content server as a WSGI app
import sys, os

# You can get the paths referenced here by running
# calibre-debug --paths
# on your server

# The first entry from CALIBRE_PYTHON_PATH
sys.path.insert(0, '/usr/lib/calibre')

# CALIBRE_RESOURCES_PATH
sys.resources_location = '/usr/share/calibre'

# CALIBRE_EXTENSIONS_PATH
sys.extensions_location = '/usr/lib/calibre/calibre/plugins'

# Path to directory containing calibre executables
sys.executables_location = '/usr/bin'

# Path to a directory for which the server has read/write permissions
# calibre config will be stored here
os.environ['CALIBRE_CONFIG_DIRECTORY'] = '/storage/ebooks/calibre-config'

del sys
del os

from calibre.library.server.main import create_wsgi_app
application = create_wsgi_app(
        # The mount point of this WSGI application (i.e. the first argument to
        # the WSGIScriptAlias directive). Set to empty string is mounted at /
        prefix='/calibre',

        # Path to the calibre library to be served
        # The server process must have write permission for all files/dirs
        # in this directory or BAD things will happen
        path_to_library='/storage/ebooks/Calibre_Library',

        # The virtual library (restriction) to be used when serving this
        # library.
        virtual_library=None
)

del create_wsgi_app
</code></pre>
<h4 id="apachevirtualhostconfiguration">Apache Virtual Host configuration</h4>
<p>Add the following virtual host config file to the appropriate place in your Apache configuration.</p>
<blockquote>
<p>e.g.: for Ubuntu put it in <code>/etc/apache2/sites-available</code> then enable it with <code>a2ensite ebooks.domain.com</code></p>
</blockquote>
<p>Restart Apache and navigate to <a href="https://ebooks.domain.com/calibre/">https://ebooks.domain.com/calibre/</a> to browse your ebooks.</p>
<p><strong>ebooks.domain.com.conf</strong></p>
<pre><code class="language-language-markup">&lt;VirtualHost *:80&gt;
	ServerName ebooks.domain.com
	ServerAdmin admin@ebooks.domain.com
	DocumentRoot /storage/ebooks/

        RewriteEngine on
        ReWriteCond %{SERVER_PORT} !^443$
        RewriteRule ^/(.*) https://%{HTTP_HOST}/$1 [NC,R,L]

&lt;/VirtualHost&gt;
&lt;IfModule mod_ssl.c&gt;
	&lt;VirtualHost *:443&gt;
	ServerName ebooks.domain.com
	ServerAdmin admin@ebooks.domain.com
	DocumentRoot /storage/ebooks/
	&lt;Directory /storage/ebooks&gt;
		AllowOverride None
		Require all granted
	&lt;/Directory&gt;
	DirectoryIndex index.md index.sd index.php index.html
	Header set Accept-Ranges &quot;none&quot;
	RequestHeader unset Accept-Encoding
        WSGIDaemonProcess calibressl processes=1 threads=3
        WSGIScriptAlias /calibre /usr/lib/cgi-bin/calibre-wsgi-adapter.wsgi
        &lt;Directory /storage/ebooks/&gt;
                WSGIProcessGroup calibressl
                WSGIApplicationGroup %{GLOBAL}
                Order deny,allow
                Allow from all
        &lt;/Directory&gt;

	SSLEngine on
	SSLCertificateFile	/etc/ssl/server/ebooks.domain.com.crt
	SSLCertificateKeyFile   /etc/ssl/private/ebooks.domain.com.key
	BrowserMatch &quot;MSIE [2-6]&quot; \
			nokeepalive ssl-unclean-shutdown \
			downgrade-1.0 force-response-1.0
	BrowserMatch &quot;MSIE [17-9]&quot; ssl-unclean-shutdown
	&lt;/VirtualHost&gt;
&lt;/IfModule&gt;
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[2.2" Display for Raspberry Pi]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>These cheap <a href="http://www.banggood.com/2_2-Inch-Serial-TFT-SPI-LCD-Screen-Module-HD-240-x-320-5110-Compatible-p-912854.html">2.2&quot; LCD</a>s are pretty handy for a Raspberry Pi.</p>
<p>Connect them like this:</p>
<p><img src="https://troy.dack.com.au/content/images/2015/06/LCD-Display-Wiring.png" alt></p>
<p>Use <a href="https://github.com/notro/fbtft/wiki/LCD-Modules#adafruit-22">Notoro's fbtft</a> modules to get them working with Linux. They are included in the Raspberry Pi Raspbian image.</p>
<p>Edit /etc/modules and add (for landscape orientation):</p>
<pre><code>fbtft_device name=adafruit22a rotate=</code></pre>]]></description><link>https://troy.dack.com.au/2-2-display-for-raspberry-pi/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40c5</guid><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Sat, 13 Jun 2015 09:03:33 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2015/06/SKU113399--8-.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2015/06/SKU113399--8-.jpg" alt="2.2" Display for Raspberry Pi"><p>These cheap <a href="http://www.banggood.com/2_2-Inch-Serial-TFT-SPI-LCD-Screen-Module-HD-240-x-320-5110-Compatible-p-912854.html">2.2&quot; LCD</a>s are pretty handy for a Raspberry Pi.</p>
<p>Connect them like this:</p>
<p><img src="https://troy.dack.com.au/content/images/2015/06/LCD-Display-Wiring.png" alt="2.2" Display for Raspberry Pi"></p>
<p>Use <a href="https://github.com/notro/fbtft/wiki/LCD-Modules#adafruit-22">Notoro's fbtft</a> modules to get them working with Linux. They are included in the Raspberry Pi Raspbian image.</p>
<p>Edit /etc/modules and add (for landscape orientation):</p>
<pre><code>fbtft_device name=adafruit22a rotate=90
</code></pre>
<p>Edit /boot/cmdline.txt to put login console on the LCD, add the following to the end:</p>
<pre><code>fbcon=map:10
</code></pre>
<p>Edit /boot/config.txt and add or uncomment the following to enable SPI:</p>
<pre><code>dtparam=spi=on
</code></pre>
<p>User mode access using <a href="http://pygame.org">PyGame</a> and DirectFB took a bit of sleuthing, bit I finally nailed it down.</p>
<ul>
<li>Append the modes for the LCD to <code>/etc/fb.modes</code></li>
</ul>
<pre><code>fbset --info -fb /dev/fb1|sed -n '/mode/,/endmode/p'|sudo tee -a /etc/fb.modes
</code></pre>
<ul>
<li>Create <code>~/.directfbrc</code> with:</li>
</ul>
<pre><code>fbdev=/dev/fb1
quiet
no-vt
no-vt-switch
no-banner
mode=320x240
depth=16
</code></pre>
<ul>
<li>Check permissions and ownership of /dev/fb1</li>
</ul>
<pre><code>pi@raspberry ~ $ ls -l /dev/fb1
crw-rw---T 1 root video 29, 1 Jun 14 16:50 /dev/fb1
</code></pre>
<ul>
<li>Make your user is in the <code>video</code> group:</li>
</ul>
<pre><code>pi@raspberry ~ $ groups
pi adm dialout cdrom sudo audio video plugdev games users netdev gpio i2c spi input
</code></pre>
<ul>
<li>Use the following code to initialise your PyGame application</li>
</ul>
<pre><code class="language-(python)">        &quot;&quot;&quot;Ininitializes a new pygame screen using the framebuffer&quot;&quot;&quot;
        # Based on &quot;Python GUI in Linux frame buffer&quot;
        # http://www.karoltomala.com/blog/?p=679
        disp_no = os.getenv(&quot;DISPLAY&quot;)
        if disp_no:
            print &quot;I'm running under X display = {0}&quot;.format(disp_no)
        
        # Check which frame buffer drivers are available
        # Start with fbcon since directfb hangs with composite output
        drivers = ['fbcon', 'directfb', 'svgalib']
        found = False
        for driver in drivers:
            # Make sure that SDL_VIDEODRIVER is set
            if not os.getenv('SDL_VIDEODRIVER'):
                os.putenv('SDL_VIDEODRIVER', driver)
            try:
                pygame.display.init()
            except pygame.error:
                print 'Driver: {0} failed.'.format(driver)
                continue
            found = True
            break
    
        if not found:
            raise Exception('No suitable video driver found!')
        
        size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
        print &quot;%s: framebuffer size: %d x %d&quot; % (driver, size[0], size[1])
        screen = pygame.display.set_mode(size, pygame.FULLSCREEN)
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Hackintosh Trials and Tribulations]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>My system has been running as a Hackinstosh for the better part 2 years, there’s been the odd hiccup along the way, mainly around sound drivers, but other than that it’s been pretty reliable. Its a GigaByte Z87-HD3 main board with a Core i7–4790 processor and an</p>]]></description><link>https://troy.dack.com.au/mackintosh-trials-and-tribulations/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40c4</guid><category><![CDATA[os x]]></category><category><![CDATA[hackintosh]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Thu, 09 Apr 2015 09:14:58 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2016/01/ws_Not_an_Apple_1600x1200.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2016/01/ws_Not_an_Apple_1600x1200.jpg" alt="Hackintosh Trials and Tribulations"><p>My system has been running as a Hackinstosh for the better part 2 years, there’s been the odd hiccup along the way, mainly around sound drivers, but other than that it’s been pretty reliable. Its a GigaByte Z87-HD3 main board with a Core i7–4790 processor and an NVidia GT-740 video card.</p>
<p>I had to recently re-convince Windows 7 that it would work on my main machine. After trying a variety of BIOS settings I finally had a few working that would allow me to boot Windows in UEFI mode and, more importantly, allowed the USB keyboard and mouse to function during the install. The joys of an operating system that was created before USB3.</p>
<p>Once Windows was up and running, mother board drivers installed, software updates done (232 of them!), Office, SolidWorks, AutoCAD, MATLAB and a bunch of other software installed it was time to head back to the land of OS X.</p>
<p>And here’s where the grief really started.</p>
<p>After my futzing around with Windows I had managed to wipe the partition table on one of my other drives, the most important one. The one holding all my documents, my home folder etc.. A quick trip into Linux and it was back. Yippee! Go Linux for not breaking easily and being able to handle more file systems than you can poke a stick at.</p>
<p>On returning to OS X things went from bad to worse. OS X refused to display the login screen, getting stuck at a black screen with the mouse cursor present. Much debugging and swearing ensued. The system was accessible via SSH, so that was a start, but not so good for a pointy clicky GUI.</p>
<p>Digging through the logs I discovered that loginwindow.app was crashing. Then logind would respawn it. It would crash again. Constantly. This wasn’t right, I hadn’t done anything to OS X recently, why was it happening?</p>
<p>Reboot into Safe Mode — things work, but no accelerated graphics and a few other niceties aren’t working. Did the usual OS X thing, checked and repaired permissions, checked the file system.</p>
<p>All. Good.</p>
<p>Verbose boot logging wasn’t any more helpful, just showed lots of stuff starting and then back to the black screen.</p>
<p>So I headed back into the BIOS to see if I’d left anything turned off that should have been on from when I was fighting with Windows. Everything seemed fine, in fact pretty much everything I thought should be on was.</p>
<p>Time to hit up Google and see what it knew. Apparently nothing on this topic. How could the all knowing oracle not have an answer? Where do I go next? So many unanswered questions!</p>
<p>A little more sleuthing and I stumbled across the Intel Vt-d setting in the BIOS and some hints that OS X might not like it, and it was turned on. I thought this was a good thing, what self respecting nerd running multiple operating systems and virtual machines doesn’t want a bit of Vt-d?</p>
<p>It turns out those nerds who are running OS X on non-Apple hardware are the ones who don’t want Vt-d enabled. With flipping of one setting and a quick reboot later I was back in the land of Hackintosh. With everything working as it should.</p>
<p>So a lesson to all those who blithely swing a mouse around their BIOS settings — Save a “known good” copy of your settings somewhere before you screw something up and waste your time chasing rabbits down holes that don’t really exist.</p>
<p>Oh, the recent OS X 10.10.3 update installed without any problems on my Hackintosh.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Get Telstra Bigpond Usage from the command line]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>If you feel the need to get your Telstra Bigpond usage from the command line, say for putting into a round-robin database for drawing pretty graphs, then give this a go:</p>
<pre><code class="language-language-python">#!/usr/bin/env python2.7
import requests, sys, re
from bs4 import BeautifulSoup
    
data = {'username': 'steve.smith', 'password': 'somethingsecret!</code></pre>]]></description><link>https://troy.dack.com.au/get-telstra-bigpond-usage-from-the-command-line/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40c3</guid><category><![CDATA[linux]]></category><category><![CDATA[telstra]]></category><category><![CDATA[bigpond]]></category><category><![CDATA[python]]></category><category><![CDATA[command line]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Thu, 02 Apr 2015 07:58:07 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2019/10/ilya-pavlov-OqtafYT5kTw-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2019/10/ilya-pavlov-OqtafYT5kTw-unsplash.jpg" alt="Get Telstra Bigpond Usage from the command line"><p>If you feel the need to get your Telstra Bigpond usage from the command line, say for putting into a round-robin database for drawing pretty graphs, then give this a go:</p>
<pre><code class="language-language-python">#!/usr/bin/env python2.7
import requests, sys, re
from bs4 import BeautifulSoup
    
data = {'username': 'steve.smith', 'password': 'somethingsecret!'}
req = requests.post('https://signon.bigpond.com/login', params=data, allow_redirects=False)

if req.status_code == 302:
    cookies = {'BPSESSION': req.cookies['BPSESSION']}
    headers = {'Referer': 'https://signon.bigpond.com/login?goto=https%3A%2F%2Fusagemeter.bigpond.com%3A443%2Fdaily.do'}
    req = requests.get('https://usagemeter.bigpond.com/daily.do', headers=headers, cookies=cookies)
else:
    print &quot;Didn't get redirected? &quot;, req.status_code
    sys.exit()
    
if req.status_code == 200:
    # html.parser is important as it will ensure the document is parsed as
    # html rather than xml
    soup = BeautifulSoup(req.text, 'html.parser')
    usage = int(soup.find(class_='trStyleTotal').find('b').text) / 1e3
    limit = soup.find('th', text=&quot;Monthly allowance&quot;).parent.td.text.strip('\r\n\t')
    limit = re.match('\d+', limit).group()
    print usage, &quot;/&quot;, limit, &quot;GB&quot;
else:
    print &quot;Couldn't get usage page &quot;, req.status_code
    sys.exit()
</code></pre>
<p>Grab it from <a href="https://gist.github.com/tdack/5e8a5c7edfe1857e358a#file-bigpond-py">GitHub</a></p>
<p><strong>Output</strong></p>
<pre><code> troy@skade:~$ python bigpond.py
 195.525 / 500 GB
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Drawing multiple lines with D3.js]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>After a great deal of frustration and much trial and error I have finally managed to get D3.js to plot multiple lines, and when the data changes the lines are updated, complete with transitions.</p>
<p>Head over to <a href="http://brew-pi.dack.com.au/temps/d3">http://brew-pi.dack.com.au/temps/d3</a> to see the code behind</p>]]></description><link>https://troy.dack.com.au/drawing-multiple-lines-with-d3-js/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40c1</guid><category><![CDATA[raspberry-pi]]></category><category><![CDATA[programming]]></category><category><![CDATA[javascript]]></category><category><![CDATA[d3.js]]></category><category><![CDATA[rrd]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Tue, 20 Jan 2015 07:21:04 GMT</pubDate><media:content url="https://troy.dack.com.au/content/images/2016/01/Screen-Shot-2016-01-18-at-2-41-21-PM.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://troy.dack.com.au/content/images/2016/01/Screen-Shot-2016-01-18-at-2-41-21-PM.png" alt="Drawing multiple lines with D3.js"><p>After a great deal of frustration and much trial and error I have finally managed to get D3.js to plot multiple lines, and when the data changes the lines are updated, complete with transitions.</p>
<p>Head over to <a href="http://brew-pi.dack.com.au/temps/d3">http://brew-pi.dack.com.au/temps/d3</a> to see the code behind the example below.</p>
<style>
svg {
	font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.axis path,
.axis line {
	fill: none;
	stroke: #3c3c3c;
	shape-rendering: crispEdges;
}

.line {
	fill: none;
	stroke: #aaa;
	stroke-linejoin: round;
	stroke-linecap: round;
	stroke-width: 2px;
}

.legend-text {
	text-transform: capitalize;
	white-space: pre;
}
</style>
<div style="width: 25%">
	Period: <select id="period" class="form-control"></select>
</div>
<svg id="temp-graph"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script>
<script src="https://troy.dack.com.au/static/js/binaryXHR.js"></script>
<script src="https://troy.dack.com.au/static/js/rrdFile.js"></script>
<script src="https://troy.dack.com.au/static/js/rrdFilter.js"></script>
<script src="https://troy.dack.com.au/static/js/rrdAsync.js"></script>
<script src="https://troy.dack.com.au/static/js/rrd2D3.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/highlight.min.js"></script>
<script id="code">
// Retrieve data from RRD
var rrd_obj = new rrdAsync("/static/rrd/usage.rrd",null, init);
var rrd_data; // populated in init() once RRD file has been fetched.

var margin = {top: 20, right: 70, bottom: 100, left: 40},
	width = 800 - margin.left - margin.right,
	height = 500 - margin.top - margin.bottom;

var colours = d3.scale.category10();

function init() {
	// RRD file has been fetched, so data is ready to be used
	rrd_data = rrd_obj.rrd_data;
	populateRRASelect("#period");
	drawGraph(rrdRRA2D3Obj(rrd_data,1,["jack_usage", "harry_usage"],true), ["jack_usage", "harry_usage"]);
	d3.select("#listing")
		.append("pre")
		.append("code")
		.attr("class", "js")
		.text(d3.select("#code").html());
	hljs.highlightBlock(d3.select("#listing pre code").node());
}

function populateRRASelect(res_id) {

	// populate with RRA info
	var nrRRAs=rrd_data.getNrRRAs();
	var RRAs = [];
	for (var i=0; i<nrRRAs; i++) {
		var rra=rrd_data.getRRAInfo(i);
		var step=rra.getStep();
		var rows=rra.getNrRows();
		var period=step*rows;
		var rra_label=rfs_format_time(period) + " ("+rfs_format_time(step)+" steps)";
		RRAs.push({text: rra_label, value: i});
	}

	var select = d3.select(res_id)
		.on("change", periodChange),
	options = select
		.selectAll("option")
		.data(RRAs)
		.enter()
			.append("option")
			.attr('value', function(d){ return d.value; })
			.text(function(d){ return d.text;});

	select.property("value", 1);
}

function periodChange(){
	// Update graph with new data
	var RRA = this.value ? this.value : 1;
	var dataset=rrdRRA2D3Obj(rrd_data,RRA,["jack_usage", "harry_usage"],true);

	var x = d3.time.scale()
		.range([0, width])
		.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.date;} ) }),
				  d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.date;} ) })]);
	var y = d3.scale.linear()
		.range([height, 0])
		.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.value;} ) }),
				  d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.value;} ) })]).nice();

	var line = d3.svg.line()
		.interpolate('basis')
		.x(function(d) { return x(d.date); })
		.y(function(d) { return y(d.value); });

	var svg = d3.select("#temp-graph")

	// DATA JOIN
	var lines = svg.selectAll("path.line").data(dataset)

	// Update
	lines.transition()
			.duration(1000)
			.attr("d", function(d) { d.line = this; return line(d.values); });

	// Add new values
	lines.enter()
			.append("path")
			.attr("class", "line")
			.attr("style", function(d,i){ return "stroke: " + colours(i);} )
			.style("fill-opacity", 1e-6)
			.transition()
				.duration(1000)
				.attr("d", function(d) { d.line = this; return line(d.values); })
				.style("fill-opacity", 1);

	// Remove
	lines.exit()
		.transition()
			.duration(1000)
			.style("fill-opacity", 1e-6)
			.remove();

	// Update X axis
	svg.select(".axis.axis--x")
		.transition()
			.duration(1000)
				.call(d3.svg.axis()
						.scale(x)
						.orient("bottom"));
	// Update Y axis
	svg.select(".axis.axis--y")
		.transition()
			.duration(1000)
				.call(d3.svg.axis()
						.scale(y)
						.orient("left")
						.ticks(10, ".2f"));

	// Update stats
	var stats = svg.select("g.stats");

	formatFloat = d3.format(".2f");
	stats.selectAll("text")
		.data(dataset)
			.attr("class", "legend-text")
			.attr("x", function(d, i){ return 15 + (300 * (i %2)); })
			.attr("y", function(d, i){ return 20 * (((i+1)/2).toFixed() % 2) + 9; })
			.text(function(l){
				var max = ("      " + formatFloat(d3.min(l.values, function(d){ return d.value; }))).slice(-6);
				var min = ("      " + formatFloat(d3.max(l.values, function(d){ return d.value; }))).slice(-6);
				var mean = ("      " + formatFloat(d3.mean(l.values, function(d){return d.value; }))).slice(-6);
				var str = (l.name + "               ").slice(0,15) + "Min: " + min +"  Max: " + max + " Avg: " + mean;
				return str; });
}

function drawGraph(dataset, names) {

	var x = d3.time.scale()
		.range([0, width])
		.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.date;} ) }),
				  d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.date;} ) })]);

	var y = d3.scale.linear()
		.range([height, 0])
		.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.value;} ) }),
				  d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.value;} ) })]).nice();

	var color = d3.scale.category20();

	var line = d3.svg.line()
		.interpolate('basis')
		.x(function(d) { return x(d.date); })
		.y(function(d) { return y(d.value); });

	var svg = d3.select("#temp-graph")
		.attr("width", width + margin.left + margin.right)
		.attr("height", height + margin.top + margin.bottom)
		.append("g")
		.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

	// Draw X axis
	svg.append("g")
		.attr("class", "axis axis--x")
		.attr("transform", "translate(0," + height + ")")
		.call(d3.svg.axis()
			.scale(x)
			.orient("bottom"));

	// Draw Y axis
	svg.append("g")
		.attr("class", "axis axis--y")
		.call(d3.svg.axis()
			.scale(y)
			.orient("left")
			.ticks(10, ".2f"))

	// Plot the lines - first time, so have to append <g>
	svg.append("g")
		.attr("class", "line-group")
		.selectAll("path")
			.data(dataset)
		.enter()
			.append("path")
			.attr("class", "line")
			.attr("style", function(d,i){ return "stroke: " + colours(i);} )
			.attr("d", function(d) { d.line = this; return line(d.values); });

	// add legend
	var legend = svg.append("g")
		.attr("class", "legend")
		.attr("height", 100)
		.attr("width", 120);

	legend.selectAll('rect')
		.data(dataset)
		.enter()
			.append("rect")
			.attr("x", width + 15)
			.attr("y", function(d, i){ return i *  20;})
			.attr("width", 10)
			.attr("height", 10)
			.style("fill", function(d, i) {	return colours(i);	});

	legend.selectAll('text')
		.data(dataset)
		.enter()
			.append("text")
			.attr("x", width + 27)
			.attr("y", function(d, i){ return i *  20 + 9;})
			.attr("class", "legend-text")
			.text(function(d) { return d.name; });

	// Display some simple stats
	var stats = svg.append("g")
		.attr("class", "stats")
		.attr("height", 110)
		.attr("width", width)
		.attr("transform", "translate(40," + +(height+20) + ")");

	stats.selectAll('rect')
		.data(dataset)
		.enter()
			.append("rect")
			.attr("x", function(d, i){ return 300 * (i % 2); })
			.attr("y", function(d, i){ return 20 * (((i+1)/2).toFixed() % 2); })
			.attr("width", 10)
			.attr("height", 10)
			.style("fill", function(d, i) {	return colours(i); });

	formatFloat = d3.format(".2f");
	stats.selectAll("text")
		.data(dataset)
		.enter()
			.append("text")
			.attr("class", "legend-text")
			.attr("x", function(d, i){ return 15 + (300 * (i %2)); })
			.attr("y", function(d, i){ return 20 * (((i+1)/2).toFixed() % 2) + 9; })
			.text(function(l){
					var min = ("      " + formatFloat(d3.min(l.values, function(d){ return d.value; }))).slice(-6);
					var max = ("      " + formatFloat(d3.max(l.values, function(d){ return d.value; }))).slice(-6);
					var mean = ("      " + formatFloat(d3.mean(l.values, function(d){return d.value; }))).slice(-6);
					var str = (l.name + "               ").slice(0,15) + "Min: " + min +"  Max: " + max + " Avg: " + mean;
					return str; });

}
</script>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Change the RaspberryPi default username]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>A couple of quick commands to change the default username (<code>pi</code>) that is configured when you install Raspbian for the first time:</p>
<p>Login to a text console, ie: <code>Ctrl</code>+<code>F1</code>. Then execute the following commands:</p>
<ol>
<li><code>exec sudo -s</code></li>
<li><code>cd /</code></li>
<li><code>usermod -l newusername -d /home/newusername -m pi</code></li>
</ol>
<p>Thanks to <a href="http://raspberrypi.stackexchange.com/questions/12827/change-default-username">this</a></p>]]></description><link>https://troy.dack.com.au/change-the-raspberrypi-default-username/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40c0</guid><category><![CDATA[raspberry-pi]]></category><category><![CDATA[linux]]></category><category><![CDATA[admin]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Sat, 20 Dec 2014 04:27:12 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>A couple of quick commands to change the default username (<code>pi</code>) that is configured when you install Raspbian for the first time:</p>
<p>Login to a text console, ie: <code>Ctrl</code>+<code>F1</code>. Then execute the following commands:</p>
<ol>
<li><code>exec sudo -s</code></li>
<li><code>cd /</code></li>
<li><code>usermod -l newusername -d /home/newusername -m pi</code></li>
</ol>
<p>Thanks to <a href="http://raspberrypi.stackexchange.com/questions/12827/change-default-username">this</a> StackOverflow post.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Atom.io Syntax Highlighting Theme]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In my formative programming years I spent a lot of time writing Pascal code in the Borland Turbo Pascal IDE.  It was one of the first programming languages that had a compiler, editor and debugger all included in one integrated tool.</p>
<p>Now I'm not writing code in Pascal but a</p>]]></description><link>https://troy.dack.com.au/atom-io-syntax-highlighting-theme/</link><guid isPermaLink="false">597d8e7d4c44c319e9ac40bf</guid><category><![CDATA[programming]]></category><category><![CDATA[editor]]></category><category><![CDATA[atom.io]]></category><category><![CDATA[syntax highlighting]]></category><dc:creator><![CDATA[Troy Dack]]></dc:creator><pubDate>Tue, 09 Dec 2014 22:20:18 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In my formative programming years I spent a lot of time writing Pascal code in the Borland Turbo Pascal IDE.  It was one of the first programming languages that had a compiler, editor and debugger all included in one integrated tool.</p>
<p>Now I'm not writing code in Pascal but a variety of other languages using the <a href="http://atom.io">Atom</a> editor.  Atom is written in JavaScript using the Google V8 JavaScript engine.  This means that the entire editor is very customisable allowing users to create their own themes for both the user interface and the syntax highlighting.</p>
<p>So, here's my syntax highlighting theme for Atom that is reminiscent of the Borland Turbo Pascal IDE.</p>
<p>Grab it from:</p>
<ul>
<li><a href="https://github.com/tdack/turbo-pascal-syntax">GitHub</a></li>
<li><a href="https://atom.io/themes/turbo-pascal-syntax">Atom.io</a></li>
</ul>
<p>You can also install the syntax theme directly in the Atom editor.  Simply go to <code>Preferences &gt; Themes</code> and search for &quot;<em>turbo pascal</em>&quot;</p>
<p><img src="https://cl.ly/Yt7e/Image%202014-12-09%20at%204.37.32%20pm.png" alt></p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>