<?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>2025-04-07T00:00:00+10:00</updated><subtitle>Free software, coding and activism</subtitle><entry><title>Disabling the “HP-Setup” wifi on an HP LaserJet</title><link href="https://stumbles.id.au/disabling-the-hp-setup-wifi-on-an-hp-laserjet.html" rel="alternate"></link><published>2025-04-07T00:00:00+10:00</published><updated>2025-04-07T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2025-04-07:/disabling-the-hp-setup-wifi-on-an-hp-laserjet.html</id><summary type="html">&lt;p&gt;Our home printer, an HP LaserJet Pro MFP M281fdw, constantly broadcasts a wifi network called “HP-Setup&amp;gt;40-M281”. Why does it need to do that though? We only ever plug it in by USB.&lt;/p&gt;
&lt;p&gt;I tried every option I could find from the built-in screen on the printer to disable this …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Our home printer, an HP LaserJet Pro MFP M281fdw, constantly broadcasts a wifi network called “HP-Setup&amp;gt;40-M281”. Why does it need to do that though? We only ever plug it in by USB.&lt;/p&gt;
&lt;p&gt;I tried every option I could find from the built-in screen on the printer to disable this wifi. I also tried a factory reset, but no luck. Turns out there's a trick.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Enable and the “Wi-Fi Direct” feature and then disable it again.&lt;/p&gt;
&lt;p&gt;Enabling activates a new “DIRECT-40-HP M281” network and deactivates the “HP-Setup&amp;gt;40-M281” network. Disabling again will deactivate the DIRECT network but not re-activate the HP Setup one. Problem solved!&lt;/p&gt;</content><category term="Technology"></category><category term="printers"></category></entry><entry><title>Talks I've enjoyed (August 2022)</title><link href="https://stumbles.id.au/talks-ive-enjoyed-august-2022.html" rel="alternate"></link><published>2022-08-18T00:00:00+10:00</published><updated>2022-08-18T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2022-08-18:/talks-ive-enjoyed-august-2022.html</id><summary type="html">&lt;h1&gt;&lt;a href="https://www.youtube.com/watch?v=ShEez0JkOFw"&gt;Tim Ewald - Clojure: Programming with Hand Tools (2013)&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Great talk. I really connected with Tim's need for tangible, non-computing
activities like woodworking — a passion I share. I'd wondered if this was all
just nostalgia and romance, but Tim makes some really good points about the
fundamental tradeoffs in automation.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Prefer …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;h1&gt;&lt;a href="https://www.youtube.com/watch?v=ShEez0JkOFw"&gt;Tim Ewald - Clojure: Programming with Hand Tools (2013)&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Great talk. I really connected with Tim's need for tangible, non-computing
activities like woodworking — a passion I share. I'd wondered if this was all
just nostalgia and romance, but Tim makes some really good points about the
fundamental tradeoffs in automation.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Prefer to avoid YouTube? You can &lt;a href="https://invidious.snopyta.org/watch?v=ShEez0JkOFw"&gt;watch on
Invidious&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tim spoke extensively about his love of hand-tool woodworking and of making
things (from wood or code). Known materials, an ergonomic environment and simple
tools give you precision, control, flexibility and a connection to your work
(and avoid dangerous dust). This takes time and effort to learn, but is worth
it. You can get a lot of mileage from looking back in time and picking up tools
that most people have forgotten - especially as used by people making their
living prior to automation. Automation saves time, reduces costs and adds
convenience, but it also trades off the beauty of proportional work for the
efficiency of measurement-driven work. Automation isn't bad, but the tools
significantly affect how you see the world, what is possible and the final
results - there are costs to automation - eg. handmade mortise and tenon joints
on doors that are strong and aesthetic compared to stub tenons that are
convenient to produce with machines.&lt;/p&gt;
&lt;p&gt;Tim made the analogy to the known material properties of Clojure data
structures, the development environment (of Emacs) and the tools of Clojure
functions. He highlighted that in programming too, automation is ok, but there
are tradeoffs. The automation changes how you approach problems and the possible
solutions you consider. When you have a killer app, everything starts to look
like a problem that the killer app is the solution to. Tim worked in the
Microsoft world with SOAP for around 10 years and described SOAP/WSDL as as a
trainwreck driven by the fixation on automation within Visual Studio, and the
assumption that it would protect them from unbounded complexity. Other tools
that didn't work with with Visual Studio weren't perceived to even exist. Tim
asked whether now that we use Clojure's Lein, if we know what we ship, and
whether that's ok. More generally, how does &lt;em&gt;automation X&lt;/em&gt; change how I look at
the world?&lt;/p&gt;
&lt;p&gt;Tim described programmers as most like the woodworking pattern makers — people
who made unique wooden models of parts to be cast from steel. For this type of
work Clojure is the perfect simple tool that gives you a broader perspective on
the world.&lt;/p&gt;</content><category term="Technology"></category><category term="coding"></category><category term="clojure"></category><category term="woodworking"></category></entry><entry><title>How to fix uWSGI "OSError: write error"</title><link href="https://stumbles.id.au/how-to-fix-uwsgi-oserror-write-error.html" rel="alternate"></link><published>2021-09-21T00:00:00+10:00</published><updated>2021-09-21T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2021-09-21:/how-to-fix-uwsgi-oserror-write-error.html</id><summary type="html">&lt;p&gt;This &lt;code&gt;OSError: write error&lt;/code&gt; issue has been frustrating me for years — ever since
I began switching Python/Django web applications across from gunicorn to uWSGI
Emperor. Up until recently, I didn't understand what caused these errors,
whether they represented a problem worth solving, and if so how to deal with …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This &lt;code&gt;OSError: write error&lt;/code&gt; issue has been frustrating me for years — ever since
I began switching Python/Django web applications across from gunicorn to uWSGI
Emperor. Up until recently, I didn't understand what caused these errors,
whether they represented a problem worth solving, and if so how to deal with
them.&lt;/p&gt;
&lt;h1&gt;Problem&lt;/h1&gt;
&lt;p&gt;I deploy Python/Django sites with an Nginx frontend server, which proxies to a
Unix domain socket where uWSGI Emperor listening using the uWSGI protocol
(&lt;code&gt;uwsgi_pass&lt;/code&gt;). Something like this for Nginx:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Nginx config (truncated for clarity)&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="kn"&gt;uwsgi_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;unix:/tmp/site_name.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="kn"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;uwsgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And this for the uWSGI:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# uWSGI Emperor vassel config (truncated for clarity)&lt;/span&gt;
&lt;span class="k"&gt;[uwsgi]&lt;/span&gt;
&lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python3&lt;/span&gt;
&lt;span class="na"&gt;chdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/srv/site_name&lt;/span&gt;
&lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/srv/venvs/site_name&lt;/span&gt;
&lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;project.wsgi&lt;/span&gt;
&lt;span class="na"&gt;master&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/tmp/site_name.sock&lt;/span&gt;
&lt;span class="na"&gt;processes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;
&lt;span class="na"&gt;vacuum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;enable-threads&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# for Sentry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A few times a week, I would see an error like below, both on my Sentry error
tracking service, as well as in the uWSGI logs at &lt;code&gt;/var/log/uwsgi/emperor.log&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mysite - SIGPIPE: writing to a closed pipe/socket/fd (probably the client disconnected) on request / (ip 1.2.3.4) !!!
mysite - uwsgi_response_writev_headers_and_body_do(): Broken pipe [core/writer.c line 306] during GET / (1.2.3.4)
OSError: write error
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Noteably, I &lt;strong&gt;did not&lt;/strong&gt; receive any Django error emails about this, only notifications
from Sentry.&lt;/p&gt;
&lt;p&gt;From my literal reading of the above, I gather that the client web browser had
closed the HTTP connection with Nginx and Nginx had closed the output stream
that uWSGI was trying to write the response to. Seemed reasonable enough. I
hadn't received any reports from customers of issues, so assumed the issue was
not causing any serious inconvenience. Still, I couldn't reproduce it, which
bothered me. And while I could mark the issues in Sentry as "ignore", they still
counted towards the total weekly errors report.&lt;/p&gt;
&lt;p&gt;I tried mobile devices, command-line browsers and load testing to see if I could
pin down the cause of the issue. Nothing. The errors in the logs were sporadic
and unpredictable.&lt;/p&gt;
&lt;p&gt;Over the years I made a few blind attempts to fix the issue by adjusting Nginx's
&lt;code&gt;uwsgi_ignore_client_abort&lt;/code&gt;, &lt;code&gt;uwsgi_read_timeout&lt;/code&gt;, &lt;code&gt;uwsgi_send_timeout&lt;/code&gt; settings
and uWSGI's &lt;code&gt;harakiri&lt;/code&gt; setting. Despite what some StackOverflow posts would
suggest, none of these made any difference.&lt;/p&gt;
&lt;h1&gt;How to reproduce&lt;/h1&gt;
&lt;p&gt;I recently managed to replicate this issue. Turns out that all you need
to do is open a non-trivial Django page in a regular desktop browser and hammer
on the F5 (refresh) key a few times in rapid succession. Bingo!&lt;/p&gt;
&lt;h1&gt;Solution&lt;/h1&gt;
&lt;p&gt;Being able to reproduce the issue gave me confidence that the error was part of
normal operation and did not represent a negative experience for the visitor.
Given that the default Django &lt;code&gt;mail_admins&lt;/code&gt; logging handler didn't report the
error, it was clear that only Sentry was really a problem here.&lt;/p&gt;
&lt;p&gt;After further testing I found that uWSGI's &lt;code&gt;disable-write-exception&lt;/code&gt; would
prevent the errors being logged by Sentry.&lt;/p&gt;
&lt;p&gt;To prevent the (harmless) errors in the uWSGI log, you need all three of these
lines in your uWSGI config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="na"&gt;ignore-sigpipe&lt;/span&gt;
&lt;span class="na"&gt;ignore-write-errors&lt;/span&gt;
&lt;span class="na"&gt;disable-write-exception&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each corresponds to one of the three error lines mentioned above.&lt;/p&gt;
&lt;p&gt;Since applying the change, I've had no further notifications from Sentry about
the issue and lovely clean weekly error reports. &lt;/p&gt;
&lt;p&gt;An alternate fix is to just filter out these errors before they are sent to
Sentry. I added the following &lt;code&gt;before_send&lt;/code&gt; handler to the Sentry setup in the
production Django settings, returning &lt;code&gt;None&lt;/code&gt; when encountering an &lt;code&gt;OSError:
write error&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;before_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Don&amp;#39;t report &amp;quot;OSError: write error&amp;quot; to Sentry.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;exc_info&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ne"&gt;OSError&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;write error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;

&lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;before_send&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;before_send&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Technology"></category><category term="python"></category><category term="django"></category><category term="coding"></category><category term="work"></category></entry><entry><title>Getting started with Guix deploy</title><link href="https://stumbles.id.au/getting-started-with-guix-deploy.html" rel="alternate"></link><published>2021-08-21T00:00:00+10:00</published><updated>2021-08-21T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2021-08-21:/getting-started-with-guix-deploy.html</id><summary type="html">&lt;p&gt;It's still early days for &lt;a href="https://guix.gnu.org"&gt;Guix&lt;/a&gt;'s &lt;a href="https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-deploy.html"&gt;&lt;code&gt;guix deploy&lt;/code&gt;&lt;/a&gt;, but it may well be my server deployment tool of the future. I'm quite excited!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;guix deploy&lt;/code&gt; promises to simply and reliably drop predefined operating systems onto remote machines; including services, configuration, packages and data. In the past I've used Ansible …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's still early days for &lt;a href="https://guix.gnu.org"&gt;Guix&lt;/a&gt;'s &lt;a href="https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-deploy.html"&gt;&lt;code&gt;guix deploy&lt;/code&gt;&lt;/a&gt;, but it may well be my server deployment tool of the future. I'm quite excited!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;guix deploy&lt;/code&gt; promises to simply and reliably drop predefined operating systems onto remote machines; including services, configuration, packages and data. In the past I've used Ansible, Salt, Puppet and Fabric for this purpose, but &lt;code&gt;guix deploy&lt;/code&gt; has a slightly different approach. Rather than "massaging things until they look right", &lt;code&gt;guix deploy&lt;/code&gt; uses Guix's superpowers to declare the whole operating system precisely and drop it into place. It's like functional programming for operating systems. Rather than templated YAML, &lt;code&gt;guix deploy&lt;/code&gt; also uses a proper programming language; one of the reasons I still heavily use the Fabric deployment tool at work.&lt;/p&gt;
&lt;p&gt;This approach could significantly reduce the maintenance burden on deploying production software. The experience should hopefully be similar to using a platform-as-a-service system but more flexible, using commodity virtual machines rather than proprietary infrastructure and without needing complicated orchestration systems such as Kubernetes.&lt;/p&gt;
&lt;h1&gt;Overall strategy&lt;/h1&gt;
&lt;p&gt;The overall strategy here is to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a minimal and generic base Guix operating system image using &lt;code&gt;guix system image&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Manually provision a cloud server with this base image.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;guix deploy&lt;/code&gt; to replace the generic configuration with the specific configuration we want.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note that we don't run a traditional installer at any point. In the future, &lt;code&gt;guix deploy&lt;/code&gt; may also be able to provision the remote server for a range of cloud hosting providers. There is initial support for provisioning on Digital Ocean, but I don't use this provider.&lt;/p&gt;
&lt;h1&gt;A web server from scratch&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Preparation&lt;/strong&gt;: You'll of course need Guix installed on your local machine or a remote virtual machine. For experimentation, it's convenient to generate images and run &lt;code&gt;guix deploy&lt;/code&gt; on a temporary remote virtual machine to save uploading lots of large files on a slow internet connection. I'd suggest provisioning a Debian VM and &lt;a href="https://guix.gnu.org/manual/en/html_node/Installation.html"&gt;installing Guix&lt;/a&gt;. Down the track I intend to deploy directly from my laptop.&lt;/p&gt;
&lt;p&gt;Wherever you're deploying from, create a new directory for this experiment, change into it and generate a new SSH keypair with &lt;code&gt;ssh-keygen&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a minimal base image&lt;/strong&gt;: Create a basic a Guix System config in &lt;code&gt;vm.scm&lt;/code&gt;. Mine looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;use-modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;gnu&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;use-service-modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;networking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;use-package-modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bootloaders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;operating-system&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;host-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;vm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timezone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Etc/UTC&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bootloader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bootloader-configuration&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bootloader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;grub-bootloader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/vda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;terminal-outputs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;console&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;file-systems&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cons&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;file-system&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mount-point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/vda1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ext4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="nv"&gt;%base-file-systems&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;services&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dhcp-client-service-type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;openssh-service-type&lt;/span&gt;
&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;openssh-configuration&lt;/span&gt;
&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;openssh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;openssh-sans-x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;permit-root-login&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;#t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;authorized-keys&lt;/span&gt;
&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="c1"&gt;;; Authorise our SSH key.&lt;/span&gt;
&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;local-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;id_rsa.pub&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)))))))&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;%base-services&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Generate an image from this config using &lt;code&gt;guix system image --save-provenance vm.scm&lt;/code&gt;. This will return the path of the new ~1.5GB image in the default "efi-raw" format. If you're creating the image on a remote VM you can publish it quickly by installing Nginx and building with &lt;code&gt;rm -f /var/www/html/latest.raw &amp;amp;&amp;amp; guix system image --save-provenance --root=/var/www/html/latest.raw vm.scm&lt;/code&gt;, which will then be available at &lt;code&gt;http://yourvm/latest.raw&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If your hosting provider supports it, using &lt;code&gt;--image-type=qcow2&lt;/code&gt; is much more space efficient at ~500MB. My provider doesn't support QCOW2, but I know that Digital Ocean does.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Provision the server&lt;/strong&gt;. After logging into my &lt;a href="https://www.binarylane.com.au/"&gt;Binary Lane&lt;/a&gt; hosting control panel, I select "Add Cloud Server". I learned that I had to select "BYO ISO" here because it turns off their auto-magic storage resizing feature which chokes on the multi-partition Guix image; but that's probably not applicable to other providers eg. Digital Ocean. The machine will be provisioned and assigned a hostname I'm taken to the image upload screen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Upload the minimal base image as a backup and restore it&lt;/strong&gt;. Rather than running an installer ISO, we'll upload a full disk image. Select "Settings" (cog), "Snapshots, Backups &amp;amp; ISO Management". Select the "Upload" tab. Choose "Create a new temporary image", upload from "Local file" (if building locally) or "HTTP server" if building on a remote VM. Provide your image created in step 2 and select "Start Upload". After that's uploaded, select "Restore this Backup". The machine should reboot into the new image. In the recovery console, I can see the machine has successfully booted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test the connection to your new server&lt;/strong&gt;. Run &lt;code&gt;ssh-add id_rsa&lt;/code&gt; to load the key we created earlier to your keyring and then SSH to the server using the hostname or IP. In my case the generated hostname is &lt;code&gt;tobacco-rebel.bnr.la&lt;/code&gt;, so I use &lt;code&gt;ssh root@tobacco-rebel.bnr.la&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resize the filesystem to use the full storage allocation&lt;/strong&gt;. Our image was only 1.5G, but my provider initially allocates 20G of storage. To use this, log into the VM and run &lt;code&gt;cfdisk&lt;/code&gt;, select the &lt;code&gt;/dev/vda2&lt;/code&gt; root partition, select "Resize", "Write" and type "yes". Then resize the filesystem to match by running &lt;code&gt;resize2fs /dev/vda2&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a full guix deploy configuration&lt;/strong&gt; in &lt;code&gt;deploy.scm&lt;/code&gt;. My &lt;code&gt;operating-system&lt;/code&gt; section is significantly the same as above, with additions for the Nginx web server and to authorise my locally generated packages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;use-service-modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;networking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ssh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;web&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;use-package-modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bootloaders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;define&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;%system&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;operating-system&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;host-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tobacco-rebel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timezone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Etc/UTC&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bootloader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bootloader-configuration&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bootloader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;grub-bootloader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/vda&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;terminal-outputs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;console&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;file-systems&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cons&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;file-system&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mount-point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="c1"&gt;;; Must be vda2 or you won&amp;#39;t be able to reboot after `guix deploy`.&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="c1"&gt;;; This is because our base image makes an EFI partition at vda1.&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/vda2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ext4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="nv"&gt;%base-file-systems&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;services&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dhcp-client-service-type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;openssh-service-type&lt;/span&gt;
&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;openssh-configuration&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;openssh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;openssh-sans-x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;password-authentication?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;#f&lt;/span&gt;&lt;span class="nv"&gt;alse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;permit-root-login&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;#t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;authorized-keys&lt;/span&gt;
&lt;span class="w"&gt;                             &lt;/span&gt;&lt;span class="c1"&gt;;; Authorise our SSH key.&lt;/span&gt;
&lt;span class="w"&gt;                             &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;local-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;id_rsa.pub&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))))))&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="c1"&gt;;; Security updates, yes please!&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;unattended-upgrade-service-type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="c1"&gt;;; Note that Nginx isn&amp;#39;t automatically restarted during&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="c1"&gt;;; `guix deploy`, so run `herd restart nginx`.&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;nginx-service-type&lt;/span&gt;
&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nginx-configuration&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;server-blocks&lt;/span&gt;
&lt;span class="w"&gt;                             &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nginx-server-configuration&lt;/span&gt;
&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;server-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tobacco-rebel.bnr.la&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;80&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                                    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/var/www/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)))))))&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;modify-services&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;%base-services&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;;; The server must trust the Guix packages you build. If you add the signing-key&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;;; manually it will be overridden on next `guix deploy` giving&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;;; &amp;quot;error: unauthorized public key&amp;quot;. This automatically adds the signing-key.&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;guix-service-type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;guix-configuration&lt;/span&gt;
&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;inherit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;authorized-keys&lt;/span&gt;
&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;local-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/etc/guix/signing-key.pub&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                                           &lt;/span&gt;&lt;span class="nv"&gt;%default-authorized-guix-keys&lt;/span&gt;&lt;span class="p"&gt;)))))))))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;machine&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;operating-system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;%system&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;managed-host-environment-type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;machine-ssh-configuration&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="c1"&gt;;; Use host name or IP address here.&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;host-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tobacco-rebel.bnr.la&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x86_64-linux&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="c1"&gt;;; Update this!&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;host-key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKp5IsRNi/qU2vrWNaH9MlZnOzN4umiEXkamScuwxF4M&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="c1"&gt;;; Use this key to communicate with the machine.&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;id_rsa&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deploy the config&lt;/strong&gt; with &lt;code&gt;guix deploy&lt;/code&gt; from your local machine. Guix will initially complain about the &lt;code&gt;host-key&lt;/code&gt;, so you'll need to update your configuration with the host key of the new machine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ guix deploy deploy.scm
  tobacco-rebel

