<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Stumbles is Ben Sturmfels</title><link href="https://stumbles.id.au/" rel="alternate"></link><link href="https://stumbles.id.au/feeds/all.atom.xml" rel="self"></link><id>https://stumbles.id.au/</id><updated>2018-02-01T00:00:00+11:00</updated><entry><title>Getting started with the GnuBee Personal Cloud 2 (or 1)</title><link href="https://stumbles.id.au/getting-started-with-the-gnubee-personal-cloud-2-or-1.html" rel="alternate"></link><published>2018-02-01T00:00:00+11:00</published><updated>2018-02-01T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2018-02-01:getting-started-with-the-gnubee-personal-cloud-2-or-1.html</id><summary type="html">&lt;p&gt;I just received my &lt;a href="https://www.crowdsupply.com/gnubee/personal-cloud-2"&gt;GnuBee Personal Cloud
2&lt;/a&gt; in the post. It's a
device newly manufactured specifically to run completely free software, making
in completely unique. The device is designed for network accessible storage,
providing slots for six 3.5" hard drives.&lt;/p&gt;
&lt;p&gt;As a brand new product, the &lt;a href="http://gnubee.org/"&gt;documentation&lt;/a&gt; for getting started is nearly
non-existant though, so here's my journey so far.&lt;/p&gt;
&lt;h1&gt;Assembly&lt;/h1&gt;
&lt;p&gt;I was surprised to see that the holes for the circuit board mounting brackets
didn't all line up. It turns out that the centre brackets are oriented
differently to the end brackets. Look carefully at the photo below.&lt;/p&gt;
&lt;figure&gt; &lt;a
href="https://media.sturm.com.au/u/ben/m/assembly-of-gnubee-personal-cloud-2/"&gt;&lt;img
src="https://media.sturm.com.au/mgoblin_media/media_entries/32/IMG_20180201_145938.medium.jpg"
alt="GnuBee Personal Cloud 2" /&gt;&lt;/a&gt; &lt;figcaption&gt;The GnuBee Personal Cloud 2
(from below)&lt;/figcaption&gt; &lt;/figure&gt;