guix deploy: deploying to tobacco-rebel...
guix deploy: error: failed to deploy tobacco-rebel: server at &amp;#39;tobacco-rebel.bnr.la&amp;#39; returned host key &amp;#39;AAAAC3NzaC1lZDI1NTE5AAAAIKsuev1SJ3SODqkHsRX4oWib6e6A3MiS9CArvXZhmNbq&amp;#39; of type &amp;#39;ed25519&amp;#39; instead of &amp;#39;AAAAC3NzaC1lZDI1NTE5AAAAIKp5IsRNi/qU2vrWNaH9MlZnOzN4umiEXkamScuwxF4M&amp;#39; of type &amp;#39;ed25519&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After correcting the host key:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ guix deploy deploy.scm
The following 1 machine will be deployed:
  tobacco-rebel

guix deploy: deploying to tobacco-rebel...
...
guix deploy: successfully deployed tobacco-rebel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you run &lt;code&gt;guix system list-generations&lt;/code&gt; on the new server, you'll see that there is now a new generation for each &lt;code&gt;guix deploy&lt;/code&gt;, allowing you to potentially &lt;code&gt;guix system switch-generations&lt;/code&gt; if needed.&lt;/p&gt;
&lt;p&gt;Now create a test file on your web server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ssh root@tobacco-rebel.bnr.la &amp;quot;echo &amp;#39;Hello World!&amp;#39; &amp;gt; /var/www/index.html&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you got this far you should be able to now access the new Nginx server, eg. For me http://tobacco-rebel.bnr.la/. Yay!&lt;/p&gt;
&lt;p&gt;Did this work for you? Please &lt;a href="mailto:ben@sturm.com.au"&gt;let me know&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Further work&lt;/h1&gt;
&lt;p&gt;From here, we have a working server with Nginx, but would need HTTPS and some content to call it a website. I've added some further resources below. Adding Certbot still currently requires a dance of first deploying a config with Certbot and without HTTPS so Nginx doesn't break, obtaining the certificate, then re-deploying with the HTTPS enabled in Nginx. Certbot is run via mcron, so see &lt;code&gt;herd schedule mcron&lt;/code&gt; for the command to initially run to obtain the certificate.&lt;/p&gt;
&lt;p&gt;Should you upload the website content through &lt;code&gt;guix deploy&lt;/code&gt; or separately eg. &lt;code&gt;rsync&lt;/code&gt;? That's up to you. Many examples use &lt;code&gt;rsync&lt;/code&gt;, but the configuration for Berlin (below) uses &lt;code&gt;static-web-site-configuration&lt;/code&gt; which builds a static website from a git repository.&lt;/p&gt;
&lt;p&gt;How about adding a Mumble server, or any of the other &lt;a href="https://guix.gnu.org/manual/en/html_node/Guix-Services.html"&gt;services&lt;/a&gt; available in Guix? They're all only a few lines of config away!&lt;/p&gt;
&lt;p&gt;Could the base image be shrunk further? 1.5GB is rather a lot to be moving around from many internet connections. This is not so much of an issue if your provider supports compressed QCOW2 images.&lt;/p&gt;
&lt;p&gt;My host also has a Nova API and examples of using python-novaclient and docker-machine to provision VMs. I'd like to see whether I can upload Guix images and provision new machines this way.&lt;/p&gt;
&lt;h1&gt;Wrong turns I took along the way&lt;/h1&gt;
&lt;p&gt;Here's a few issues I wasted plenty of time on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My host doesn't support loading QCOW2 images. It accepted them, but they would always fail to boot.&lt;/li&gt;
&lt;li&gt;Before I added the &lt;code&gt;signing-key&lt;/code&gt; config, &lt;code&gt;guix deploy&lt;/code&gt; would fail with &lt;code&gt;error: unauthorized public key&lt;/code&gt; because it didn't trust the substitute packages built on my deployment machine.&lt;/li&gt;
&lt;li&gt;I misunderstood the &lt;code&gt;guix system image&lt;/code&gt; argument &lt;code&gt;--volatile&lt;/code&gt; and ended up with a read-only server the regenerated it's SSH key on every reboot. Volatile in computing means non-persistent.&lt;/li&gt;
&lt;li&gt;Resizing storage for the base image was problematic on my hosting provider due to their auto-magic resizing feature that allocates storage, resizes the partition and the filesystem. Their support advised that selecting "BYO ISO" disables the partition/filesystem resizing and you can just do it manually. Before this I did experiment with creating extra-large &lt;code&gt;efi-raw&lt;/code&gt; images eg. 20GB, but it wasn't really feasible.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;efi-raw&lt;/code&gt; image creates a 40MB EFI partition at &lt;code&gt;/dev/vda1&lt;/code&gt; labelled "GNU-ESP", before the root partition at &lt;code&gt;/dev/vda2&lt;/code&gt;. Deploying a config with the root partition listed as &lt;code&gt;/dev/vda1&lt;/code&gt; will mean the VM will no longer boot. After discussions with Mathieu (below), he is working on a single partition &lt;code&gt;raw&lt;/code&gt; image type that will be available in the near future.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Thanks and further information&lt;/h1&gt;
&lt;p&gt;Mathieu Othacehe has a great &lt;a href="https://othacehe.org/hosting-a-blog-using-only-scheme.html"&gt;blog post&lt;/a&gt; on this topic, and goes further into the web hosting configuration of the server.&lt;/p&gt;
&lt;p&gt;Ludovic Courtès and roptat pointed out the &lt;a href="https://git.savannah.gnu.org/cgit/guix/maintenance.git/tree/hydra/berlin.scm"&gt;configuration for Guix's server Berlin&lt;/a&gt;, which uses &lt;code&gt;static-web-site-configuration&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Christine Lemmer-Webber initially encouraged me to look into &lt;code&gt;guix deploy&lt;/code&gt;, wrote the &lt;a href="https://guix.gnu.org/cookbook/en/html_node/Running-Guix-on-a-Linode-Server.html"&gt;Running Guix on a Linode Server&lt;/a&gt; cookbook entry and provided some examples from her own use.&lt;/p&gt;
&lt;p&gt;Jakob L. Kreuze implemented much of &lt;code&gt;guix deploy&lt;/code&gt; and described it in &lt;a href="http://guix.gnu.org/blog/2019/towards-guix-for-devops/"&gt;Towards Guix for DevOps&lt;/a&gt; and &lt;a href="https://guix.gnu.org/blog/2019/managing-servers-with-gnu-guix-a-tutorial/"&gt;Managing Servers with GNU Guix: A Tutorial&lt;/a&gt;. Thanks Jakob and the other Guix contributors!&lt;/p&gt;</content><category term="Technology"></category><category term="coding"></category><category term="guix"></category><category term="work"></category></entry><entry><title>This week I learnt (Week 33, 2021)</title><link href="https://stumbles.id.au/this-week-i-learnt-week-33-2021.html" rel="alternate"></link><published>2021-08-20T00:00:00+10:00</published><updated>2021-08-20T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2021-08-20:/this-week-i-learnt-week-33-2021.html</id><summary type="html">&lt;h1&gt;SVG supports SMIL animation — no JavaScript or CSS&lt;/h1&gt;
&lt;p&gt;SVG can use &lt;a href="https://svgwg.org/specs/animations/"&gt;SMIL animation&lt;/a&gt; elements such as &lt;code&gt;&amp;lt;animate&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;set&amp;gt;&lt;/code&gt; to create declarative animation/interactivity. This allows you to dynamically change attributes like position, opacity, fill and CSS class. These features seem very widely used, but would be handy for a …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;SVG supports SMIL animation — no JavaScript or CSS&lt;/h1&gt;
&lt;p&gt;SVG can use &lt;a href="https://svgwg.org/specs/animations/"&gt;SMIL animation&lt;/a&gt; elements such as &lt;code&gt;&amp;lt;animate&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;set&amp;gt;&lt;/code&gt; to create declarative animation/interactivity. This allows you to dynamically change attributes like position, opacity, fill and CSS class. These features seem very widely used, but would be handy for a self-contained diagram, for example a tabbed interface based on opacity.&lt;/p&gt;
&lt;p&gt;The following demo uses the &lt;code&gt;&amp;lt;set&amp;gt;&lt;/code&gt; element to change fill colours base on &lt;code&gt;click&lt;/code&gt; and &lt;code&gt;mouseover&lt;/code&gt; events, showing that events can target other elements by &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;svg width="415" height="100" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"&gt;
&lt;rect fill="#ff660099" x="0" y="0" width="200" height="100"&gt;
    &lt;set attributeName="fill" to="#ff009999" begin="click" dur="1s" /&gt;
    &lt;set attributeName="opacity" to="0.1" begin="rect2.mouseover" dur="1s" /&gt;
&lt;/rect&gt;
&lt;text x="20" y="55" fill="#333"&gt;Click/tap to change colour&lt;/text&gt;
&lt;rect id="rect2" fill="#ff660099" x="215" y="0" width="200" height="100"&gt;
&lt;/rect&gt;
&lt;text x="225" y="55" fill="#333"&gt;Hover targets other rectangle&lt;/text&gt;
&lt;/svg&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;415&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;100&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;xmlns:xlink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.w3.org/1999/xlink&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;rect&lt;/span&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ff660099&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;200&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;100&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;set&lt;/span&gt; &lt;span class="na"&gt;attributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ff009999&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;begin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;click&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;dur&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1s&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;set&lt;/span&gt; &lt;span class="na"&gt;attributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;opacity&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.1&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;begin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rect2.mouseover&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;dur&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1s&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;rect&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;text&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;20&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;55&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#333&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Click/tap to change colour&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;text&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;rect&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rect2&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ff660099&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;215&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;200&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;100&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;rect&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;text&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;225&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;55&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#333&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hover targets other rectangle&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;text&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;svg&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Inkscape can also edit such SVGs without destroying overwriting the animation elements.&lt;/p&gt;
&lt;h1&gt;GitHub commit squashing — no need to rebase&lt;/h1&gt;
&lt;p&gt;I still occasionally get well-meaning advice from GitHub bystanders to rebase and squash my commits in pull requests. Ignore those folks — it's not necessary. Way back in 2016, GitHub introduced a &lt;a href="https://github.blog/2016-04-01-squash-your-commits/"&gt;pull request squash&lt;/a&gt; feature to give maintainers the discretion to squash a contribution into a single commit. Besides, overly-enthusiastic rebasing can cause problems for others following along with your work.&lt;/p&gt;
&lt;p&gt;Pelican maintainer Justin Mayer wrote this in response to a piece of &lt;a href="https://github.com/getpelican/pelican/pull/2914#issuecomment-900293970"&gt;such advice&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(Bystander) As a general note, I would expect all the commits to be squashed into a single one, the upstream doesn't need your development history I think.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Meyer) I'm not sure I agree. When formulated with thought and care, multiple commits can indeed be quite useful. Also, during feedback-and-change iterations, it can be useful to see what has changed in a PR over time, in which case cleaning up the commits can be done at the very end, right before merging.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(Me) I don't use GitHub a lot for collaboration myself, but I believe there's been a "merge and squash" button available to maintainers for a few years now for use at their discretion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Meyer) That is correct. If the commits aren't valuable on their own (e.g., if there are "fix-up" commits that add no value as separate commits), the maintainer can squash the commits if the contributor hasn't already done said clean-up.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GitLab, which I use a lot more day-to-day, has had a similar feature available for many years too. I don't use it regularly as I prefer to retain the full development history.&lt;/p&gt;
&lt;h1&gt;Reordering commits during git rebase&lt;/h1&gt;
&lt;p&gt;Reordering git commits during a &lt;code&gt;git rebase --interactive&lt;/code&gt; is as simple as
reordering the lines of text in the commit list that pops up in your editor.
Although I tend not to rebase aggressively, this reordering is handy to group
related changes together for easier review.&lt;/p&gt;</content><category term="Technology"></category><category term="coding"></category></entry><entry><title>Talks I've watched (March 2021)</title><link href="https://stumbles.id.au/talks-ive-watched-march-2021.html" rel="alternate"></link><published>2021-03-15T00:00:00+11:00</published><updated>2021-03-15T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2021-03-15:/talks-ive-watched-march-2021.html</id><summary type="html">&lt;h1&gt;&lt;a href="https://clojuresync.com/emily-ashley/"&gt;Emily Ashley: 7 Falsehoods Programmers Believe about Place &amp;amp; Time&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Emily described how the human side of collecting and visualising time-based data is more complicated than you might think, highlighting issues like varying precision where some data might have a year only, or some might be ordinal where some event happened …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;&lt;a href="https://clojuresync.com/emily-ashley/"&gt;Emily Ashley: 7 Falsehoods Programmers Believe about Place &amp;amp; Time&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Emily described how the human side of collecting and visualising time-based data is more complicated than you might think, highlighting issues like varying precision where some data might have a year only, or some might be ordinal where some event happened before another but we don't know the precise time. These things don't fit well into our common date/time format. She didn't recommend any particular solutions, but mentioned that &lt;a href="https://en.wikipedia.org/wiki/ISO_8601"&gt;ISO 8601&lt;/a&gt; does cover a range of cases.&lt;/p&gt;
&lt;h1&gt;&lt;a href="https://clojuresync.com/baishampayan-ghose/"&gt;Baishampayan Ghose: Revenge of the Pragmatists&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Baishampayan discussed why adopting Clojure early in it's life worked out well for their company (100 staff). Talked about recruiting and culter and the need to grow teams rather than just hire them. Specific recommendation for all current and new Clojure programmers to read SICP. Upfront design as promoted by Clojure is different to "debugger-driven development". At scale, all problem are people problems. Small teams and FP can be very productive. FP does help build reliable software. Interestingly, he mentioned that haven't used ClojureScript because of their existing JS/TS codebase, though would like to.&lt;/p&gt;</content><category term="Technology"></category><category term="coding"></category><category term="clojure"></category></entry><entry><title>Why MediaGoblin?</title><link href="https://stumbles.id.au/why-mediagoblin.html" rel="alternate"></link><published>2021-03-11T00:00:00+11:00</published><updated>2021-03-11T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2021-03-11:/why-mediagoblin.html</id><summary type="html">&lt;p&gt;We've recently released &lt;a href="https://mediagoblin.org/news/mediagoblin-0.11.0-release.html"&gt;MediaGoblin
0.11.0&lt;/a&gt;! The
release ends our support for Python 2 and significantly simplifies the
maintenance of the project. It also re-introduces audio spectrograms in addition
to a handful of bug fixes.&lt;/p&gt;
&lt;p&gt;This is my second release in the maintainer seat after becoming a co-maintainer
in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;We've recently released &lt;a href="https://mediagoblin.org/news/mediagoblin-0.11.0-release.html"&gt;MediaGoblin
0.11.0&lt;/a&gt;! The
release ends our support for Python 2 and significantly simplifies the
maintenance of the project. It also re-introduces audio spectrograms in addition
to a handful of bug fixes.&lt;/p&gt;
&lt;p&gt;This is my second release in the maintainer seat after becoming a co-maintainer
in September 2019 and sole maintainer in January 2021. Given that, I'd like to
reflect on why I'm here, and some of my shorter and longer-term goals.&lt;/p&gt;
&lt;p&gt;Personally, MediaGoblin has been a way to privately share photos of my kids with
their grandparents, a place to share technical presentations and talks and
somewhere to publish recordings from our free software group. But beyond this,
it's also been an inspiration to me.&lt;/p&gt;
&lt;p&gt;From the beginning, Chris and Deb have done an exceptional job of pitching a
vision of a better future for artists, photographers, videographers and makers;
a way for them to have independence and control over their publishing. Back in
2010, photo, audio and video publishing had recently become mainstream, but
there were few good alternatives to centralised, corporately-controlled publishing
platforms. This clear vision, plus the energy, deliberate community building and
quirky artistic flair that Chris and Deb brought to the project, helped the
concept resonate with a whole lot of people, myself included. This community of
great people is a large part of why I'm here.&lt;/p&gt;
&lt;p&gt;In many ways, MediaGoblin has already proven itself as a useful tool and a
worthwhile project. Many people and organisations already use MediaGobin, so
there is no doubt about the impact that we, the members of the MediaGoblin
community, have made. Can we go further? Absolutely we can.&lt;/p&gt;
&lt;p&gt;MediaGoblin had a fairly quiet period in terms of releases between version 0.9.0
in March 2016 and 0.10.0 in May 2020. Chris and Jessica diverted a lot of energy
into the bigger issue of standards for federated social media through the Social
Web Working Group. And even before the major interruptions of COVID-19, the Python
2-to-3 transition spread our efforts a little thin.&lt;/p&gt;
&lt;p&gt;Despite this, we've made significant progress in the last year or so; squashing
bugs, adding small features, improving documentation and testing installation on
a number of operating systems. This has been partly boosted by a &lt;a href="https://www.sturm.com.au/preserving-indigenous-culture-using-mediagoblin/"&gt;pilot media
archiving
project&lt;/a&gt;
I've worked on with NG Media in Western Australia.&lt;/p&gt;
&lt;p&gt;So where to from here? At this point, it would be reasonable to ask what
mainstream success might look like for MediaGoblin. A lot has changed since the
project was founded in 2010, but one thing that hasn't changed is the dominance
of previously mentioned centralised corporate-controlled photo, audio and video
publishing. There are many younger sibling projects working towards similar
goals, including &lt;a href="https://joinpeertube.org/"&gt;PeerTube&lt;/a&gt;,
&lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://pixelfed.org/"&gt;Pixelfed&lt;/a&gt; and
many more. MediaGoblin does still remain unique in its focus on artwork
publishing and multiple media formats.&lt;/p&gt;
&lt;p&gt;There is still plenty of work to be done on MediaGoblin, and while there are no guarantees of
imminent mainstream success, the potential up-side of and impact
of our work is huge, while the potential downside is small, a concept I've
recently heard referred to as
&lt;a href="https://mccormick.cx/news/entries/bootstrapping-and-convexity"&gt;"convex"&lt;/a&gt;. This
idea encourages me to keep pushing forwards, even if slowly and intermittently
and not worry too much about trying to predict the future.&lt;/p&gt;
&lt;p&gt;There are definitely easier and harder problems to solve in the world of free
software. While working on programming libraries and development tools is far
from simple, the scope is usually more clearly defined. End-user facing
applications like MediaGoblin are challenging because they have a larger scope
and are used by a wider range of people. This is part of what motivates me
though — bringing free software to those beyond just the tech community.&lt;/p&gt;
&lt;p&gt;It's been great to see the community's reaction to these recent two releases.
People do still know and love MediaGoblin; both the media publishing software we
have now and the ambitious federated free software platform we hope to have in
the future.&lt;/p&gt;</content><category term="Technology"></category><category term="mediagoblin"></category><category term="free-software"></category></entry><entry><title>MyGovID ethical, privacy and security problems for ATO Business Portal</title><link href="https://stumbles.id.au/mygovid-ethical-privacy-and-security-problems-for-ato-business-portal.html" rel="alternate"></link><published>2020-03-26T00:00:00+11:00</published><updated>2020-03-26T00:00:00+11:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2020-03-26:/mygovid-ethical-privacy-and-security-problems-for-ato-business-portal.html</id><summary type="html">&lt;p&gt;&lt;em&gt;[Updated 16 April 2020 with response from Catherine King MP and ATO. See bottom of this post.]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I've just sent a letter to the Commissioner of Taxation about the
rollout of MyGovID as the only way to log in to the ATO Business Portal. Here it
is in case it …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;[Updated 16 April 2020 with response from Catherine King MP and ATO. See bottom of this post.]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I've just sent a letter to the Commissioner of Taxation about the
rollout of MyGovID as the only way to log in to the ATO Business Portal. Here it
is in case it helps to encourage other business owners to speak out.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Essentially the ATO is switching off the nice email/password/SMS-code
MyGov login method I use to access the Business Portal to manage
tax/GST/PAYG/super. The are replacing this with login via a proprietary
mobile app called, confusingly, MyGovID. I'm late to the party, with the
changeover due in only a few days time, but better late than not heard
at all.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;