&lt;h1&gt;Getting started&lt;/h1&gt;
&lt;p&gt;The GBPC comes with the LibreCMC operating system installed in the on-board
flash memory.&lt;/p&gt;
&lt;p&gt;Based on the &lt;a href="http://gnubee.org/background_info.html"&gt;Background information for GnuBee
PC1&lt;/a&gt;, I plugged a network cable in to
the black socket and plugged the other end in to my computer. I then tured on
the GBPC using the silver switch (red button on GBPC1). No SDCard was installed
at this point.&lt;/p&gt;
&lt;p&gt;Once it had started up, the computer connected to the GBPC's network. Visiting
http://192.168.10.1/ in a web browser showed the LibreCMC login screen. Although
it's not made clear, you can initially log in as user "root" by leaving the
password blank. You can also connect with SSH with no password by running &lt;code&gt;ssh
-o StrictHostKeyChecking root@192.168.10.1&lt;/code&gt; with no password. I needed the &lt;code&gt;-o
StrictHostKeyChecking&lt;/code&gt; as I also have a GBPC1 that uses the same IP address.&lt;/p&gt;
&lt;p&gt;Proceeding to the System/Administration section allows entry of a root password.
I set up key-based SSH access at the same time. Leave "Interface" as
"unspecified", uncheck password authentication and enter your SSH public key
fingerprint (from .ssh/id_rsa.pub). Select "save and apply" at the bottom of the
page. I was then able to log out and log back in with my new password, and
connect with SSH to root@192.168.10.1.&lt;/p&gt;
&lt;p&gt;Unfortunately I notied that after switching the device off and back on again,
the password and SSH changes were reset. I'm not sure why.&lt;/p&gt;
&lt;h1&gt;Resources&lt;/h1&gt;
&lt;p&gt;The GnuBee devices are not well documented at the moment, but at present the
best information can be found at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://gnubee.org/background_info.html"&gt;Background information for GnuBee PC 1&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://groups.google.com/forum/#!forum/gnubee"&gt;GnuBee Forum&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/gnubee-git/GnuBee_Docs/issues"&gt;GnuBee Issues&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/gnubee-git/GnuBee_Docs/"&gt;GnuBee Docs repository&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Installing hard drives&lt;/h1&gt;
&lt;p&gt;I installed a hard drive in slot 1 and after resetting the GBPC with the black
reset button, noted that the new drive was listed under the System/Mount Points
section as filesystem "crytpo_LUKS". The second drive was an unencrypted "ext4"
drive. That didn't show up under "Mount Points" automatically and didn't seem to
mount when I selected "Add", chose the drive from the list and defined a custom
mount point &lt;code&gt;/backup&lt;/code&gt;. There was no problem mounting this drive from the SSH
command line though.&lt;/p&gt;
&lt;h1&gt;Further work&lt;/h1&gt;
&lt;p&gt;At this point I still have a lot of questions, and not enough time to
investigate them all. If you know the answers, please &lt;a href="mailto:ben@stumbles.id.au"&gt;get in
touch&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Do I need to install a different operating system to do something useful&lt;/strong&gt; with
the device? I was a little suprised to find that the GBPC doesn't do anything
much out of the box. Not that this in any way limits the device's potential, but
it's nice not to have to do everything yourself. It seems that all drive
mounting must be done manually, that there's no file sharing services or even
rsync installed by default.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How do you install software&lt;/strong&gt; on LibreCMC? I tried the System/Software section
of the web interface, but installing or searching for packages fails with an
error connecting to the package repositories.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How do you boot from the SDCard&lt;/strong&gt; included in my "deluxe pack"? The card
appears to have Debian installed, but after insterting the SDCard and resetting,
the device still seem to boot to LibreCMC. Noting that the SDCard is
auto-mounted if inserted while LibreCMC is running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What do the different network ports do&lt;/strong&gt;? Black is a LAN port. Blue seems to be
the WAN port as the device can ping the outside world once plugged in. The
yellow port doesn't seem to do anything.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Can drives be made to auto-mount&lt;/strong&gt;? I noticed that plugging in a USB drive are
mounted automatically under &lt;code&gt;/tmp/run/mountd/&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How do you connect with the USB-to-UART&lt;/strong&gt; cable? I tried plugging it in,
connecting with &lt;code&gt;picocom /dev/ttyUSB0 9600&lt;/code&gt; and resetting the GBPC, but only see
garbage on the screen. Clearly I need different connection settings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Why don't password changes stick&lt;/strong&gt;? Cleary the device is accepting some
changes, because if I &lt;code&gt;mkdir /mnt/backup&lt;/code&gt;, the directory is still there after a
power off and on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Can it be configured to be a DHCP client&lt;/strong&gt;, rather than a DCHP server?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Update 5 Feb 2018&lt;/h1&gt;
&lt;p&gt;I learnt that USB-to-UART works via minicom, eg. &lt;code&gt;sudo minicom -b 57600 -D
/dev/ttyUSB0&lt;/code&gt;. See the &lt;a href="http://gnubee.org/USB_to_UART/README.html"&gt;GnuBee
documentation&lt;/a&gt;. I presume that
minicom must use different defaults to picocom.&lt;/p&gt;
&lt;p&gt;It appears that a (firmware update)[https://groups.google.com/forum/#!topic/gnubee/vbKwd7r-8_8] is required to allow booting from the provided SDCard.&lt;/p&gt;
&lt;p&gt;The network interface for LibreCMC can be changed from "static" to "DHCP
client".&lt;/p&gt;
&lt;p&gt;I'm having trouble mounting some encrypted drives. It appears that missing kernel
support for "aes-xts-plain64" may be the cause.&lt;/p&gt;
&lt;h1&gt;Update 7 Feb 2018&lt;/h1&gt;
&lt;p&gt;Plugged two RAID1 drives into the GBPC2 and it correctly recognised the three
RAID partitions and allocated them to &lt;code&gt;/dev/md125&lt;/code&gt;, &lt;code&gt;/dev/md126&lt;/code&gt; and
&lt;code&gt;/dev/md127&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;opkg update&lt;/code&gt; command, as well as the web interface fail to update the
package list with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Downloading https://librecmc.org/librecmc/downloads/snapshots/v1.4.1/ramips/mt7621/packages/Packages.gz
*** Failed to download the package list from https://librecmc.org/librecmc/downloads/snapshots/v1.4.1/ramips/mt7621/packages/Packages.gz

Downloading https://librecmc.org/librecmc/downloads/snapshots/v1.4.1/packages/mipsel_24kc/base/Packages.gz
*** Failed to download the package list from https://librecmc.org/librecmc/downloads/snapshots/v1.4.1/packages/mipsel_24kc/base/Packages.gz

Downloading https://librecmc.org/librecmc/downloads/snapshots/v1.4.1/packages/mipsel_24kc/packages/Packages.gz
*** Failed to download the package list from https://librecmc.org/librecmc/downloads/snapshots/v1.4.1/packages/mipsel_24kc/packages/Packages.gz

Collected errors:
 * opkg_download: Failed to download https://librecmc.org/librecmc/downloads/snapshots/v1.4.1/ramips/mt7621/packages/Packages.gz, wget returned 5.
 * opkg_download: Failed to download https://librecmc.org/librecmc/downloads/snapshots/v1.4.1/packages/mipsel_24kc/base/Packages.gz, wget returned 5.
 * opkg_download: Failed to download https://librecmc.org/librecmc/downloads/snapshots/v1.4.1/packages/mipsel_24kc/packages/Packages.gz, wget returned 5.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In contrast, my LibreCMC-based router from ThinkPenguin updates and installs
packages with no problems, so clearly something just isn't configured correctly.&lt;/p&gt;
&lt;p&gt;Password changes do appear to be saved, and persist across reboots and powering
off. It appears though that the small black button does a "factory reset", which
resets the LibreCMC login credentials.&lt;/p&gt;
&lt;p&gt;The hard drive idling plugin available through the web interface works well.&lt;/p&gt;
&lt;p&gt;Slot #1 on the GBPC2 is blocked by the two 12V plastic clips. This is a minor
design fault, but you can simply bend the four plastic tabs firmly down to fix
the problem.&lt;/p&gt;</summary><category term="hardware"></category></entry><entry><title>Configuring Android K-9 Mail from the command line</title><link href="https://stumbles.id.au/configuring-android-k-9-mail-from-the-command-line.html" rel="alternate"></link><published>2017-10-06T00:00:00+11:00</published><updated>2017-10-06T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2017-10-06:configuring-android-k-9-mail-from-the-command-line.html</id><summary type="html">&lt;p&gt;I &lt;em&gt;hate&lt;/em&gt; having to re-enter my email settings in a new or reinstalled device.
Secure passwords are particularly painful to transcribe via a phone keyboard,
but it's not just the basic settings. It's also those extras like signature,
disabling notifications and folders that buy you if they're not right. I've set
it all up dozens of times before and will no doubt do it many more.&lt;/p&gt;
&lt;p&gt;On my laptop and desktop I have a permanent, version controlled copy of my email
settings that I can apply automatically. Surely you could do the same on
Android/Replicant? I couldn't find any useful information about people doing
this with configuration management tools like Puppet, Salt, Ansible or anything
else so here's my first attempt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; -x

&lt;span class="c1"&gt;# Set the K-9 incoming and outgoing account details via ADB and SQLite3.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Does not yet *create* the accounts, so you still need to click through the&lt;/span&gt;
&lt;span class="c1"&gt;# accounts wizard and enter dummy information.#&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# K-9 account settings live in an SQLite3 database and have a unique identifier&lt;/span&gt;
&lt;span class="c1"&gt;# for each account. Records in the database look like:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# primkey                                           | value&lt;/span&gt;
&lt;span class="c1"&gt;# ba46a4cf-bde0-4d5d-aa6d-9da12a8367df.transportUri | xxx&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# The transportUri and storeUri represent the server settings and are base64&lt;/span&gt;
&lt;span class="c1"&gt;# encoded, presumably to obfuscate the password.&lt;/span&gt;

&lt;span class="nv"&gt;PREFERENCES_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/data/data/com.fsck.k9/databases/preferences_storage&amp;#39;&lt;/span&gt;
&lt;span class="nv"&gt;ACCOUNT_UUID_PERSONAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ba46a4cf-bde0-4d5d-aa6d-9da12a8367df&amp;#39;&lt;/span&gt;
&lt;span class="nv"&gt;ACCOUNT_UUID_WORK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;83a40e3e-a4ef-4351-a9f2-cd2fb8510654&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# These weird looking URLs came directly from the database (base64 decoded), eg.&lt;/span&gt;
&lt;span class="c1"&gt;# sqlite3 preferences_storage &amp;quot;select * from preferences_storage&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;PERSONAL_STOREURI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;imap+ssl+://PLAIN:ben%2540stumbles.id.au:password1@imap.fastmail.com:993/1%7C&amp;#39;&lt;/span&gt;
&lt;span class="nv"&gt;PERSONAL_TRANSPORTURI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;smtp+ssl+://ben%2540stumbles.id.au:password1:PLAIN@smtp.fastmail.com:465&amp;#39;&lt;/span&gt;
&lt;span class="nv"&gt;WORK_STOREURI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;imap+ssl+://PLAIN:ben%2540sturm.com.au:password2@imap.fastmail.com:993/1%7C&amp;#39;&lt;/span&gt;
&lt;span class="nv"&gt;WORK_TRANSPORTURI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;smtp+ssl+://ben%2540sturm.com.au:password2:PLAIN@smtp.fastmail.com:465&amp;#39;&lt;/span&gt;
&lt;span class="nv"&gt;WORK_SIGNATURE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&amp;lt;~/dotfiles/.signature&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;read&lt;/span&gt; -r -d &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; QUERY &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;Ben Sturmfels&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_PERSONAL}.name.0&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;Personal&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_PERSONAL}.description&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;$(echo -n $PERSONAL_STOREURI | base64)&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_PERSONAL}.storeUri&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;$(echo -n $PERSONAL_TRANSPORTURI | base64)&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_PERSONAL}.transportUri&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;false&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_PERSONAL}.notifyNewMail&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_PERSONAL}.signature.0&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;Archive&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_PERSONAL}.sentFolderName&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;Junk Mail&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_PERSONAL}.spamFolderName&amp;#39;;&lt;/span&gt;


&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;Ben Sturmfels&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_WORK}.name.0&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;Work&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_WORK}.description&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;$(echo -n $WORK_STOREURI | base64)&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_WORK}.storeUri&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;$(echo -n $WORK_TRANSPORTURI | base64)&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_WORK}.transportUri&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;false&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_WORK}.notifyNewMail&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;--&lt;/span&gt;
&lt;span class="s"&gt;${WORK_SIGNATURE}&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_WORK}.signature.0&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;    UPDATE preferences_storage SET VALUE = &amp;#39;Archive&amp;#39; WHERE primkey = &amp;#39;${ACCOUNT_UUID_WORK}.sentFolderName&amp;#39;;&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;