&lt;p&gt;To: Chris Jordan AO, Commissioner of Taxation&lt;/p&gt;
&lt;p&gt;Dear Mr Jordan&lt;/p&gt;
&lt;p&gt;Due to the major ethical, privacy and security issues with MyGovID and its
upcoming compulsory use in the ATO Business Portal, I request that the
transition be deferred. This will allow for further review and revisions to
MyGovID and the way it is distributed.&lt;/p&gt;
&lt;p&gt;As a small business owner, I currently complete my Business Activity Statement,
Pay As You Go tax witholding, GST and superannuation payments to employees
through the Business Portal. I log in using MyGov (not MyGovID). This approach
works very well for me, requiring only an email, password and SMS code.&lt;/p&gt;
&lt;p&gt;As you know, login via MyGov is being decommissioned at the end of March 2020 to
be replaced by the new MyGovID smartphone app. MyGovID has two major problems.
Firstly, all business owners  will require an account with either Apple or Google. Secondly
MyGovID is proprietary software that business owners are asked to blindly trust and
cannot audit.&lt;/p&gt;
&lt;p&gt;To download the MyGovID app requires that the business owner register with Apple
to access the Apple App Store, or with Google to access the Google Play Store.
This typically requires providing full name, date of birth, phone number,
address and credit card details. Apple and Google are two of the world's richest
companies who's sole responsibility is to their shareholders, not to account
holders. While many Australians have already given up their personal information
to Apple or Google, we are really only just beginning to understand the
implications of these actions. These companies have no place collecting dossiers
on Australians or be in a position of trust and power between the Australian
Government and its citizens.&lt;/p&gt;
&lt;p&gt;MyGovID is proprietary software, which means that the people using it, even
technology professionals like myself, have absolutely no knowledge of what it
does. We can't tell what information it tracks and collects about us and whether
or not it is behaving in our best interests. This is the worst kind of
technology — monopoly, non-interoperable technology that we are forced to
depend on and must trust on blind faith.&lt;/p&gt;
&lt;p&gt;Personally, for ethical, privacy and security reasons I do not have or wish to
have an account with either Apple or Google and choose not to use proprietary
software. From April I will no longer have access to the Business Portal and
will be forced manage my tax obligations by post. For my business this means not
having the most up-to-date information about my tax account, spending more time
on managing my tax affairs and finding an alternative method to report and pay
superannuation.&lt;/p&gt;
&lt;p&gt;As a technology professional I'm sympathetic to the challenges of designing a
simple and secure online system, let alone one that is responsible for highly
confidential information and is rolled out to millions of citizens. This is not
easy, but it &lt;em&gt;can&lt;/em&gt;} be done without sacrificing ourselves to Apple/Google
and without putting unaccountable technology in a position of unjust power over our lives.&lt;/p&gt;
&lt;p&gt;How could this situation be improved right now? Firstly, please defer
decommissioning the existing MyGov login to allow for further public review.
Secondly, please release the source code to the new MyGovID app to the public to
allow it to be reviewed and verified by any Australians with the interest and
technical expertise to do so. Thirdly, please ensure that the MyGovID app is
available for download without requiring registration; for example in an F-Droid
compatible repository.&lt;/p&gt;
&lt;p&gt;My apologies for the lateness in raising these concern. As a busy sole-trader,
it's difficult to allocate time to allocate time to these things. I would be
very happy to discuss this matter with you further.&lt;/p&gt;
&lt;p&gt;Yours sincerely,&lt;/p&gt;
&lt;p&gt;Ben Sturmfels&lt;/p&gt;
&lt;p&gt;CC: Catherine King MP, Federal Member for Ballarat&lt;/p&gt;
&lt;hr&gt;

&lt;p&gt;&lt;strong&gt;Update 27 March 2020&lt;/strong&gt;: Catherine King MP responded very promptly and sent me
a copy of the letter she wrote to Treasurer Josh Frydenberg about the matter on
my behalf.&lt;/p&gt;
&lt;hr&gt;

&lt;p&gt;&lt;strong&gt;Update 31 March 2020&lt;/strong&gt;: I had a lovely call from a person at ATO
responding to my complaint. A couple of things they mentioned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ATO is the first agency to use MyGovID&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MyGovID has a &lt;a href="https://www.mygovid.gov.au"&gt;feedback form&lt;/a&gt; so please use it&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;they have received quite a bit of feedback similar to mine&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;there was some form of hard deadline in place around their previous
authentication set up around 10 years ago - sounded like a contract
expiry but I didn't get specifics - may have been just related to AusKey&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;they really didn't know how the transition was going to go - now they
have learned, surprise surprise, for example a bunch of tax accountants
who don't have smartphones - much respect to those accountants!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;currently the Digital Identity team is only speaking with people who
are having technical difficulties with the app, not people who want to
participate in the upstream process&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All in all, they were very empathetic about the ethical issues of
requiring Apple or Google accounts and trust in proprietary tech. If you
can spare a few minutes, this is an important time to be heard and they
are certainly listening.&lt;/p&gt;
&lt;hr&gt;

&lt;p&gt;&lt;strong&gt;Update 16 April 2020&lt;/strong&gt;: A representative of ATO called to suggest that as a sole-trader (not a company),
I can manage activity statements and superannuation through the ATO linked
service on &lt;a href="https://my.gov.au"&gt;my.gov.au&lt;/a&gt;. I tried this and after doing the necessary linking
security questions, I get essentially the exact same functionality I had via the
ATO Business Portal.&lt;/p&gt;
&lt;p&gt;This isn't an option for companies though, who are forced to use MyGovID
so that multiple authorised people can access these features on the ATO
Business Portal.&lt;/p&gt;
&lt;p&gt;The representative told me that there's no plans to move my.gov.au to
MyGovID login for the foreseeable future.&lt;/p&gt;
&lt;p&gt;So that solves my issues for now, but I expect it's only a matter of
time before MyGovID gets more widely rolled out.&lt;/p&gt;</content><category term="Activism"></category><category term="mygovid"></category></entry><entry><title>GitLab CI/CD on OpenShift Online Kubernetes… or not</title><link href="https://stumbles.id.au/gitlab-cicd-on-openshift-online-kubernetes-or-not.html" rel="alternate"></link><published>2018-06-14T00:00:00+10:00</published><updated>2018-06-14T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2018-06-14:/gitlab-cicd-on-openshift-online-kubernetes-or-not.html</id><summary type="html">&lt;p&gt;&lt;em&gt;(Spoiler) In short, it appears that you &lt;strong&gt;currently can't&lt;/strong&gt; use OpenShift Online as a
Kubernetes cluster for gitlab.com. I'd be happy to find out I'm wrong though.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;don't&lt;/em&gt; want to learn how to administer a Kubernetes cluster right now. I want
 &lt;em&gt;less&lt;/em&gt; infrastracture to manage, not more. With …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;(Spoiler) In short, it appears that you &lt;strong&gt;currently can't&lt;/strong&gt; use OpenShift Online as a
Kubernetes cluster for gitlab.com. I'd be happy to find out I'm wrong though.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;don't&lt;/em&gt; want to learn how to administer a Kubernetes cluster right now. I want
 &lt;em&gt;less&lt;/em&gt; infrastracture to manage, not more. With that in mind, Openshift
Online seems like the ideal Kubernetes backend to run my GitLab continuous
integration/deployment services. I already pay for and use the platform, it's
run by a company I trust and respect and provides a great web interface. Let's
set it up!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Given an account on &lt;a href="https://gitlab.com"&gt;gitlab.com&lt;/a&gt;, create a new project,
   for example by forking
   &lt;a href="https://gitlab.com/auto-devops-examples/minimal-ruby-app"&gt;minimal-ruby-app&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Given either a "Starter" or "Pro" account on &lt;a href="https://www.openshift.com/products/online/"&gt;OpenShift
   Online&lt;/a&gt;, create a project called
   &lt;code&gt;gitlab-managed-apps&lt;/code&gt;, create a &lt;a href="https://docs.openshift.org/3.9/dev_guide/service_accounts.html"&gt;service
   account&lt;/a&gt; for
   GitLab to use, and copy the service token:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;oc&lt;span class="w"&gt; &lt;/span&gt;login
$&lt;span class="w"&gt; &lt;/span&gt;oc&lt;span class="w"&gt; &lt;/span&gt;new-project&lt;span class="w"&gt; &lt;/span&gt;gitlab-managed-apps
$&lt;span class="w"&gt; &lt;/span&gt;oc&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;serviceaccount&lt;span class="w"&gt; &lt;/span&gt;gitlab-robot
$&lt;span class="w"&gt; &lt;/span&gt;oc&lt;span class="w"&gt; &lt;/span&gt;policy&lt;span class="w"&gt; &lt;/span&gt;add-role-to-user&lt;span class="w"&gt; &lt;/span&gt;admin&lt;span class="w"&gt; &lt;/span&gt;system:serviceaccount:gitlab-managed-apps:gitlab-robot
$&lt;span class="w"&gt; &lt;/span&gt;oc&lt;span class="w"&gt; &lt;/span&gt;serviceaccounts&lt;span class="w"&gt; &lt;/span&gt;get-token&lt;span class="w"&gt; &lt;/span&gt;gitlab-robot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you log in to the OpenShift web console and visit the
&lt;code&gt;gitlab-managed-apps&lt;/code&gt; project, you'll find our new service account under
"Resources, Membership".&lt;/p&gt;
&lt;p&gt;Alternately you can create the project and the service account via the web
console, but I don't believe it's possible to view the service account
token without the &lt;code&gt;oc&lt;/code&gt; command line.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Returning to GitLab, select "Settings, CI / CD" and expand the "Runners
   settings". Click "Install Runner on Kubernetes". Click "Add Kubernetes
   cluster", then click "Add an existing Kubernetes cluster". Set the cluster
   name, API URL and token. For me these are something like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitLab Kubernetes setup" src="https://stumbles.id.au/images/2018/gitlab-kubernetes-setup.png"&gt;&lt;/p&gt;
&lt;p&gt;Note that the "API URL" is the base URL from your OpenShift Online web
console, excluding the bit after the "/" and the token is the one we
exported above.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;So far so good. On the following screen, under "Applications" click the
   "Install" button next to "Helm Tiller". If it's not clickable, try refreshing
   the page. If the service account is set up right, the indicator will spin for
   a bit, then display "Installing" for about a minute, then issue an error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;Something&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;went&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wrong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;installing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Helm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Tiller&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;sh&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;can&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;t create /etc/apk/repositories: Permission denied&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you're fast, you can briefly see a pod being spun up and deleted on the