&lt;span class="c1"&gt;# As my config grew, ADB started telling me &amp;quot;error: service name too long&amp;quot;, so I&lt;/span&gt;
&lt;span class="c1"&gt;# had to write the configuration to a shell script, transfer it to the phone and&lt;/span&gt;
&lt;span class="c1"&gt;# run it.&lt;/span&gt;
&lt;span class="nv"&gt;TEMPFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;tempfile&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sqlite3 &lt;/span&gt;&lt;span class="nv"&gt;$PREFERENCES_DATABASE&lt;/span&gt;&lt;span class="s2"&gt; \&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$QUERY&lt;/span&gt;&lt;span class="s2"&gt;\&amp;quot;&amp;quot;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$TEMPFILE&lt;/span&gt;
adb push &lt;span class="nv"&gt;$TEMPFILE&lt;/span&gt; /data/local/tmp
adb shell sh /data/local/tmp/&lt;span class="k"&gt;$(&lt;/span&gt;basename &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEMPFILE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The idea is that you'd plug in your Android/Replicant phone by USB with ADB
debugging enabled and run the above shell script. I gather that you need to kill
K-9 mail for the settings to take effect (long hold on primary button and close K-9).&lt;/p&gt;
&lt;p&gt;Alright, now I'll freely admit that this took quite a bit more time than it
would of to just complete the K-9 settings wizard, but it's the principle that
counts! The great thing about Free Software is that I can have confidence that
I'll be using K-9 for many years to come, so this investment of time is
worthwhile.&lt;/p&gt;
&lt;p&gt;I haven't tried creating accounts from scratch. It could be just
generating a UUID identifier and setting &lt;code&gt;[UUID].accountNumber&lt;/code&gt; to say &lt;code&gt;2&lt;/code&gt; for a
third account. Or there might be some place you need to register the UUID.
That's work for another day.&lt;/p&gt;</summary><category term="android"></category><category term="replicant"></category></entry><entry><title>Disabling Django's password strength checking for development</title><link href="https://stumbles.id.au/disabling-djangos-password-strength-checking-for-development.html" rel="alternate"></link><published>2017-03-05T00:00:00+11:00</published><updated>2017-03-05T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2017-03-05:disabling-djangos-password-strength-checking-for-development.html</id><summary type="html">&lt;p&gt;Django 1.9 comes with a feature that enforces strong passwords. It's an
excellent security feature, but password complexity is &lt;em&gt;not&lt;/em&gt; what you want in
development. Here's how to turn the feature off to allow simple passwords.&lt;/p&gt;
&lt;p&gt;It goes like this. You've created a new database, run the migrations and are
about to create a superuser account:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ python manage.py createsuperuser
Username (leave blank to use &amp;#39;user&amp;#39;):
Email address: user@example.com
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
Password:
...
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Gahhh! All I want to be able to do is use the same one-letter password for all
my development environments. The story is similarly frustrating when creating
new user accounts via the Django Admin. You're probably creating a test account
for a colleague. You want something short and simple.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://stumbles.id.au/images/django-password-too-short.png"
alt="Django administration: Please correct the error below. This password is too short. It must contain at least 8 characters." /&gt;
&lt;figcaption&gt;Django Administration password validation&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The solution is to set the following in your development settings. If you're
following best practises, will be something like &lt;code&gt;settings/local.py&lt;/code&gt; or
&lt;code&gt;settings/dev.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;AUTH_PASSWORD_VALIDATORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Just promise me you won't do this in production!&lt;/p&gt;</summary><category term="django"></category><category term="python"></category></entry><entry><title>Python, FTPS and mis-configured servers</title><link href="https://stumbles.id.au/python-ftps-and-mis-configured-servers.html" rel="alternate"></link><published>2016-12-09T00:00:00+11:00</published><updated>2016-12-09T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2016-12-09:python-ftps-and-mis-configured-servers.html</id><summary type="html">&lt;p&gt;Today's project involves automatically uploading electrical metering data to an
FTPS server (explicit FTP over TLS, otherwise knowns as ESFTP). Shouldn't be a
problem, since Python supports FTPS out of the box. Only it doesn't work. Here's
the code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;ftplib&lt;/span&gt;
&lt;span class="n"&gt;ftp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ftplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FTP_TLS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;host&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ftp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_debuglevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ftp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;directory&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;filename&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ftp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storbinary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;STOR filename&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ftp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After a successful initial connection, the data transfer connection fails:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;*cmd* &amp;#39;PASV&amp;#39;
*resp* &amp;#39;227 Entering Passive Mode (10,200,0,100,255,150).&amp;#39;
*cmd* &amp;#39;CWD collect/CSV&amp;#39;
*resp* &amp;#39;250 CWD command successful&amp;#39;
*cmd* &amp;#39;TYPE I&amp;#39;
*resp* &amp;#39;200 Type set to I&amp;#39;
*cmd* &amp;#39;PASV&amp;#39;
*resp* &amp;#39;227 Entering Passive Mode (10,200,0,100,255,123).&amp;#39;

Traceback (most recent call last):
  File &amp;quot;metering/__main__.py&amp;quot;, line 143, in main
    ftp.storbinary(&amp;#39;STOR {}&amp;#39;.format(filename), stream)
  File &amp;quot;.../lib/python3.5/ftplib.py&amp;quot;, line 503, in storbinary
    with self.transfercmd(cmd, rest) as conn:
  File &amp;quot;.../lib/python3.5/ftplib.py&amp;quot;, line 398, in transfercmd
    return self.ntransfercmd(cmd, rest)[0]
  File &amp;quot;.../lib/python3.5/ftplib.py&amp;quot;, line 793, in ntransfercmd
    conn, size = FTP.ntransfercmd(self, cmd, rest)
  File &amp;quot;.../lib/python3.5/ftplib.py&amp;quot;, line 360, in ntransfercmd
    source_address=self.source_address)
  File &amp;quot;.../lib/python3.5/socket.py&amp;quot;, line 711, in create_connection
    raise err
  File &amp;quot;.../lib/python3.5/socket.py&amp;quot;, line 702, in create_connection
    sock.connect(sa)
OSError: [Errno 113] No route to host
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The most confusing aspect was that the transfer worked perfectly well via FileZilla or Gnome "Connect to Server".&lt;/p&gt;
&lt;p&gt;I eventually noticed a message in the FileZilla logs, &lt;code&gt;Server sent passive reply with unroutable address. Using server address instead&lt;/code&gt;. It turns out that the FTPS server was mis-configured and was replying to the &lt;code&gt;PASV&lt;/code&gt; command with an &lt;em&gt;internal&lt;/em&gt; IP address that was not accessible from the public internet. It seems that this is a common enough configuration issue that some FTP clients detect the problem and use the existing server address instead. Python's FTP client doesn't do this though.&lt;/p&gt;
&lt;p&gt;The solution was to sub-class the &lt;code&gt;FTP_TLS&lt;/code&gt; class and force it to ignore the response:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FTP_TLS_IgnoreHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ftplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FTP_TLS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;makepasv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makepasv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;

&lt;span class="n"&gt;ftp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FTP_TLS_IgnoreHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;host&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;makepasv&lt;/code&gt; method parses the remote server's response to &lt;code&gt;PASV&lt;/code&gt; and returns a host and a port. We're extending this method to throw away the returned host and use the one we already have from the original connection. The underscore is just a convention to indicate that we don't care about the first item in the tuple, the host (it's special in some languages like Prolog, but not in Python). Note of course that this approach doesn't attempt to detect unroutable addresses like FileZilla, it just assumes.&lt;/p&gt;
&lt;p&gt;There are no doubt third-party packages that provide this functionality and more, but for such a small extension, it wasn't worth the hassle.&lt;/p&gt;</summary><category term="python"></category></entry><entry><title>Join us for a swim at Brown Hill</title><link href="https://stumbles.id.au/join-us-for-a-swim-at-brown-hill.html" rel="alternate"></link><published>2016-12-03T00:00:00+11:00</published><updated>2016-12-03T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2016-12-03:join-us-for-a-swim-at-brown-hill.html</id><summary type="html">&lt;p&gt;The water may be a little chilly, but to coincide with today's opening of the
Brown Hill outdoor pool for the summer, we're launching the first page of the &lt;a
href="http://www.brownhill.org.au/"&gt;Brown Hill Community Hub&lt;/a&gt; website — a
page about the pool.&lt;/p&gt;
&lt;p&gt;We've been getting more involved the local community over the past 6 months or
so, following the formation of the Brown Hill Partnership. This is a City of
Ballarat initiative to involve a wide range of community groups in choosing
projects to fund. So far we've seen the publication of the Brown Hill Community
Newsletter, the inaugural Brown Hill Community Festival and today, the launch of
the Brown Hill Community Hub. Over the next few months we'll be extending the
website to include information about local businesses, sporting clubs and
community grounds as well as info about the Hall, the Progress Association, the
History Project and Fire Aware.&lt;/p&gt;
&lt;p&gt;Phwew, that's certainly a lot of "community". But with the weather warming up and the
festive season just around the corner, it's an exciting time of the year to be
out and about. Don't forget your togs!&lt;/p&gt;</summary><category term="brown hill"></category></entry><entry><title>Angular eats my default form values</title><link href="https://stumbles.id.au/angular-eats-my-default-form-values.html" rel="alternate"></link><published>2016-07-26T00:00:00+10:00</published><updated>2016-07-26T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2016-07-26:angular-eats-my-default-form-values.html</id><summary type="html">&lt;p&gt;Today, AngularJS isn't doing what I expect. I've auto-generating a really long
form in Django, and adding &lt;code&gt;ng-model&lt;/code&gt; attributes so I can summarise the form.
Unfortunately, Angular is overwriting my defaults. Here's a shortened example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;ng-app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;testApp&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;ng-controller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;TestCtrl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;colour&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;ng-model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;colour&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Red&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;green&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;selected&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Green&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Blue&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;You favourite colour is &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;color: {{ colour }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ colour }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="nx"&gt;angular&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;testApp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;TestCtrl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;$scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;colour&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Colour is: &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It looks something like this:&lt;/p&gt;
&lt;figure style="float: none; overflow: auto;"&gt;
&lt;img src="https://stumbles.id.au/images/angular-favourite-colour.png" alt="" /&gt;
&lt;/figure&gt;

&lt;p&gt;After marvelling at how little code you need to do something useful, I note that
although the HTML form has Green "selected", the select box is initially has no
value selected. Why is that? The reason is that once Angular has loaded, it sets
the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; field to its own initial value of &lt;code&gt;undefined&lt;/code&gt;, overwriting the
HTML default value.&lt;/p&gt;
&lt;h2&gt;Enter: Angular Auto Value&lt;/h2&gt;
&lt;p&gt;Thankfully, John Wright and Glauco Custódio have both come up with libraries to
make Angular do what we expect. These are
&lt;a href="https://github.com/johngeorgewright/angular-auto-value"&gt;Angular Auto Value&lt;/a&gt; and
&lt;a href="https://github.com/glaucocustodio/angular-initial-value"&gt;Angular Initial Value&lt;/a&gt;
respectively. Here's our code after modification to use Angular Auto Value
(extra &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; element and &lt;code&gt;'auto-value'&lt;/code&gt; argument to &lt;code&gt;angular.module&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;ng-app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;testApp&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;ng-controller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;TestCtrl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;colour&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;ng-model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;colour&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Red&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;green&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;selected&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Green&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Blue&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;You favourite colour is &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;color: {{ colour }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ colour }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Extra script --&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://cdn.jsdelivr.net/angular.auto-value/latest/angular-auto-value.min.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="nx"&gt;angular&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;testApp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;auto-value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;// Extra &amp;#39;auto-value&amp;#39; dependency&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;TestCtrl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;$scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;colour&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Colour is: &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I often reach for a pinch of Angular as a "jQuery substitute". A touch of
auto-completion or form validation adds a lot of value to a server-side web app.
Like jQuery, there's no assumption that your whole system is written in
JavaScript. Unlike jQuery, it remains maintainable as you add more and more
features.&lt;/p&gt;</summary><category term="angular"></category><category term="javascript"></category></entry><entry><title>Bruising first experience with ClojureScript</title><link href="https://stumbles.id.au/bruising-first-experience-with-clojurescript.html" rel="alternate"></link><published>2016-05-20T00:00:00+10:00</published><updated>2016-05-20T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2016-05-20:bruising-first-experience-with-clojurescript.html</id><summary type="html">&lt;p&gt;"Bruised" is a little how I feel about my first experience porting a program to
ClojureScript — configuring production builds has a few traps for newcomers.
That said, I am very pleased with the results and excited to do more working with ClojureScript.&lt;/p&gt;
&lt;p&gt;My Dad is a soil conservation officer with &lt;abbr title="Department of Environment, Land, Water and Planning"&gt;DELWP&lt;/abbr&gt;. Many years ago I wrote a small Java applet for use in his workshops. It help farmers estimate how much water can flow down an earthen channel without causing soil erosion. Here's a screenshot of the original Java version:&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://stumbles.id.au/images/channel-flow-calculator-java.png" alt="" /&gt;
&lt;figcaption&gt;Channel flow calculator (Java version)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Despite its simplicity, the channel flow calculator has been a useful and popular tool. The only drawback has been the inconsistent support for Java applets in web browsers; particularly in government departments. To solve this, we recently decided to rewrite it in HTML and JavaScript. It's also a chance to indulge my interest in ClojureScript!&lt;/p&gt;
&lt;h2&gt;Why ClojureScript?&lt;/h2&gt;
&lt;p&gt;Well, less JavaScript for a start. Rather than trying to incrementally fix
JavaScript's lack of modules, non-existent standard library, unexpected type
coercion rules and inconsistent browser support, ClojureScript starts afresh
with solid language fundamentals and good design choices based the Clojure Lisp
dialect.&lt;/p&gt;
&lt;p&gt;ClojureScript compiles down to lowest-common-denominator JavaScript, meaning
that my code will run consistently on any modern browser. I can confidently use
the full breadth of the elegant language features, powerful data-types and the
convenient standard library. I get real module support. Last but not least,
thanks to the optimising compiler, I can fearlessly pull in large libraries,
knowing that my visitors will only download the code they need.&lt;/p&gt;
&lt;h2&gt;ClojureScript trap 1: Unwanted &lt;code&gt;:main&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The basic translation from Java to ClojureScript was straightforward; even the
Java and JavaScript canvas-drawing APIs are nearly identical. After some messing
around, I even managed to get &lt;em&gt;Lein&lt;/em&gt;, Clojure's de-facto build tool,
auto-rebuilding and live-updating my web browser (via plugins &lt;em&gt;lein-cljsbuild&lt;/em&gt;
and &lt;em&gt;lein-figwheel&lt;/em&gt;). The trouble came when I needed to produce the optimised
production version for publication.&lt;/p&gt;
&lt;p&gt;ClosureScript utilises the Google Closure compiler to remove unused JavaScript code, turning around 1MB into 90kB (20kB Gzipped). All this by simply setting &lt;code&gt;:optimizations :advanced&lt;/code&gt; in the build settings.&lt;/p&gt;
&lt;p&gt;The first trap was that the &lt;code&gt;:main&lt;/code&gt; setting used in development builds can't be left in place when you switch on &lt;code&gt;:optimizations :advanced&lt;/code&gt;. Not that Clojure gives up this information willingly. Here's my faulty build script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;;; build.clj&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;cljs.build.api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cljs.build.api/build&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;src&amp;quot;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:main&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;channel_flow.core&lt;/span&gt;
   &lt;span class="ss"&gt;:output-to&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;out/main.js&amp;quot;&lt;/span&gt;
   &lt;span class="ss"&gt;:optimizations&lt;/span&gt; &lt;span class="ss"&gt;:advanced&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/exit&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And here's the output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ java -cp cljs-1.8.51 clojure.main build.clj
Exception in thread &amp;quot;main&amp;quot; java.lang.AssertionError: Assert failed: No file for namespace channel_flow.core exists
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Not so helpful. That error kept me busy for nearly two hours, including diving
into the ClojureScript source code to figure out what was going on. After
removing the &lt;code&gt;:main&lt;/code&gt; setting, the error disappears. It's worth noting that if
you're building through &lt;em&gt;lein-cljsbuild&lt;/em&gt;, it does the right thing and ignores
the unnecessary &lt;code&gt;:main&lt;/code&gt; setting.&lt;/p&gt;
&lt;h2&gt;ClojureScript trap 2: Symbol renaming&lt;/h2&gt;
&lt;p&gt;The second trap was that after switching from &lt;code&gt;:optimizations :simple&lt;/code&gt; to &lt;code&gt;:optimizations :advanced&lt;/code&gt;, I was seeing JavaScript errors like &lt;code&gt;document.ga is undefined&lt;/code&gt;. It's a bizarre feeling when your code was working only moments ago. Optimisation isn't meant to change the &lt;em&gt;behaviour&lt;/em&gt; of code, right?&lt;/p&gt;
&lt;p&gt;Turns out it does, if you're not abiding by certain rules. I was using the following HTML form:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;mannings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Top width: &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;number&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;w1&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
…
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I was referencing this in ClojureScript to trigger the recalculation when inputs were changed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;;; Don&amp;#39;t do this&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.addEventListener&lt;/span&gt; &lt;span class="nv"&gt;document.mannings&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;input&amp;quot;&lt;/span&gt; &lt;span class="nv"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Google Closure compiler was helpfully &lt;a href="https://developers.google.com/closure/compiler/docs/api-tutorial3#mixed"&gt;renaming&lt;/a&gt; &lt;code&gt;document.mannings&lt;/code&gt; to &lt;code&gt;document.ga&lt;/code&gt; to make my code shorter; thereby breaking the reference to the HTML form:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// &amp;quot;Optimised&amp;quot; (broken) JavaScript&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ga&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;input&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;Oe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;All this makes sense in hindsight; I just didn't expect that enabling
compilation optimisation do anything other than slow down the build and produce
a smaller output file.&lt;/p&gt;
&lt;p&gt;The solution is to explicitly advise the Google Closure compiler that
&lt;code&gt;document.mannings&lt;/code&gt; is something external to this file and not be renamed. Create an &lt;code&gt;externs.js&lt;/code&gt; file that references the variables:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// externs.js&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mannings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;w1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;w2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Declare the externs to the compiler in your build script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;:optimizations :advanced
:externs [&amp;quot;externs.js&amp;quot;]
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;The results&lt;/h2&gt;
&lt;figure&gt;
&lt;a href="http://clem.sturmfels.com.au/channel-flow"&gt;&lt;img src="https://stumbles.id.au/images/channel-flow-calculator-clojurescript.png" alt="" /&gt;&lt;/a&gt;
&lt;figcaption&gt;Channel flow calculator (HTML/JavaScript version)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After clearing these initial hurdles, the updated
&lt;a href="http://clem.sturmfels.com.au/channel-flow/"&gt;channel flow calculator&lt;/a&gt; is now
working well (picture below). A few weeks on, now that my frustration has faded,
I'm actually really looking forward to my next ClojureScript project.&lt;/p&gt;</summary><category term="clojure"></category><category term="clojurescript"></category><category term="lisp"></category></entry><entry><title>Nginx dynamic image resizing with caching</title><link href="https://stumbles.id.au/nginx-dynamic-image-resizing-with-caching.html" rel="alternate"></link><published>2016-04-21T00:00:00+10:00</published><updated>2016-04-21T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2016-04-21:nginx-dynamic-image-resizing-with-caching.html</id><summary type="html">&lt;p&gt;At &lt;a href="http://www.sturm.com.au"&gt;Sturm&lt;/a&gt; we build web apps that perform beautifully on both tiny mobile devices and huge desktop monitors. To achieve these, we use Nginx to resize images on-the-fly. This approach was used in our recent &lt;a href="https://study.federation.edu.au"&gt;Course Finder&lt;/a&gt; project with Federation University.&lt;/p&gt;
&lt;p&gt;The Nginx web server comes with a handy &lt;a href="http://nginx.org/en/docs/http/ngx_http_image_filter_module.html#image_filter"&gt;image filter&lt;/a&gt; module, but most tutorials don't configure it with performance in mind. Performance means caching.&lt;/p&gt;
&lt;p&gt;Let's start with the basics:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Basic image resizing server, no caching.&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;&amp;quot;^/media/(?&amp;lt;width&amp;gt;\d+)/(?&amp;lt;image&amp;gt;.+)$&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;alias&lt;/span&gt; &lt;span class="s"&gt;/var/www/images/&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;image_filter&lt;/span&gt; &lt;span class="s"&gt;resize&lt;/span&gt; &lt;span class="nv"&gt;$width&lt;/span&gt; &lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;image_filter_jpeg_quality&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;image_filter_buffer&lt;/span&gt; &lt;span class="s"&gt;8M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Don't forget to reload your Nginx configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo service nginx force-reload
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This server block will respond to a request like &lt;code&gt;http://example.com/media/768/mountains.jpg&lt;/code&gt; with the file &lt;code&gt;/var/www/images/mountains.jpg&lt;/code&gt;. It will resize the image down to 768 pixels wide if necessary and compress it using JPEG quality 75. Requests where the original image is 8MB will be rejected.&lt;/p&gt;
&lt;p&gt;Unfortunately our simple example doesn't perform so well, since it does a resize for every single request. Let's test it using &lt;a href="https://httpd.apache.org/docs/current/programs/ab.html"&gt;Apache Bench&lt;/a&gt; (from &lt;code&gt;apache2-utils&lt;/code&gt; on a Debian-based GNU/Linux). We'll make 100 requests using 5 concurrent connections:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ ab -n &lt;span class="m"&gt;100&lt;/span&gt; -c &lt;span class="m"&gt;5&lt;/span&gt; http://example.com/media/768/mountains.jpg
...
Requests per second:    &lt;span class="m"&gt;4&lt;/span&gt;.14 &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="c1"&gt;#/sec] (mean)&lt;/span&gt;
...
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Oh no, only four requests per second!&lt;/p&gt;
&lt;p&gt;The solution is to cache the resized image so that the work is only done once a day. Since Nginx can't cache and resize at the same time, we'll need a trick. That trick is to use &lt;em&gt;two&lt;/em&gt; server blocks. The first will be an internal-onlyserver that resize images. The second will be a public server that proxies requests to our internal server and then caches the result:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Internal image resizing server.&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;&amp;quot;^/media/(?&amp;lt;width&amp;gt;\d+)/(?&amp;lt;image&amp;gt;.+)$&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;alias&lt;/span&gt; &lt;span class="s"&gt;/var/www/images/&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;image_filter&lt;/span&gt; &lt;span class="s"&gt;resize&lt;/span&gt; &lt;span class="nv"&gt;$width&lt;/span&gt; &lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;image_filter_jpeg_quality&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;image_filter_buffer&lt;/span&gt; &lt;span class="s"&gt;8M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;proxy_cache_path&lt;/span&gt; &lt;span class="s"&gt;/tmp/nginx-images-cache/&lt;/span&gt; &lt;span class="s"&gt;levels=1:2&lt;/span&gt; &lt;span class="s"&gt;keys_zone=images:10m&lt;/span&gt; &lt;span class="s"&gt;inactive=24h&lt;/span&gt; &lt;span class="s"&gt;max_size=100m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Public-facing cache server.&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Only serve widths of 768 or 1920 so we can cache effectively.&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;&amp;quot;^/media/(?&amp;lt;width&amp;gt;(768|1920))/(?&amp;lt;image&amp;gt;.+)$&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# Proxy to internal image resizing server.&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8888/media/&lt;/span&gt;&lt;span class="nv"&gt;$width/$image&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache&lt;/span&gt; &lt;span class="s"&gt;images&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache_valid&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="s"&gt;24h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/media&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# Nginx needs you to manually define DNS resolution when using&lt;/span&gt;
        &lt;span class="c1"&gt;# variables in proxy_pass. Creating this dummy location avoids that.&lt;/span&gt;
        &lt;span class="c1"&gt;# The error is: &amp;quot;no resolver defined to resolve localhost&amp;quot;.&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8888/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that we're caching the resized images, Apache Bench shows a slow first request, but thereafter serves over 7000 requests per second!&lt;/p&gt;</summary><category term="nginx"></category></entry><entry><title>Lots of love for Conservancy at LCA 2016</title><link href="https://stumbles.id.au/lots-of-love-for-conservancy-at-lca-2016.html" rel="alternate"></link><published>2016-02-09T00:00:00+11:00</published><updated>2016-02-09T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2016-02-09:lots-of-love-for-conservancy-at-lca-2016.html</id><summary type="html">&lt;p&gt;It was wonderful to see an outpouring of support for Conservancy last week at &lt;a href="http://2016.linux.conf.au/"&gt;LCA 2016&lt;/a&gt;. A number of speakers highlighted Conservancy during their talks, Bradley Kuhn gave a &lt;a href="http://mirror.linux.org.au/linux.conf.au/2016/05_Friday/Costa_Hall/Copyleft_For_the_Next_Decade_A_Comprehensive_Plan.webm"&gt;talk about GPL compliance&lt;/a&gt;, we held a Supporter lunch (photo below), and both the conference organisers and the new Linux Australia President highlighted Conservancy in the conference closing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Conservancy Supporter lunch" src="https://stumbles.id.au/images/conservancy-supporter-lunch-lca2016.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sfconservancy.org/"&gt;Software Freedom Conservancy&lt;/a&gt; is a tiny organisation doing amazing work to support the development of Free Software. They assist community free software projects with their administrative work (&lt;a href="http://sfconservancy.org/members/services/"&gt;financial, legal, etc.&lt;/a&gt;) so that the developers can concentrate on making great software. Conservancy's &lt;a href="http://sfconservancy.org/members/current/"&gt;member projects&lt;/a&gt; include BusyBox, Git, Inkscape, BusyBox, Mercurial, PyPy, Samba, Twisted and Wine.&lt;/p&gt;</summary><category term="conservancy"></category><category term="free software"></category></entry><entry><title>Speaking on MediaGoblin at linux.conf.au 2016</title><link href="https://stumbles.id.au/speaking-on-mediagoblin-at-linuxconfau-2016.html" rel="alternate"></link><published>2016-02-02T00:00:00+11:00</published><updated>2016-02-02T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2016-02-02:speaking-on-mediagoblin-at-linuxconfau-2016.html</id><summary type="html">&lt;p&gt;I'm very excited to be speaking about &lt;a href="http://mediagoblin.org/"&gt;GNU MediaGoblin&lt;/a&gt;
this Friday 5 February at
&lt;a href="http://linux.conf.au/schedule/30269/view_talk?day=friday"&gt;linux.conf.au&lt;/a&gt; in
Geelong, Australia. MediaGoblin is a media publishing tool for artists.&lt;/p&gt;
&lt;p&gt;I began using MediaGoblin nearly four years ago as a way to share photos of our
new baby with our family and close friends — a popular use case from all
accounts! The two compelling features at the time were the ability to upload
both images, audio and video to the same service and the automatic resizing,
transcoding and compression. Being a technical person, I theoretically had all
the tools and knowledge to convert the media and publish the HTML, but in
practise it simply wouldn't have happened. With MediaGoblin, the publishing
process is a joyful one.&lt;/p&gt;
&lt;p&gt;A lot has changed in MediaGoblin since then. GStreamer 1.0 has brought more
robust transcoding with support for more formats. The client-to-server API
allows publishing media from a pump.api client. There's great new features for
admin users including reprocessing of failed transcoding, command-line uploads,
bulk uploads, reporting/moderation, PDF/document media type, 3D model media type
and much more. These advances have allowed me to also use MediaGoblin to publish
audio, video and slides from my &lt;a href="https://media.sturm.com.au"&gt;past presentations&lt;/a&gt;
on Emacs, Python, free software and software patents.&lt;/p&gt;
&lt;p&gt;Thanks to Jessica Tallon's work, funded entirely by your donations, the project
is also looking forward to announcing federation in the near future. This will
bring social features such as sharing, commenting and notifications across
different distinct MediaGoblin servers. I can't wait to talk more about these
exciting developments on Friday. I'm very grateful to Chris Webber and Jessica
for walking me through some of the details of their current work.&lt;/p&gt;
&lt;p&gt;Hope to see you at 10:40am in D4.303 Costa Theatre!&lt;/p&gt;</summary><category term="mediagoblin"></category><category term="python"></category></entry><entry><title>Grunt build hangs on ngtemplates:dist</title><link href="https://stumbles.id.au/grunt-build-hangs-on-ngtemplatesdist.html" rel="alternate"></link><published>2015-10-28T00:00:00+11:00</published><updated>2015-10-28T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2015-10-28:grunt-build-hangs-on-ngtemplatesdist.html</id><summary type="html">&lt;p&gt;I just ran into a strange situation where &lt;code&gt;grunt build&lt;/code&gt; on a Yeoman Angular
project was hanging at the following step:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ grunt build
⋮
Running &amp;quot;ngtemplates:dist&amp;quot; (ngtemplates) task
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The cause turned out to be an extra double quote symbol in one of the HTML view
templates:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;onsubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;window.location=&amp;#39;#/results/&amp;#39; + query.value; return false;&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A &lt;code&gt;git bisect&lt;/code&gt; helped reveal the problematic revision, but even so it took a lot
of trial and error to find the cause. The challenge was that Grunt gave no clues
as to what might be causing it to stall. It also didn't crash, rather sat there
in an infinite loop merrily consuming the CPU.&lt;/p&gt;
&lt;p&gt;Interesting, the problem doesn't occur for just any old extra quote, so I'd
guess that means that Angular is trying to parse this snippet of JavaScript and
getting stuck. And yes, I agree that this is a very un-Angular way to submit a
form. :)&lt;/p&gt;</summary><category term="angular"></category><category term="javascript"></category><category term="grunt"></category></entry><entry><title>Auto-starting Emacs smerge-mode for Git</title><link href="https://stumbles.id.au/auto-starting-emacs-smerge-mode-for-git.html" rel="alternate"></link><published>2015-09-28T00:00:00+10:00</published><updated>2015-09-28T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2015-09-28:auto-starting-emacs-smerge-mode-for-git.html</id><summary type="html">&lt;p&gt;I love that Emacs notices merge conflicts in Bazaar versioned files,
highlighting the differences in green and pink. It's a small thing, but I really
miss this automation for when working with Git — I have to manually type &lt;code&gt;M-x
smerge-mode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Emacs smerge-mode highlighting a conflict" src="https://stumbles.id.au/images/emacs-smerge-git.png" /&gt;&lt;/p&gt;
&lt;p&gt;The feature works by looking for conflict files and markers whenever you open a Bazaar versioned file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;;;; Copied from Emacs 24.3.1: vc/vc-bzr.el.gz:472&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;vc-bzr-find-file-hook&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;buffer-file-name&lt;/span&gt;
             &lt;span class="c1"&gt;;; FIXME: We should check that &amp;quot;bzr status&amp;quot; says &amp;quot;conflict&amp;quot;.&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file-exists-p&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;concat&lt;/span&gt; &lt;span class="nv"&gt;buffer-file-name&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;.BASE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file-exists-p&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;concat&lt;/span&gt; &lt;span class="nv"&gt;buffer-file-name&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;.OTHER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file-exists-p&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;concat&lt;/span&gt; &lt;span class="nv"&gt;buffer-file-name&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;.THIS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
             &lt;span class="c1"&gt;;; If &amp;quot;bzr status&amp;quot; says there&amp;#39;s a conflict but there are no&lt;/span&gt;
             &lt;span class="c1"&gt;;; conflict markers, it&amp;#39;s not clear what we should do.&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;save-excursion&lt;/span&gt;
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;goto-char&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;point-min&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;re-search-forward&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;^&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; &amp;quot;&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="c1"&gt;;; TODO: the merge algorithm used in `bzr merge&amp;#39; is nicely configurable,&lt;/span&gt;
    &lt;span class="c1"&gt;;; but the one in `bzr pull&amp;#39; isn&amp;#39;t, so it would be good to provide an&lt;/span&gt;
    &lt;span class="c1"&gt;;; elisp function to remerge from the .BASE/OTHER/THIS files.&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;smerge-start-session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;after-save-hook&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;vc-bzr-resolve-when-done&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;There are unresolved conflicts in this file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above looks a little complicated, but the important part is the line
&lt;code&gt;(smerge-start-session)&lt;/code&gt;. This starts &lt;code&gt;smerge-mode&lt;/code&gt; to highlight merge
conflicts.&lt;/p&gt;
&lt;p&gt;Let's do something similar with Git by defining a function
&lt;code&gt;vc-git-find-file-hook&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;vc-git-find-file-hook&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;save-excursion&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;goto-char&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;point-min&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;re-search-forward&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;^&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; &amp;quot;&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;smerge-start-session&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Emacs &lt;code&gt;vc-mode&lt;/code&gt; has a standard interface for version control systems, so simply
defining this function is all we need to do. Emacs will look for and run the
function if it exists (it doesn't by default).&lt;/p&gt;
&lt;p&gt;Similar to the Bazaar version, this function looks for conflict marker text in the file, and starts &lt;code&gt;smerge-mode&lt;/code&gt; if it finds any. The Git version is shorter because Git deals with conflicts differently, and also because I haven't hit any edge cases… yet.&lt;/p&gt;</summary><category term="emacs"></category><category term="lisp"></category></entry><entry><title>John Oliver on Software Patents</title><link href="https://stumbles.id.au/john-oliver-on-software-patents.html" rel="alternate"></link><published>2015-06-05T00:00:00+10:00</published><updated>2015-06-05T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2015-06-05:john-oliver-on-software-patents.html</id><summary type="html">&lt;p&gt;A few years back, only the legal and computing industry understood the dangers
of software patents. Today I was amazed watch John Oliver's detailed and
hilarious treatment of the subject on
&lt;a href="https://www.youtube.com/watch?v=3bxcc3SM_KA"&gt;Last Week Tonight&lt;/a&gt; (YouTube). The
narrator summed up our concerns beautifully:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the last decade, the number of software patents has skyrocketed. The issue
here, say experts, is that while patents for machines tend to be fairly
specific, software patents can be so broad and vague that they may give
someone the ability to later claim ownership for inventions they never dreamed
of at the time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I suppose it's tempting for reporters to dwell on patent trolls, but many of the
most harmful patents come from large well-known companies and consortia. These
patents make important fields such as video encoding almost unbearable to work
in and leave the public all the poorer for lack of compatibly and choice. That
aside, it's always satisfying to see Uniloc being berated for it's misuse of the
patent system.&lt;/p&gt;</summary><category term="patents"></category></entry><entry><title>Rock Your Emacs video from LibrePlanet</title><link href="https://stumbles.id.au/rock-your-emacs-video-from-libreplanet.html" rel="alternate"></link><published>2015-04-18T00:00:00+10:00</published><updated>2015-04-18T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2015-04-18:rock-your-emacs-video-from-libreplanet.html</id><summary type="html">&lt;p&gt;I've recently returned from &lt;a href="https://libreplanet.org/2015/"&gt;LibrePlanet 2015&lt;/a&gt;
where I presented a workshop called
&lt;a href="https://libreplanet.org/2015/program/"&gt;&lt;em&gt;Rock Your Emacs&lt;/em&gt;&lt;/a&gt;. The workshop is
designed to help Emacs users over the initial barriers to writing their own
customisations with Lisp code. While we're waiting for
&lt;a href="https://media.libreplanet.org"&gt;official videos&lt;/a&gt; to be published, here's the
video, slides and exercises from my workshop.&lt;/p&gt;
&lt;video src="http://www.sturm.com.au/2015/talks/rock-your-emacs-libreplanet/rock-your-emacs.webm" controls width="640" height="480" style="box-shadow: rgba(0, 0, 0, 0.5) 1px 1px 2px;"&gt;&lt;/video&gt;

&lt;h1&gt;Rock Your Emacs, LibrePlanet 2015&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.sturm.com.au/2015/talks/rock-your-emacs-libreplanet/rock-your-emacs.pdf"&gt;Slides&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.sturm.com.au/2015/talks/rock-your-emacs-libreplanet/rock-your-emacs.el"&gt;Exercises&lt;/a&gt; (Emacs Lisp)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do you love Emacs, but have never understood the strange code with lots of
brackets? You're missing out on one of the great joys of Emacs — customizing
it to work exactly the way you want. It turns out that Emacs is a full Lisp
code interpreter, so once you know a little Emacs Lisp, almost anything is
possible.&lt;/p&gt;
&lt;p&gt;After attending this tutorial, you will know how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;read basic Emacs Lisp code&lt;/li&gt;
&lt;li&gt;modify Emacs Lisp code as well as writing your own&lt;/li&gt;
&lt;li&gt;make persistent customizations to your Emacs&lt;/li&gt;
&lt;li&gt;bind your favourite commands to keys&lt;/li&gt;
&lt;li&gt;answer your own questions with the amazing help system&lt;/li&gt;
&lt;li&gt;use built-in Emacs Lisp development tools like the debugger&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Programming experience is helpful but not required. Bring your Emacs and type
along.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ben Sturmfels is a software engineer and free software activist from Ballarat,
Australia. He organises Free Software Melbourne, leads the End Software Patents
Australia campaign, rides bikes, flies kites and grows vegetables.&lt;/em&gt;&lt;/p&gt;</summary><category term="emacs"></category><category term="lisp"></category><category term="libreplanet"></category><category term="talks"></category></entry><entry><title>Controlling fan speed on Thinkpad X60</title><link href="https://stumbles.id.au/controlling-fan-speed-on-thinkpad-x60.html" rel="alternate"></link><published>2014-04-09T00:00:00+10:00</published><updated>2014-04-09T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2014-04-09:controlling-fan-speed-on-thinkpad-x60.html</id><summary type="html">&lt;p&gt;There’s just one minor annoyance with my new LibreBoot Thinkpad X60 from &lt;a href="http://shop.gluglug.org.uk/product/ibm-lenovo-thinkpad-x60-coreboot/"&gt;GLUGLUG&lt;/a&gt;. Working it hard causes it to overheat and shut down. Thankfully, the fan turns out to have much more capability than the defaults allow; all but solving this problem.&lt;/p&gt;
&lt;p&gt;In &lt;a href="http://trisquel.info/"&gt;Trisquel GNU/Linux&lt;/a&gt; 6.0, the X60′s automatic fan controller runs at one of eight preset speeds (0-7), depending on the temperature. About 10 mins with both CPUs running at 100% will cause it to overheat (eg. two copies of &lt;code&gt;python -c"while True: pass"&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;As described on &lt;a href="http://www.thinkwiki.org/wiki/How_to_control_fan_speed"&gt;ThinkWiki&lt;/a&gt;, you can manually override the fan control by adding the line options &lt;code&gt;thinkpad_acpi fan_control=1&lt;/code&gt; to &lt;code&gt;/etc/modprobe.d/thinkpad_acpi.conf&lt;/code&gt;. Then after rebooting and as root, you can set the fan to run at maximum speed (~3700 RPM) with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;echo level 7 &amp;gt; /proc/acpi/ibm/fan
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Only that’s not the true maximum speed. You can actually disable the rotation speed regulation and just supply full voltage to the fan (~4900 RPM):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;echo level full-speed &amp;gt; /proc/acpi/ibm/fan
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This setting is a bit noisier, but allows both CPUs to run at 100% with no signs of overheating. A good result.&lt;/p&gt;
&lt;p&gt;Next I need to figure out how figure out how to enable this automatically when the machine gets hot. I’ve tried thinkfan, but it seems to only allow the default 0-7 speeds used by the default fan control.&lt;/p&gt;</summary><category term="libreboot"></category></entry><entry><title>Raising concerns about ACTA and TPP</title><link href="https://stumbles.id.au/raising-concerns-about-acta-and-tpp.html" rel="alternate"></link><published>2012-07-14T00:00:00+10:00</published><updated>2012-07-14T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2012-07-14:raising-concerns-about-acta-and-tpp.html</id><summary type="html">&lt;p&gt;The recent resounding defeat of ACTA in the European Parliament has given me a lot of hope. Many citizens took the time to contact members of parliament and this seems to have made all the difference. I hope that Australian can also reject this harmful agreement. That won’t happen without some effort though.&lt;/p&gt;
&lt;p&gt;This morning I had the opportunity to meet and speak with Australian Government MP Catherine King. I raised concerns about the ACTA and TPP agreements. She acknowledged my concerns and agreed to contact Minister Stephen Conroy about these issues. Here are the points I provided:&lt;/p&gt;
&lt;h2&gt;ACTA&lt;/h2&gt;
&lt;p&gt;Please defend our freedom by voting down this harmful agreement.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Negotiated in secret for over two years&lt;/li&gt;
&lt;li&gt;Uses the guise of reducing commercial international-level copyright and trademark infringement (an acceptable aim) to introduce provisions that restrict the freedom of citizens&lt;/li&gt;
&lt;li&gt;Serves the interest of large incumbent music and movie companies, preserving their outdated business models&lt;/li&gt;
&lt;li&gt;Punishes Internet users with disconnection if accused of sharing&lt;/li&gt;
&lt;li&gt;Prohibits software that circumvents digital-restrictions on text/audio/video (ie. “you must only use our locked-down e-book reader software to read this book”)&lt;/li&gt;
&lt;li&gt;Encourages a culture of surveillance and suspicion in which freedom and creativity are seen as dangerous.&lt;/li&gt;
&lt;li&gt;Following a huge public backlash, the European Parliament recently voted ACTA down, 478 votes to 39.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;TPP&lt;/h2&gt;
&lt;p&gt;Please speak out against the nature and scope of this agreement.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Completely non-transparent process – NGOs could make submissions at stakeholder forums knowing almost nothing about the scope of the agreement&lt;/li&gt;
&lt;li&gt;Leaked draft indicates that United States is using the TPP to spread it’s draconian copyright and patent laws around the world.&lt;/li&gt;
&lt;li&gt;Expected to increase the duration and scope of copyright and patents and adding harsher infringement penalties. These laws are already a long way in the favour of publishing and media companies to the detriment of authors, artists and consumers.&lt;/li&gt;
&lt;li&gt;Expected to explicitly include areas of computation and information processing in patentable subject matter (aka. software patents). These patents don’t work as a way to encourage innovation, actually stifling the Australian software industry (see our &lt;a href="http://www.sturm.com.au/resources/patents-petition.pdf"&gt;2010 petition&lt;/a&gt; which received 1000 signatures).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Update 21 July 2012&lt;/strong&gt;: Looks like I wasn’t paying attention. Australia signed up to ACTA in October 2011. The TPP is definitely still being negotiated though.&lt;/p&gt;</summary><category term="acta"></category><category term="tpp"></category></entry><entry><title>Choice, please speak out against DRM</title><link href="https://stumbles.id.au/choice-please-speak-out-against-drm.html" rel="alternate"></link><published>2010-01-08T00:00:00+11:00</published><updated>2010-01-08T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2010-01-08:choice-please-speak-out-against-drm.html</id><summary type="html">&lt;p&gt;*The following is a letter written to &lt;a href="http://www.choice.com.au/"&gt;Choice&lt;/a&gt;, the leading Australian consumer advocacy organisation.&lt;/p&gt;
&lt;p&gt;I urge Choice to speak out against products like the Amazon Kindle electronic book reader. They use Digital Restrictions Management (DRM) technology to restrict your basic freedoms.&lt;/p&gt;
&lt;p&gt;The Kindle uses DRM technology to give Amazon full control over what the customer can and can’t do with the product. It allows many freedoms of traditional books to be stripped off, such as sharing the book with friend or giving the book away when you’re finished with it. DRM also allows monitoring and censoring of what you read. For example, Amazon recently remotely deleted copies of George Orwell’s “Animal Farm” and “1984″. The customers involved were given no notice or choice. The breathtakingly irony is that “1984″ describes a fictional society where citizens are under constant surveillance and control.&lt;/p&gt;
&lt;p&gt;Understandably, devices like the Kindle are widely considered to be unethical. DRM is also being added to many products like phones, music players, digital music, films and books. The result is we consumers lose. Please help defend our rights by considering these issues in future reviews and campaigns.&lt;/p&gt;
&lt;p&gt;More information about DRM is available at the &lt;a href="http://www.defectivebydesign.org/"&gt;Defective By Design&lt;/a&gt; website.&lt;/p&gt;</summary><category term="drm"></category></entry></feed>