OpenShift web console.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unfortunately I wasn't able to overcome this issue, that's as far as I got.
There's no clear indication of what actual problem is and, as far as I know, no
way to dive in and find out what's going on. I suspect the issue may be related
to OpenShift Online's default policy of &lt;a href="https://blog.openshift.com/deploying-images-from-dockerhub/"&gt;not allowing containers to run as
root&lt;/a&gt;, but I can't
say for sure.&lt;/p&gt;
&lt;p&gt;The disappointing thing is that back in 2016 and 2017, there did seem to be some
sense of &lt;a href="https://hub.openshift.com/primed/3-gitlab"&gt;partnership&lt;/a&gt; between GitLab
and OpenShift. There are a number of official GitLab/OpenShift videos from
around this time, demonstrating the manual setup of GitLab Runner on OpenShift
(pre-GitLab Kubernetes integration). Unfortunately, there don't seem to be any
official and stable GitLab Runner templates for OpenShift. This
&lt;a href="https://gitlab.com/gitlab-examples/openshift-deploy/tree/master"&gt;official-sounding
one&lt;/a&gt; is marked
EXPERIMENTAL and this &lt;a href="https://github.com/oprudkyi/openshift-templates/tree/master/gitlab-runner"&gt;unofficial
one&lt;/a&gt;
template I came across advises that it is not compatible with OpenShift Online
(only self-hosted OpenShift). The &lt;a href="https://docs.gitlab.com/ee/ci/review_apps/"&gt;Review
Apps&lt;/a&gt; mention down the bottom that
docs for OpenShift/kubernetes are soon to be added.&lt;/p&gt;
&lt;p&gt;The approach I &lt;em&gt;have&lt;/em&gt; had some success with is using the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;
&lt;a href="https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/gitlab-ci-yml/OpenShift.gitlab-ci.yml"&gt;template that GitLab
provide&lt;/a&gt;
for OpenShift via the "Set up CI/CD" button. This uses gitlab.com's shared
runners, but deploys to OpenShift. This
&lt;a href="https://gitlab.com/gitlab-examples/review-apps-openshift/tree/master"&gt;review-apps-openshift&lt;/a&gt;
looks promising too. But that's another blog post.&lt;/p&gt;
&lt;h1&gt;An aside: How is this &lt;em&gt;meant&lt;/em&gt; to work?&lt;/h1&gt;
&lt;p&gt;In contrast, the GitLab CI/CD and Google Kubernetes Engine (GKE) integration
appears to be getting significantly more promotion and quite deep-running
integration from the GitLab team. While I don't use or recommend Google services
to any of my clients due to privacy concerns and lock-in from proprietary
network services, I gave this a brief trial to see how the GitLab/Kubernetes
integration is meant to work.&lt;/p&gt;
&lt;p&gt;GitLab provide a custom setup process and documentation for GKE. This mostly
avoids the need to use the GKE web interface at all, aside from creating an
account, creating a project and enabling API permissions. That's a good thing,
since the GKE web interface seemed to be very buggy. Once Kubernetes was
configured, GitLab did the rest; launching a cluster and prompting me to install
Helm Tiller, GitLab Runner and create a wildcard DNS entry. Using this setup, I
was able to enable the Kubernetes GitLab Runner and use Auto Deploy to
successfully deploy "minimal-ruby-app" to the custom domain&lt;/p&gt;
&lt;p&gt;The overall user experience with GKE wasn't actually significantly different to
that of using the 80 line OpenShift .gitlab-ci.yml mentioned above; just that
instead of using gitlab.com shared runners, the build/test/deploy work is done
from on Kubernetes. The major downside of the Kubernetes approach though (either
GKE or OpenShift if it worked), is that Auto Deploy magic is actually 800 lines
of shell script and YAML which, sooner or later, will need troubleshooting. So
while it makes for a slick webcast, I'm not sure this is a path I want to take
after all.&lt;/p&gt;</content><category term="Technology"></category><category term="devops"></category><category term="gitlab"></category><category term="kubernetes"></category><category term="openshift"></category><category term="work"></category></entry><entry><title>Practical Clojure/Java interop, CSV parsing with Apache Commons CSV</title><link href="https://stumbles.id.au/practical-clojurejava-interop-csv-parsing-with-apache-commons-csv.html" rel="alternate"></link><published>2018-04-03T00:00:00+10:00</published><updated>2018-04-03T00:00:00+10:00</updated><author><name>Ben Sturmfels</name></author><id>tag:stumbles.id.au,2018-04-03:/practical-clojurejava-interop-csv-parsing-with-apache-commons-csv.html</id><summary type="html">&lt;p&gt;One particularly powerful aspect of Clojure is that it allows you to use Java
features without &lt;em&gt;actually&lt;/em&gt; writing any Java. And the best way to get my head
around the Java interop features is to do a practical example; something like
I'd do for work. And as far as examples …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One particularly powerful aspect of Clojure is that it allows you to use Java
features without &lt;em&gt;actually&lt;/em&gt; writing any Java. And the best way to get my head
around the Java interop features is to do a practical example; something like
I'd do for work. And as far as examples go, they don't come any more practical
than CSV file reading.&lt;/p&gt;
&lt;p&gt;Given I'm just learning Clojure, to have any chance of getting the interop code
working, I first need to write the Java version. So how &lt;em&gt;would&lt;/em&gt; a Java person,
which I'm not, parse CSV? Using &lt;a href="https://commons.apache.org/proper/commons-csv/user-guide.html"&gt;Apache Commons
CSV&lt;/a&gt; I suppose.
Here's the CSV:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;id,greeting
1,Shiver me timbers!
2,Ahoy there!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;CSV parsing in Java&lt;/h2&gt;
&lt;p&gt;Here's the Java code, &lt;code&gt;PiratePhrases.java&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.FileReader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.IOException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;org.apache.commons.csv.CSVFormat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;org.apache.commons.csv.CSVParser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;org.apache.commons.csv.CSVRecord&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PiratePhrases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;FileReader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pirate.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CSVRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CSVFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withFirstRecordAsHeader&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSVRecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;greeting&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As I'm running Guix SD, I'll install Java and Commons CSV and compile and run it
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;guix&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;--install&lt;span class="w"&gt; &lt;/span&gt;icedtea&lt;span class="w"&gt; &lt;/span&gt;java-commons-csv
$&lt;span class="w"&gt; &lt;/span&gt;javac&lt;span class="w"&gt; &lt;/span&gt;-cp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.guix-profile/share/java/commons-csv.jar&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PiratePhrases.java
$&lt;span class="w"&gt; &lt;/span&gt;java&lt;span class="w"&gt; &lt;/span&gt;-cp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.guix-profile/share/java/commons-csv.jar&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PiratePhrases
Shiver&lt;span class="w"&gt; &lt;/span&gt;me&lt;span class="w"&gt; &lt;/span&gt;timbers!
Ahoy&lt;span class="w"&gt; &lt;/span&gt;there!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I've barely written an Java since university, and don't use any statically typed
languages at the moment so it was comforting to that &lt;code&gt;javac&lt;/code&gt; demanded I handle
&lt;code&gt;IOExceptions&lt;/code&gt;; even if I did fob it off by adding the exception to the
interface.&lt;/p&gt;
&lt;h2&gt;CSV parsing in Clojure&lt;/h2&gt;
&lt;p&gt;Any sane Clojure programmer would probably reach for a Clojure native CSV
library, like &lt;a href="https://clojure.github.io/data.csv/"&gt;data.csv&lt;/a&gt;, but since this is
an exercise, I'm going to stick with Commons CSV and translate this Java code as
literally as I can. The Clojure version, &lt;code&gt;pirate_phrases.clj&lt;/code&gt;, looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;import &lt;/span&gt;&lt;span class="nv"&gt;java.io.FileReader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;import &lt;/span&gt;&lt;span class="nv"&gt;org.apache.commons.csv.CSVFormat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;import &lt;/span&gt;&lt;span class="nv"&gt;org.apache.commons.csv.CSVParser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;import &lt;/span&gt;&lt;span class="nv"&gt;org.apache.commons.csv.CSVRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;reader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;FileReader.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pirate.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;records&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.parse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.withFirstRecordAsHeader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CSVFormat/DEFAULT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;doseq &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;record&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;records&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;record&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;greeting&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And, on Guix SD, is run like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;guix&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;--install&lt;span class="w"&gt; &lt;/span&gt;clojure
java&lt;span class="w"&gt; &lt;/span&gt;-cp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.guix-profile/share/java/clojure-1.9.0.jar:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.guix-profile/share/java/commons-csv.jar&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;clojure.main&lt;span class="w"&gt; &lt;/span&gt;pirate_phrases.clj
Shiver&lt;span class="w"&gt; &lt;/span&gt;me&lt;span class="w"&gt; &lt;/span&gt;timbers!
Ahoy&lt;span class="w"&gt; &lt;/span&gt;there!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This was a good chance to get familiar with a few of the interop features:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Importing with &lt;code&gt;(import java.io.FileReader)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Instantiating an object, where &lt;code&gt;new FileReader(path)&lt;/code&gt; becomes &lt;code&gt;(FileReader.
   path)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Accessing a class attribute/method, where &lt;code&gt;CSVFormat.DEFAULT&lt;/code&gt; becomes
   &lt;code&gt;CSVFormat/DEFAULT&lt;/code&gt;, and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Calling an instance method, where &lt;code&gt;record.get("greeting")&lt;/code&gt; becomes &lt;code&gt;(.get
   record "greeting")&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I particularly like that the Clojure notation clearly distinguishes class
attributes/methods from instance methods eg. &lt;code&gt;CSVFormat/DEFAULT&lt;/code&gt; vs. &lt;code&gt;(.get
record "greeting")&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I'll still be reaching for Python when a CSV file comes my way, but I was
pleasantly surprised at how neat the Clojure version of this code ended up.&lt;/p&gt;</content><category term="Technology"></category><category term="coding"></category><category term="clojure"></category><category term="java"></category><category term="guix"></category><category term="work"></category></entry><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;&lt;em&gt;Updated 21 September 2018 and 29 August 2019. See bottom of this post.&lt;/em&gt;&lt;/p&gt;
&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 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Updated 21 September 2018 and 29 August 2019. See bottom of this post.&lt;/em&gt;&lt;/p&gt;
&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 &lt;a href="https://groups.google.com/forum/#!topic/gnubee/vbKwd7r-8_8"&gt;firmware update&lt;/a&gt; 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;&lt;code&gt;&lt;span class="n"&gt;Downloading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ramips&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mt7621&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="o"&gt;***&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ramips&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mt7621&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;

&lt;span class="n"&gt;Downloading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mipsel_24kc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="o"&gt;***&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mipsel_24kc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;

&lt;span class="n"&gt;Downloading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mipsel_24kc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="o"&gt;***&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mipsel_24kc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;

&lt;span class="n"&gt;Collected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opkg_download&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ramips&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mt7621&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;returned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opkg_download&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mipsel_24kc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;returned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opkg_download&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;librecmc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mipsel_24kc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;returned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.&lt;/span&gt;
&lt;/code&gt;&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;
&lt;h1&gt;Update 21 Sep 2018&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://openwrt.org/releases/18.06/notes-18.06.0"&gt;OpenWRT v18.06&lt;/a&gt; was released
on 31 July. This release includes support for the GnuBee PC1 and PC2 devices and
represents a merge between OpenWRT and LEDE projects. Here's the &lt;a href="https://git.openwrt.org/?p=openwrt/openwrt.git;a=commit;h=c60a21532bc903ab86d971b478752152d4476ef8"&gt;GBPC2
commit&lt;/a&gt;.
Today I've installed in on my GBPC2.&lt;/p&gt;
&lt;h3&gt;Installing OpenWRT&lt;/h3&gt;
&lt;p&gt;As per the &lt;a href="https://openwrt.org/docs/guide-quick-start/factory_installation"&gt;OpenWRT
quickstart&lt;/a&gt; and
the &lt;a href="https://openwrt.org/toh/hwdata/gnubee/gnubee_personal_cloud_two"&gt;GBPC2 OpenWRT hardware
info&lt;/a&gt;,
the images are under 
&lt;a href="https://downloads.openwrt.org/releases/18.06.1/targets/ramips/mt7621/"&gt;here&lt;/a&gt;.
The one I needed was called "gnubee_gb-pc2-initramfs-kernel.bin". I verified the
sha256sum of the file and the GnuPG signature provided.&lt;/p&gt;
&lt;p&gt;I first plugged the serial cable into my GBPC2 and into my computer and ran
&lt;code&gt;sudo minicom -b 57600 -D /dev/ttyUSB0&lt;/code&gt;. This is invaluable to be able to see
what the device is doing.&lt;/p&gt;
&lt;p&gt;I plugged my GBPC2 directly into my computer (not a router) using the black
ethernet socket and visited the web interface at http://192.168.1.1. After
logging in, I went to "System", "Backup/Flash Firmware". Under "Flash new
firmware image", I unchecked "Keep settings" and selected the file
"gnubee_gb-pc2-initramfs-kernel.bin".&lt;/p&gt;
&lt;p&gt;The verification page pops up and prompts you to verify that the displayed hash
matches the file you downloaded. I checked this with &lt;code&gt;md5sum
openwrt-18.06.1-ramips-mt7621-gnubee_gb-pc2-initramfs-kernel.bin&lt;/code&gt; and proceeded
with the install. Following along on the serial console, I could see the device
reboot into OpenWRT. I found I had to switch off and on to get an IP address and
access the new web interface.&lt;/p&gt;
&lt;p&gt;The new firmware to work well enough, but doesn't seem to include support for
mounting SATA drives out of the box and would't let me install packages. To
access the Internet, changed the existing network interface to "DHCP client",
hit "apply and save" and plugged it into my router (weirdly there seems to be
some sort of auto-revert requiring you to make the change, re-plug the device
and log in in under 30 seconds or so, which took me a few shots). By looking up my router's
DHCP leases, I could find what the new IP address was to connect to.&lt;/p&gt;
&lt;p&gt;I first logged in via SSH and updated the package list with "opkg update" (web
interface would work fine too). When I tried to install a package via the web
interface or "opkg install" I got an error like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opkg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsync&lt;/span&gt;
&lt;span class="nx"&gt;Installing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m m-Double"&gt;3.1.3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="nx"&gt;Collected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;verify_pkg_installable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;kb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filesystem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;needs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;137&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opkg_install_cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cannot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rsync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I then downloaded the "gnubee_gb-pc1-squashfs-sysupgrade.bin", which I was now
able to install with the "Flash new firmware image" feature, presumably because
I already had OpenWRT installed. After repeating all the above I was able to log
in and &lt;code&gt;opkg install rsync&lt;/code&gt; and see that my SATA drive was detected at /dev/sda.&lt;/p&gt;
&lt;p&gt;I had some problems getting the drive to mount. Not entirely sure whether it was
permissions or a missing package. I installed "mountd" which enabled
automounting, but mounting was failing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# mount -t ext4 /dev/sda /mnt
mount: mounting /dev/sda on /mnt failed: Invalid argument
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I installed a few other packages including "e2fsprogs", "mount-utils" and
"kmod-fs-ext4". Eventually I realised that &lt;code&gt;mkdir ~/x; mount /dev/sda1 ~/x&lt;/code&gt;
worked fine, so changed the permissions on /mnt to 777 which seemed to fix that.
Weird though, since I was root. Anyhow, it's working, which is good enough for now.&lt;/p&gt;
&lt;h1&gt;Update 29 Aug 2019&lt;/h1&gt;
&lt;p&gt;For some time I've been accessing my GnuBee via it's dynamic IP address, since
zeroconf was no longer working after installing v18.06. That's downright
painful. Today I learn that this can be fixed by installing avahi:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;opkg update
opkg install avahi-daemon-service-ssh avahi-daemon-service-http
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;See the OpenWRT &lt;a href="https://openwrt.org/docs/guide-user/network/zeroconfig/zeroconf"&gt;zeroconf documentation&lt;/a&gt;.&lt;/p&gt;</content><category term="Technology"></category><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 …&lt;/p&gt;</summary><content 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;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &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;&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;QUERY&lt;span class="w"&gt; &lt;/span&gt;&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="w"&gt; &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;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TEMPFILE&lt;/span&gt;
adb&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TEMPFILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/data/local/tmp
adb&lt;span class="w"&gt; &lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;/data/local/tmp/&lt;span class="k"&gt;$(&lt;/span&gt;basename&lt;span class="w"&gt; &lt;/span&gt;&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;/code&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;</content><category term="Technology"></category><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 …&lt;/p&gt;</summary><content 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;&lt;code&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;/code&gt;&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/2017/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;code&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Just promise me you won't do this in production!&lt;/p&gt;</content><category term="Technology"></category><category term="coding"></category><category term="django"></category><category term="python"></category><category term="work"></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;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &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;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content 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;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &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;/code&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;&lt;code&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;/code&gt;&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;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &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;/code&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;</content><category term="Technology"></category><category term="coding"></category><category term="python"></category><category term="work"></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 …&lt;/p&gt;</summary><content 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;</content><category term="Personal"></category><category term="brown hill"></category><category term="work"></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;code&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content 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;code&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="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="w"&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="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&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="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&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;/code&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/2016/angular-favourite-colour.png" alt="Screenshot" /&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;code&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="cm"&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="w"&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="w"&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="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Extra &amp;#39;auto-value&amp;#39; dependency&lt;/span&gt;
&lt;span class="w"&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="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&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="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&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;/code&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;</content><category term="Technology"></category><category term="angular"></category><category term="javascript"></category><category term="coding"></category><category term="work"></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;/p&gt;</summary><content 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/2016/channel-flow-calculator-java.png" alt="Screenshot of
channel flow calculator" /&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;code&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="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;src&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;channel_flow.core&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="ss"&gt;:output-to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;out/main.js&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="ss"&gt;:optimizations&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&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;&lt;code&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;/code&gt;&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;code&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;/code&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;code&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;document.mannings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;input&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&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;code&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;/code&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;code&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;w1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;w2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&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;&lt;code&gt;:optimizations :advanced
:externs [&amp;quot;externs.js&amp;quot;]
&lt;/code&gt;&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/2016/channel-flow-calculator-clojurescript.png"
alt="Screenshot of channel flow calculator" /&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;</content><category term="Technology"></category><category term="coding"></category><category term="clojure"></category><category term="clojurescript"></category><category term="lisp"></category><category term="work"></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 …&lt;/a&gt;&lt;/p&gt;</summary><content 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;code&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Basic image resizing server, no caching.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;image_filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;resize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;image_filter_jpeg_quality&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;image_filter_buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;8M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&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;&lt;code&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;force&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;reload&lt;/span&gt;
&lt;/code&gt;&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;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ab&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://example.com/media/768/mountains.jpg
...
Requests&lt;span class="w"&gt; &lt;/span&gt;per&lt;span class="w"&gt; &lt;/span&gt;second:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.14&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="c1"&gt;#/sec] (mean)&lt;/span&gt;
...
&lt;/code&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;code&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Internal image resizing server.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;image_filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;resize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;image_filter_jpeg_quality&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;image_filter_buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;8M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/tmp/nginx-images-cache/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;levels=1:2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;keys_zone=images:10m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;inactive=24h&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Public-facing cache server.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&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="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Proxy to internal image resizing server.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;images&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_cache_valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;24h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&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="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# variables in proxy_pass. Creating this dummy location avoids that.&lt;/span&gt;
&lt;span class="w"&gt;        &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="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&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;</content><category term="Technology"></category><category term="coding"></category><category term="nginx"></category><category term="work"></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 …&lt;/p&gt;</summary><content 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/2016/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;</content><category term="Technology"></category><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 …&lt;/p&gt;</summary><content 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;</content><category term="Technology"></category><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;&lt;code&gt;$ grunt build
⋮
Running &amp;quot;ngtemplates:dist&amp;quot; (ngtemplates) task
&lt;/code&gt;&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;code&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content 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;&lt;code&gt;$ grunt build
⋮
Running &amp;quot;ngtemplates:dist&amp;quot; (ngtemplates) task
&lt;/code&gt;&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;code&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;/code&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;</content><category term="Technology"></category><category term="coding"></category><category term="angular"></category><category term="javascript"></category><category term="grunt"></category><category term="work"></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/2015/emacs-smerge-git.png"&gt;&lt;/p&gt;
&lt;p&gt;The feature works by looking for conflict files and markers …&lt;/p&gt;</summary><content 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/2015/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;code&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vc-bzr-find-file-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;buffer-file-name&lt;/span&gt;
&lt;span class="w"&gt;             &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="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;concat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;buffer-file-name&lt;/span&gt;&lt;span class="w"&gt; &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="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;concat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;buffer-file-name&lt;/span&gt;&lt;span class="w"&gt; &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="w"&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;concat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;buffer-file-name&lt;/span&gt;&lt;span class="w"&gt; &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="w"&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="w"&gt;             &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="w"&gt;             &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;save-excursion&lt;/span&gt;
&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;goto-char&lt;/span&gt;&lt;span class="w"&gt; &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="w"&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="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="w"&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="w"&gt;    &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="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;;; elisp function to remerge from the .BASE/OTHER/THIS files.&lt;/span&gt;
&lt;span class="w"&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;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;after-save-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;vc-bzr-resolve-when-done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &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;/code&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;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vc-git-find-file-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;save-excursion&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;goto-char&lt;/span&gt;&lt;span class="w"&gt; &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="w"&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="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&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;/code&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;</content><category term="Technology"></category><category term="coding"></category><category term="emacs"></category><category term="lisp"></category><category term="work"></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 …&lt;/p&gt;&lt;/blockquote&gt;</summary><content 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;</content><category term="Activism"></category><category term="patents"></category><category term="activism"></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 …&lt;/p&gt;</summary><content 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;</content><category term="Technology"></category><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 …&lt;/p&gt;</summary><content 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;&lt;code&gt;echo level 7 &amp;gt; /proc/acpi/ibm/fan
&lt;/code&gt;&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;&lt;code&gt;echo level full-speed &amp;gt; /proc/acpi/ibm/fan
&lt;/code&gt;&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;</content><category term="Technology"></category><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 …&lt;/p&gt;</summary><content 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;</content><category term="Activism"></category><category term="acta"></category><category term="tpp"></category><category term="activism"></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 …&lt;/p&gt;</summary><content 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;</content><category term="Activism"></category><category term="drm"></category></entry></feed>