<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Kamil Choudhury</title><link href="https://www.anserinae.net/" rel="alternate"/><link href="https://www.anserinae.net/feeds/all.atom.xml" rel="self"/><id>https://www.anserinae.net/</id><updated>2023-01-03T00:00:00-05:00</updated><subtitle>#define ZERO -1 // oh no it's technology all the way down</subtitle><entry><title>The Bangladesh Remittance Riddle</title><link href="https://www.anserinae.net/the-bangladesh-remittance-riddle.html" rel="alternate"/><published>2023-01-03T00:00:00-05:00</published><updated>2023-01-03T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2023-01-03:/the-bangladesh-remittance-riddle.html</id><summary type="html">&lt;p&gt;Thinking about Bangladesh's external solvency&lt;/p&gt;</summary><content type="html">&lt;p&gt;An ongoing dump of stuff I've been thinking about with respect to Bangladesh's recent foreign exchange crisis and how the private sector could help.&lt;/p&gt;
&lt;p&gt;Specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The problem Bangladesh is facing.&lt;/li&gt;
&lt;li&gt;Who's sending money home?&lt;/li&gt;
&lt;li&gt;How much money is currently being sent home?&lt;ul&gt;
&lt;li&gt;How is this money being sent home?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;How much money is still stuck outside Bangladesh?&lt;ul&gt;
&lt;li&gt;Is any of this money persuadable, i.e. willing to come home?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What might a modern platform to send money back to Bangladesh look like?&lt;ul&gt;
&lt;li&gt;How could such a platform be made financially sustainable?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="money"/><category term="bangladesh"/><category term="money"/><category term="remittance"/></entry><entry><title>We &lt;3 Govtech</title><link href="https://www.anserinae.net/we-3-govtech.html" rel="alternate"/><published>2022-07-27T00:00:00-05:00</published><updated>2022-07-27T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2022-07-27:/we-3-govtech.html</id><summary type="html">&lt;p&gt;I guess I have thoughts about how government technology should work&lt;/p&gt;</summary><content type="html">&lt;p&gt;Just a small placeholder to force the creation of the govtech tag on this blog.&lt;/p&gt;
&lt;p&gt;More to follow.&lt;/p&gt;</content><category term="govtech"/><category term="technology"/><category term="governance"/></entry><entry><title>Trade Model Thoughts</title><link href="https://www.anserinae.net/trade-model-thoughts.html" rel="alternate"/><published>2022-01-01T00:00:00-05:00</published><updated>2022-01-01T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2022-01-01:/trade-model-thoughts.html</id><summary type="html">&lt;p&gt;Ramblings on capturing trades in large scale trading systems&lt;/p&gt;</summary><content type="html">&lt;p&gt;This blog post started out as an internal memo, but nothing it reveals is terribly confidential, so I thought I'd post an extended version of it online and see what people have to say about it. I started working on it after posting this on Twitter:&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;The CDC now recommends financial firms implement technology to be able to mark all positions and all prices every day.&lt;/p&gt;&amp;mdash; Kamil Choudhury (@kchoudhu) &lt;a href="https://twitter.com/kchoudhu/status/1476373239156953088?ref_src=twsrc%5Etfw"&gt;December 30, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;

&lt;p&gt;The tweet jokingly cuts to the heart of what a modern financial institution needs from its technology stack: an accurate accounting of what is on its books, and what those positions are currently worth. Risk, analytics and trading systems are layered on top of this capability. Get this wrong function wrong, and your chances of having to do an expensive, productivity-killing technical retrofit approaches 1.&lt;/p&gt;
&lt;p&gt;The question then becomes: how do you build such a tracking system?&lt;/p&gt;
&lt;h1&gt;Double Entry Book Keeping Rules Everything Around You&lt;/h1&gt;
&lt;p&gt;Counterintuitively, the first step in building a trade capture system is to stop thinking in terms of individual trades.&lt;/p&gt;
&lt;p&gt;Instead, start thinking about position effects in double entry accounting. A quick refresher:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You have a ledger of accounts&lt;/li&gt;
&lt;li&gt;A transaction that creates a credit or debit in one account &lt;em&gt;must&lt;/em&gt; be countered by equal and opposite transaction notation in one (or more -- this becomes important later) other accounts&lt;/li&gt;
&lt;li&gt;If everything doesn't tie out, there is a break.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Trades are by-products of recording the position effects of transactions between two accounts; going long a security creates a positive position effect in one account, and a negative one elsewhere.&lt;/p&gt;
&lt;h1&gt;Building Blocks&lt;/h1&gt;
&lt;p&gt;I think it's easier to think about these things with a couple of simple examples. But first we need some primitives though, so here are a few of them in Python. They should translate pretty easily to other languages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Accounts&lt;/strong&gt; are simple:&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;Account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&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;add_position_effects&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;positions&lt;/span&gt;&lt;span class="o"&gt;=&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;&lt;/span&gt;
&lt;span class="sd"&gt;        Adds securities to account position store&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And here we have &lt;strong&gt;position effects&lt;/strong&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;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;PositionEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&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;&lt;/span&gt;
&lt;span class="sd"&gt;    Container class to store positions in security:value&lt;/span&gt;
&lt;span class="sd"&gt;    format&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&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;pos_effects&lt;/span&gt;&lt;span class="p"&gt;):&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;_position_effects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos_effects&lt;/span&gt;

    &lt;span class="nd"&gt;@property&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;inverse&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="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Return position effect object with quantities inverted&lt;/span&gt;

&lt;span class="sd"&gt;        Trust me.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PositionEffects&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="n"&gt;sec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
            &lt;span class="ow"&gt;in&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;_position_effects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&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 what might a &lt;strong&gt;trade&lt;/strong&gt; look like?&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;Trade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&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="fm"&gt;__init__&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;poseffects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;portfolio1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;portfolio2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="n"&gt;portfolio1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_position_effects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poseffects&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;portfolio2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_position_effects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poseffects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inverse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;A Super Simple Example&lt;/h1&gt;
&lt;h1&gt;&lt;/h1&gt;</content><category term="money"/><category term="trading"/><category term="securities"/></entry><entry><title>Route53 From First Principles</title><link href="https://www.anserinae.net/route53-from-first-principles.html" rel="alternate"/><published>2021-11-28T00:00:00-05:00</published><updated>2021-11-28T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2021-11-28:/route53-from-first-principles.html</id><summary type="html">&lt;p&gt;Dynamic DNS in a non-cloud world is... complicated.&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of my favorite AWS services -- and I have a lot of them! -- has to be Route53: it makes programmatically maintaining DNS entries for dynamic services extremely simple.&lt;/p&gt;
&lt;p&gt;But what if you don't have access to AWS?&lt;/p&gt;
&lt;h3&gt;Enter BIND&lt;/h3&gt;
&lt;p&gt;As it turns out, all of the tools to recreate Route53 are available via &lt;code&gt;bind&lt;/code&gt;. Installation on FreeBSD is easy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;bind911
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can correct the horrible mistake FreeBSD made in removing &lt;code&gt;nslookup&lt;/code&gt; from the base system by installing &lt;code&gt;bind-tools&lt;/code&gt;; don't intall it if you can get by with &lt;code&gt;drill&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;bind-tool
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Follow any the a million tutorials out there to get a basic DNS server up and running. In my case, I defined a zone in &lt;code&gt;/usr/local/etc/namedb/dynamic/anserinae.net.db&lt;/code&gt; for &lt;code&gt;anserinae.net&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ORIGIN .
$TTL 604800     ; 1 week
anserinae.net           IN SOA  installation01.anserinae.net. administrator.anserinae.net. (
                                4          ; serial
                                604800     ; refresh (1 week)
                                86400      ; retry (1 day)
                                2419200    ; expire (4 weeks)
                                604800     ; minimum (1 week)
                                )
                        NS      ns1.
                        A       208.97.148.219 ; how to resolve a naked lookup for anserinae.net
                        MX      10 mxe.    ; mailserver
$ORIGIN anserinae.net.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add the zone definition to the list of zones served by &lt;code&gt;bind&lt;/code&gt; in &lt;code&gt;/usr/local/etc/namedb/named.conf.local&lt;/code&gt;; the following configuration also allows hosts in &lt;code&gt;192.168.7.0/24&lt;/code&gt; to update the zone definition via RPC calls:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;acl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;AllowUpdateIPs&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="m m-Double"&gt;192.168.7.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;zone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;anserinae.net&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;master&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/usr/local/etc/namedb/dynamic/anserinae.net.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AllowUpdateIPs&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;RPC calls are made via the &lt;code&gt;rndc&lt;/code&gt; mechanism, and are authenticated using a pre-shared key. Simply installing the &lt;code&gt;bind&lt;/code&gt; should have generated a key in &lt;code&gt;/usr/local/etc/namedb/rndc.key&lt;/code&gt;, but just in case it didn't you can create one as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;key &amp;quot;rndc-key&amp;quot; {
        algorithm hmac-md5;
        secret &amp;quot;&amp;lt;your super secret md5 key&amp;gt;&amp;quot;;
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Plug your zone definition and rndc key into the main &lt;code&gt;/usr/local/etc/namedb/named.conf&lt;/code&gt; by adding the following to the bottom of the file:&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;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/local/etc/namedb/rndc.key&amp;quot;&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/local/etc/namedb/named.conf.local&amp;quot;&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, rig &lt;code&gt;named&lt;/code&gt; to run at startup by editing &lt;code&gt;/etc/rc.conf&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;named_enable=&amp;quot;YES&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point restart your machine or start &lt;code&gt;named&lt;/code&gt; to get the service running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;service named start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Updating BIND Remotely&lt;/h3&gt;
&lt;p&gt;Now that we have a bind server that can be updated remotely, we use our favorite programming language (Python, doi) to update it. First, a few preliminaries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip-3.8 install dnspython
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Supposing we want to create a new alias &lt;code&gt;forgedcharity.anserinae.net&lt;/code&gt; that resolves to 192.168.0.150 on a server running at 192.168.0.1, we can execute the following:&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;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tsigkeyring&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dns.tsig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HMAC_MD5&lt;/span&gt;

&lt;span class="n"&gt;bindhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;192.168.0.1&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;192.168.0.150&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;keyring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tsigkeyring&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_text&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;rndc-key&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;your super secrete md5 key&amp;gt;&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;anserinae.net.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyring&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;keyring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyalgorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HMAC_MD5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;forgedcharity&amp;#39;&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="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bindhost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If we check the zone file at &lt;code&gt;/usr/local/etc/namdb/dynamic/anserinae.net.db&lt;/code&gt;, we now see:&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;$ORIGIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.&lt;/span&gt;
&lt;span class="k"&gt;$TTL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;604800&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;; 1 week&lt;/span&gt;
&lt;span class="n"&gt;anserinae.net&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nc"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;SOA&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l"&gt;installation01.anserinae.net.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;administrator.anserinae.net.&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="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;; serial&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="mi"&gt;604800&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;; refresh (1 week)&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;; retry (1 day)&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="mi"&gt;2419200&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;; expire (4 weeks)&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="mi"&gt;604800&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;; minimum (1 week)&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="kt"&gt;NS&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="l"&gt;ns1.&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="l"&gt;208.97.148.219&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="kt"&gt;MX&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mxe.&lt;/span&gt;
&lt;span class="k"&gt;$ORIGIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;anserinae.net.&lt;/span&gt;
&lt;span class="k"&gt;$TTL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;; 1 second&lt;/span&gt;
&lt;span class="n"&gt;forgedcharity&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="kt"&gt;A&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="l"&gt;192.168.0.150&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point, carrying out an nslookup for &lt;code&gt;forgedcharity.anserinae.net&lt;/code&gt; should resolve correctly to the appropriate IP. Hooray!&lt;/p&gt;
&lt;h3&gt;Should I Do This In Production?&lt;/h3&gt;
&lt;p&gt;Probably not. Getting a barebones nameserver up and running is easy, but justifying the cost and complexity of deploying something like this when Route53 is available is difficult. As always, production turns fun proofs of concept into monstrosities... why not just let someone else do the heavy lifting for you?&lt;/p&gt;</content><category term="technology"/><category term="dns"/><category term="route53"/><category term="bind"/></entry><entry><title>Trying Out Latex</title><link href="https://www.anserinae.net/trying-out-latex.html" rel="alternate"/><published>2021-10-02T00:00:00-05:00</published><updated>2021-10-02T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2021-10-02:/trying-out-latex.html</id><summary type="html">&lt;p&gt;I hear all the cool kids are putting equations on the internet...&lt;/p&gt;</summary><content type="html">&lt;p&gt;A simple inline math equation: $x^2$&lt;/p&gt;
&lt;p&gt;A blocked equation: &lt;/p&gt;
&lt;p&gt;$$x^2$$&lt;/p&gt;
&lt;p&gt;And then an "I pretend I know stuff" equation: &lt;/p&gt;
&lt;p&gt;$$\frac{\partial V}{\partial t} + \frac{1}{2}\sigma^2 S^2 \frac{\partial^2 V}{\partial S^2} + rS\frac{\partial V}{\partial S} - rV = 0$$&lt;/p&gt;</content><category term="technology"/><category term="latex"/><category term="blogging"/></entry><entry><title>Book Talk - Fall; Or Dodge In Hell</title><link href="https://www.anserinae.net/book-talk-fall-or-dodge-in-hell.html" rel="alternate"/><published>2021-08-01T00:00:00-05:00</published><updated>2021-08-01T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2021-08-01:/book-talk-fall-or-dodge-in-hell.html</id><summary type="html">&lt;p&gt;Neal Stephenson does immortality and depresses us all.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Every couple of years I dedicate a week to reading Neal Stephenson's newest book. It took me two years to get around to it this time round (young kids and COVID have not been good for reading...), but I finally found enough time during vacation to read &lt;em&gt;Fall: Or Dodge In Hell&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I started recording my thoughts in this Twitter thread:&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Alright: tonight’s book is FALL: DODGE IN HELL by Neal Stephenson.&lt;/p&gt;&amp;mdash; Kamil Choudhury (@kchoudhu) &lt;a href="https://twitter.com/kchoudhu/status/1419104297573257218?ref_src=twsrc%5Etfw"&gt;July 25, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;

&lt;p&gt;I won't discuss the first 200 or so pages of the novel: a detailed exploration of online disinformation and its effect on red America must have been immensely topical to Stephenson as he was writing the book (probably in the 2017-18 timeframe?), but it is ultimately vestigial to the rest of the novel, which tries to get to the root -- in typically voluminous fashion -- of what it means, really, to be alive.&lt;/p&gt;
&lt;p&gt;The novel opens with Richard "Dodge" Forsyth dying during a routine medical procedure and  having his consciousness uploaded to a virgin quantum computing cloud. Stephenson walks us in excruciating detail through Dodge (known in-cloud as Edgod) forging a world to live in out of quantum chaos, and then forming a new society in it alongside other newly uploaded souls.&lt;/p&gt;
&lt;p&gt;The steady advance of upload technology in the real world results in power hierarchies organically forming in the cloud, and the fortuitous introduction of a Big Bad fills the back quarter of the novel leads to a moderately gripping Heroic Quest that ends with peace being restored to the (virtual) world, whose mythology now somehow explains our collective affinity for Greek mythology and our weirdly cross-culturally consistent creation myths. And oh yeah, as long as the cloud keeps running and expanding, humanity is effectivley immortal. Huzzah.&lt;/p&gt;
&lt;p&gt;To Stephenson's credit, the 900 or so pages in the novel fairly fly by.&lt;/p&gt;
&lt;p&gt;Unfortunately, accepting Dodge's virtual reality requires us to suspend belief about the 'real' world that ultimately hosts it. Stephenson tells us that creating and running a metaverse for departed souls is stupendously expensive, but hand waves the details away by invoking "financial bots". &lt;em&gt;Really&lt;/em&gt;? There is the potential for a first rate financial drama here that is left completely unexplored: alpha decays, endowments fail... what happens when souls have to be terminated because of operating account shortfalls?&lt;/p&gt;
&lt;p&gt;In the novel's universe, large swathes of middle America have been reduced to theocratic fundmentalism ("Ameristan") by the unrestricted flow of algorithmic disinformation ("Facebooking") on the internet. Left unexplored is how the denizens of this blighted subregion respond to the spiritual aftershock of &lt;em&gt;humanity becoming immortal&lt;/em&gt;. And how does the American left respond to similar stimuli? Stephenson asks us to accept without question that the coastal elite will endure in innovative quantum utopia because They Believe In Science. Sure the book  was written before Trump's COVID-fueled assault on good sense and science, but I would sooner believe that unicorns exist than accept that the left would be any better than the religious right at filtering insanity-inducing online disinformation.&lt;/p&gt;
&lt;p&gt;Meanwhile, as our main characters happily create a virtual world for the souls of hundreds of millions of their dearly departed friends and family, Enoch Root, the seemingly immortal character from elsewhere in the Stephenson Extended Universe, drops by to tell us that his mission has ended now that humanity is able to simulate itself into immortality. With twenty pages to go, the reader is left with the unsettling prospect of worlds simulating worlds in infinite recursive futility. It's &lt;a href="https://en.wikipedia.org/wiki/The_Ricks_Must_Be_Crazy"&gt;Rick and Morty&lt;/a&gt; for the literary crowd.&lt;/p&gt;
&lt;p&gt;Until now, no one who has slogged through one of his novels could seriously claim that Stephenson lacks vision or ambition: just think back to the richly realized worlds of &lt;em&gt;The Baroque Cycle&lt;/em&gt; or &lt;em&gt;Anathem&lt;/em&gt;. &lt;em&gt;Fall's&lt;/em&gt; flacid vision of immortality -- simulation without end in a computer playground, with no consciousness or link to what came before -- is profoundly unsatisfying and I cannot help but wonder why any of its characters would bend humanity's best efforts towards achieving it.&lt;/p&gt;
&lt;p&gt;Near the end of the novel, the quest for cheap electricity and heat dissipation leads to the dense colonization of space and the proposed enclosure of the sun in a Dyson sphere. The novel's Humanity would rather use the energy of its only star to simulate itself in virtual eternity than explore the farthest reaches of its actual reality.&lt;/p&gt;
&lt;p&gt;I would rather die.&lt;/p&gt;</content><category term="life"/><category term="books"/><category term="stephenson"/></entry><entry><title>Setting Up a Pkgsrc Repository</title><link href="https://www.anserinae.net/setting-up-a-pkgsrc-repository.html" rel="alternate"/><published>2020-07-20T00:00:00-05:00</published><updated>2020-07-20T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2020-07-20:/setting-up-a-pkgsrc-repository.html</id><summary type="html">&lt;p&gt;Fluency is multiple package managers is a character flaw, so just learn how to use &lt;code&gt;pkgsrc&lt;/code&gt;.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently found myself needing to distribute software to developers' MacOS machines. The options were limited, and not great: I have had issues with MacPorts in the past, and Homebrew reminds me of Gentoo circa 2005 (compiling everything from scratch is great!), along with a hair-raising tendency to take over &lt;code&gt;/usr/local&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Enter &lt;code&gt;pkgsrc&lt;/code&gt;: developed for NetBSD, it is intended from the ground up to maintain a clean separation between itself and base, and is dead simple to write packages for to boot. While the NetBSD &lt;a href="https://www.netbsd.org/docs/pkgsrc/index.html"&gt;handbook&lt;/a&gt; explains how to write packages in exhaustive detail, it is less clear about how to distribute custom binary packages to a wider audience once you have compiled them. This blog post aims to fill that gap.&lt;/p&gt;
&lt;h3&gt;Bootstrap &lt;code&gt;pkgin&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Obviously, you are on a Mac. Start by installing developer tools:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;xcode-select&lt;span class="w"&gt; &lt;/span&gt;--install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Go get a cup of coffee, this is going to take a while.&lt;/p&gt;
&lt;p&gt;Get your machine ready to install &lt;code&gt;pkgsrc&lt;/code&gt; binary packages using the &lt;code&gt;pkgin&lt;/code&gt; frontend:&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;BOOTSTRAP_TAR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bootstrap-macos14-trunk-x86_64-20200716.tar.gz&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;BOOTSTRAP_SHA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;395be93bf6b3ca5fbe8f0b248f1f33181b8225fe&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Download the bootstrap kit to the current directory.&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;https://pkgsrc.joyent.com/packages/Darwin/bootstrap/&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BOOTSTRAP_TAR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Verify the SHA1 checksum.&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;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BOOTSTRAP_SHA&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BOOTSTRAP_TAR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;check-shasum
shasum&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;check-shasum

&lt;span class="c1"&gt;# Install bootstrap kit to /opt/pkg&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;-zxpf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BOOTSTRAP_TAR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;/

&lt;span class="c1"&gt;# Reload PATH/MANPATH (pkgsrc installs /etc/paths.d/10-pkgsrc for new sessions)&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/usr/libexec/path_helper&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This code is from the &lt;a href="https://pkgsrc.joyent.com/install-on-osx/"&gt;Joyent&lt;/a&gt; &lt;code&gt;pkgsrc&lt;/code&gt; how-to, and it will change quarterly as they update their package sets, so do make sure to check out their website for fresh instructions.&lt;/p&gt;
&lt;h3&gt;Bootstrap the &lt;code&gt;pkgsrc&lt;/code&gt; Tree&lt;/h3&gt;
&lt;p&gt;Next, install the &lt;code&gt;pkgsrc&lt;/code&gt; tree on your machine. This is where you will develop your packages (or at least export them to after developing them somewhere else):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/opt

&lt;span class="c1"&gt;# Check to see if pkgsrc is already there. If it is, blow it away&lt;/span&gt;
&lt;span class="k"&gt;if&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;-d&lt;span class="w"&gt; &lt;/span&gt;./pkgsrc&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;./pkgsrc
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c1"&gt;# Pull in a compressed archive of the pkgsrc tree and unpack it&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;https://cdn.netbsd.org/pub/pkgsrc/current/pkgsrc.tar.xz
tar&lt;span class="w"&gt; &lt;/span&gt;xzf&lt;span class="w"&gt; &lt;/span&gt;pkgsrc.tar.xz

&lt;span class="c1"&gt;# Only root can install things&lt;/span&gt;
chown&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;root:wheel&lt;span class="w"&gt; &lt;/span&gt;./pkgsrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I put the tree in the &lt;code&gt;/opt&lt;/code&gt; directory created by the &lt;code&gt;pkgin&lt;/code&gt; bootstrap step. You could just as easily put it somewhere else.&lt;/p&gt;
&lt;h3&gt;Optional: Install Binary Versions of Build Tools&lt;/h3&gt;
&lt;p&gt;This step is optional in that &lt;code&gt;pkgsrc&lt;/code&gt; will compile the build tools for you if your custom package needs them, but necessary in that building GCC or libtool from scratch will take hours that could be better spent doing other things.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pkgin&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;gcc7&lt;span class="w"&gt; &lt;/span&gt;cmake&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;etc&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Develop Your Package&lt;/h3&gt;
&lt;p&gt;I'm not going to get into this (go see the &lt;a href="https://www.netbsd.org/docs/pkgsrc/index.html"&gt;handbook!&lt;/a&gt;), but I recommend you become intimately familiar with the functioning of the &lt;a href="https://pkgsrc.se/pkgtools/url2pkg"&gt;&lt;code&gt;url2pkg&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pkgsrc.se/pkgtools/createbuildlink"&gt;&lt;code&gt;createbuildlink&lt;/code&gt;&lt;/a&gt; packages, both which reduce the process of packaging external software into pkgsrc format to (basically) a point-and-click operation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pkgin&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;url2pkg&lt;span class="w"&gt; &lt;/span&gt;createbuildlink
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Serve Your Packages&lt;/h3&gt;
&lt;p&gt;Once you have finished building your software, you should have a directory of distributable software in &lt;code&gt;/opt/pkgsrc/packages/All&lt;/code&gt;. Serve it up using HTTP at &lt;code&gt;https://pkgdist.testdomain.com&lt;/code&gt;; I use &lt;code&gt;nginx&lt;/code&gt;, but it doesn't really matter what you use.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pkgin&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A simple configuration would look this:&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;user   nginx  nginx;&lt;/span&gt;
&lt;span class="na"&gt;worker_processes  1;&lt;/span&gt;

&lt;span class="na"&gt;events {&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# After increasing this value You probably should increase limit&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# of file descriptors (for example in start_precmd in startup script)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;worker_connections  1024;&lt;/span&gt;
&lt;span class="na"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;http {&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;default_type  application/octet-stream;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;gzip          on;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;include       /opt/pkg/etc/nginx/mime.types;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;keepalive_timeout  65;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;sendfile      on;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;access_log    /var/log/nginx/access.log;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;error_log     /var/log/nginx/error.log;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Redirect all insecure connections to HTTPS&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;server {&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="na"&gt;listen 80;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="na"&gt;return 301 https&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;//$host$request_uri&lt;/span&gt;&lt;span class="c1"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;server {&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="na"&gt;listen 443 ssl;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="na"&gt;server_name pkgdist.testdomain.com;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="na"&gt;include common.conf;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="na"&gt;root /opt/pkgsrc/packages/;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="na"&gt;location / {&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="na"&gt;autoindex on;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="na"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My SSL settings are in &lt;code&gt;common.conf&lt;/code&gt;; you probably don't need to see them. The format of your configuration will vary slightly depending on what operating system you are serving from (I can't believe I have to say this, but yes, you can serve MacOS packages from Ubuntu. Or FreeBSD. Or anything at all, really).&lt;/p&gt;
&lt;p&gt;Finally, index the package directory by using &lt;a href="https://pkgsrc.se/pkgtools/pkg_summary-utils"&gt;&lt;code&gt;pkg_summary-utils&lt;/code&gt;&lt;/a&gt; to create a &lt;code&gt;pkg_summary.gz&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pkgin&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pkg_summary-utils
pkg_update_summary&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;/opt/pkgsrc/packages/All/pkg_summary.gz&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/opt/pkgsrc/packages/All&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gzip -dc&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gzip -c&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Installing Your Binaries&lt;/h3&gt;
&lt;p&gt;Assuming you can go to &lt;code&gt;https://pkgdist.testdomain.com/All/&lt;/code&gt; and see a list of archives, you are now ready to install your software on your client Mac. Start by bootstrapping &lt;code&gt;pkgin&lt;/code&gt; on your client machine just like you did on your build machine.&lt;/p&gt;
&lt;p&gt;Next, add your new repository to the list of places the client &lt;code&gt;pkgin&lt;/code&gt; looks for packages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;REPO_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://pkgdist.testdomain.com/All
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;REPO_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/pkg/etc/pkgin/repositories.conf
grep&lt;span class="w"&gt; &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_ADDR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_FILE&lt;/span&gt;&lt;span class="si"&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="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_ADDR&lt;/span&gt;&lt;span class="si"&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;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_FILE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I cannot stress enough how important it is to &lt;em&gt;not&lt;/em&gt; have a trailing slash at the end of &lt;code&gt;REPO_ADDR&lt;/code&gt;; &lt;code&gt;pkgin&lt;/code&gt; will not read your repository if you do.&lt;/p&gt;
&lt;p&gt;And then install your newly developed software:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pkgin&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;your&lt;span class="w"&gt; &lt;/span&gt;package&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Make sure you add &lt;code&gt;/opt/pkg/bin&lt;/code&gt; and &lt;code&gt;/opt/pkg/sbin&lt;/code&gt; to your &lt;code&gt;$PATH&lt;/code&gt; so that you can access your newly installed software.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I started this project looking for a way to serve MacOS software to developer machines and realized part way through that I could use the same infrastructure to serve software to our production machines running Linux: &lt;code&gt;pkgsrc&lt;/code&gt; is, after all, fully cross-platform.&lt;/p&gt;
&lt;p&gt;It's not often that a weekend afternoon of hacking results in actionable, complexity-reducing engineering insights, but here we are: it is good to have options.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.netbsd.org/donations/"&gt;Donate to NetBSD&lt;/a&gt;&lt;/p&gt;</content><category term="technology"/><category term="pkgsrc"/><category term="packaging"/><category term="devops"/><category term="macos"/></entry><entry><title>Quick and Dirty Postgres Hacks</title><link href="https://www.anserinae.net/quick-and-dirty-postgres-hacks.html" rel="alternate"/><published>2020-05-12T00:00:00-05:00</published><updated>2020-05-12T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2020-05-12:/quick-and-dirty-postgres-hacks.html</id><summary type="html">&lt;p&gt;Postgres is great, I just need some help remembering things.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have a sieve-like memory, so I am going to start recording things about Postgres that I find myself googling every time I need to do them.&lt;/p&gt;
&lt;h4&gt;Resetting sequences&lt;/h4&gt;
&lt;p&gt;An attempt to insert a row into a table with a &lt;code&gt;serial&lt;/code&gt; fails because the someone has manually inserted a value and failed to update the sequence.&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;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;equities_id_seq&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="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;equities&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;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;equities_id_seq&lt;/code&gt; is the Postgres sequence object&lt;/li&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt; is the &lt;code&gt;serial&lt;/code&gt; being corrected, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;equities&lt;/code&gt; is the table&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Adding sequence to an already existing table&lt;/h4&gt;
&lt;p&gt;The problem: you have a column with&lt;/p&gt;
&lt;h4&gt;What's holding locks in my database?&lt;/h4&gt;
&lt;p&gt;This query (or some variant of it) gives you all the queries currently holding locks on your database.&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;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;distinct&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;age&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;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transactionid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;GRANTED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_start&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_locks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%a.query%&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;F**k this lock in particular&lt;/h4&gt;
&lt;p&gt;Politely tell a troublesome query identified using the last command:&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;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_cancel_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Go nuclear on said query:&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;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_terminate_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Turn autovacuum off for a table&lt;/h4&gt;
&lt;p&gt;Don't do this lightly: autovacuum is needed for postgres to function well. But if you're doing periodic bulk loads on a table that has had surgery done it and autovacuum keeps interrupting the party...&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;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;autovacuum_enabled&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;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Finding how big a relation is&lt;/h4&gt;
&lt;p&gt;This one is pretty nifty:&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;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_total_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;table&amp;gt;&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Querying how many rows are in a table without querying the table&lt;/h4&gt;
&lt;p&gt;This function is a roundabout way to get an &lt;em&gt;estimate&lt;/em&gt; of how many rows are in a table without actually querying it:&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;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FUNCTION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count_estimate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FUNCTION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count_estimate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;RETURNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;func$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&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="n"&gt;rec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXECUTE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;EXPLAIN &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="n"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LOOP&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&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;SUBSTRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;QUERY PLAN&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; rows=([[:digit:]]+)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;EXIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;RETURN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;func$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LANGUAGE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can then obtain a reasonable estimate of a query return size by executing:&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;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count_estimate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;select * from &amp;lt;table&amp;gt;&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 results returned by this function will get progressively worse as dead tuples accumulate; make sure &lt;code&gt;autovacuum&lt;/code&gt; is turned on for the table!&lt;/p&gt;
&lt;h4&gt;Removing a sequence from a table&lt;/h4&gt;
&lt;p&gt;Sometimes you'll create a table with a sequence, add a bunch of data and then find that you don't need the sequence anymore.&lt;/p&gt;
&lt;p&gt;First you need to find out what the sequence is called:&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;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;refobjid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;objid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sequence&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_depend&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;refobjid&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="s1"&gt;&amp;#39;&amp;lt;table_name&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deptype&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="s1"&gt;&amp;#39;a&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;Then you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove the sequence from the table:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SEQUENCE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;sequence&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;OWNED&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NONE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Alter the table to not assign from the sequence by default:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COLUMN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Convert the type of the id column back to &lt;code&gt;int&lt;/code&gt; from &lt;code&gt;serial&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expiration_rules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COLUMN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exp_row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h6&gt;Post Metadata&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;Updated 5/15/2020: Added sequence assignment&lt;/li&gt;
&lt;li&gt;Updated 6/10/2020: Added autovacuum shenanigans&lt;/li&gt;
&lt;li&gt;Updated 6/15/2020: Added relation size and count estimate queries&lt;/li&gt;
&lt;li&gt;Updated 4/13/2022: Added sequence removal&lt;/li&gt;
&lt;/ul&gt;</content><category term="technology"/><category term="postgres"/></entry><entry><title>The Butter Chicken Recipe</title><link href="https://www.anserinae.net/the-butter-chicken-recipe.html" rel="alternate"/><published>2020-05-10T00:00:00-05:00</published><updated>2020-05-10T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2020-05-10:/the-butter-chicken-recipe.html</id><summary type="html">&lt;p&gt;There's no reason chicken should be a healthy meal&lt;/p&gt;</summary><content type="html">&lt;h3&gt;Ingredients&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;4 pounds of chicken. A mix of thigh and breast meat is the way to go.&lt;/li&gt;
&lt;li&gt;8 tablespoons of olive oil.&lt;/li&gt;
&lt;li&gt;2 teaspoons of red chili powder. I use the Kashmiri variant, but don't get hung up on it.&lt;/li&gt;
&lt;li&gt;3 cans of crushed tomatoes.&lt;/li&gt;
&lt;li&gt;6 ounces of cashew nuts.&lt;/li&gt;
&lt;li&gt;16 ounces of whipped greek yogurt.&lt;/li&gt;
&lt;li&gt;8-10 garlic cloves. If you want more, don't be shy about it.&lt;/li&gt;
&lt;li&gt;2.5 large white onions.&lt;/li&gt;
&lt;li&gt;4 tablespoons of butter.&lt;/li&gt;
&lt;li&gt;3 teaspoons of sugar&lt;/li&gt;
&lt;li&gt;2 teaspoons of grape vinegar.&lt;/li&gt;
&lt;li&gt;Salt to taste&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Recipe&lt;/h3&gt;
&lt;p&gt;Mix chili powder and chicken, and brown the mixture 4 tablespoons of olive oil. Set aside. Do &lt;em&gt;not&lt;/em&gt; discard the juices.&lt;/p&gt;
&lt;p&gt;While the meat is browning, use a mixer to blend cashew nuts, tomatoes and yogurt until the cashew nuts have been reduced to fine grains. Set aside.&lt;/p&gt;
&lt;p&gt;Dice two onions crudely, and blend them with the garlic cloves in the mixer.&lt;/p&gt;
&lt;p&gt;Chop remaining half onion finely, and fry in the same pot used to brown meat.&lt;/p&gt;
&lt;p&gt;Once the fried onions start browning, add the blended onion/garlic mixture and the tomato/cashew puree. Add in the butter and sugar, and a first helping of salt (I usually do about 1 teaspoon).&lt;/p&gt;
&lt;p&gt;Turn the heat up and cover the pot; the sauce will turn a characteristic orange-red in about fifteen minutes. When it does, force the mixture through a strainer to remove its lumpiness: the &lt;em&gt;makhani&lt;/em&gt; in this recipe refers not only to the butter used in the recipe, but its texture as well.&lt;/p&gt;
&lt;p&gt;Add the chicken back into strained sauce along with the grape vinegar, and cook on high heat until the meat is tender. Continue cooking on lower heat until you're happy with the consistency of the sauce: I like it pretty thick, but this is &lt;em&gt;your&lt;/em&gt; dinner.&lt;/p&gt;
&lt;h3&gt;Final Dispositions&lt;/h3&gt;
&lt;p&gt;This is a fast and loose recipe, and I tend to modify it as I cook. My only advice is to keep adding salt at the end until it tastes like butter chicken. Serve with hot white rice, and be prepared for the coronary which is certain to follow.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.twitter.com/SkeleCap"&gt;@SkeleCap&lt;/a&gt; has an interesting take on this recipe that involves a jar of Rao's pasta sauce. I'm not going to steal his thunder though -- make sure you make him tell you about it.&lt;/p&gt;</content><category term="life"/></entry><entry><title>Book Talk - Pakistan: A Hard Country</title><link href="https://www.anserinae.net/book-talk-pakistan-a-hard-country.html" rel="alternate"/><published>2020-01-06T00:00:00-05:00</published><updated>2020-01-06T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2020-01-06:/book-talk-pakistan-a-hard-country.html</id><summary type="html">&lt;p&gt;My neighbor writes books, and they are quite good.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Some books merit more than a blurb on the &lt;a href="/culture-report-2020-edition.html"&gt;annual culture review&lt;/a&gt;, and &lt;a href="https://www.amazon.com/Pakistan-Hard-Country-Anatol-Lieven/dp/1610391454"&gt;Pakistan: A Hard Country&lt;/a&gt; by Anatol Lieven is one of them. This entry serves as a dumping ground for my notes as I read the book, and will probably eventually be polished up into something more readable when I finish processing it.&lt;/p&gt;
&lt;p&gt;I initially came across this list on &lt;a href="https://twitter.com/kchoudhu/status/1163312608054861824?s=20"&gt;my main man Tyler's&lt;/a&gt; annual &lt;a href="https://marginalrevolution.com/marginalrevolution/2011/05/pakistan-a-hard-country.html"&gt;reading list&lt;/a&gt;, and it stuck out to me because Anatol is actually my neighbor in Doha and I desperately need something light and airy to talk to him about when I have him over to brunch in a couple of weeks. So: Pakistan.&lt;/p&gt;</content><category term="life"/><category term="books"/><category term="pakistan"/></entry><entry><title>Dumb Things To Do With Money</title><link href="https://www.anserinae.net/dumb-things-to-do-with-money.html" rel="alternate"/><published>2020-01-05T00:00:00-05:00</published><updated>2020-01-05T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2020-01-05:/dumb-things-to-do-with-money.html</id><summary type="html">&lt;p&gt;Money is nice, but what if you could have less of it?&lt;/p&gt;</summary><content type="html">&lt;p&gt;A couple of weeks ago I posted this on Twitter:&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Bored and lacking excitement in my life, I am thinking of buying 3x leverage ETFs as part of my year end rebalance, send twete.&lt;/p&gt;&amp;mdash; Kamil Choudhury (@kchoudhu) &lt;a href="https://twitter.com/kchoudhu/status/1210984511141564417?ref_src=twsrc%5Etfw"&gt;December 28, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;

&lt;p&gt;Just to be absolutely clear, I believe in simple retirement portfolios: the bulk of my retirement funds are invested in a two mutual-fund portfolio with a 90/10 stock/bond split that has (predictably) done quite well over the last decade. If mutual fund prospectii are to be believed, I paid 0.04% of my portfolio value in expenses last year. As I get older, I'll cut the stock/bond ratio to something less risky.&lt;/p&gt;
&lt;p&gt;But the experience has been &lt;em&gt;boring&lt;/em&gt;. You take a bit of money every month, and stash it away in several designated accounts, and then forget about it. I miss the excitement -- remembered from several extremely poor decisions I made after joining the workforce out of college -- of having a substantial amount of money exposed to the depredations of markets.&lt;/p&gt;
&lt;p&gt;Bottom line: boring portfolio planning has worked for my family and the lion's share of our money will remain invested that way. This year though, I'll put money to work doing something...spicier. I'm not sure what, but here are the basic parameters of the additional strategy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The investment amount is going to be nominal in absolute portfolio terms, but substantial from a psychological point of view.&lt;/li&gt;
&lt;li&gt;It is going to be a rational investment. I am not rolling dice. There will be due diligence involved.&lt;/li&gt;
&lt;li&gt;I will post receipts.&lt;/li&gt;
&lt;li&gt;At the conclusion of the strategy, 10% of any profit goes to a 501c3 organization TBD. Suggestions welcome.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consider this an index page for the venture; I'll update it as time goes by.&lt;/p&gt;</content><category term="money"/><category term="money"/><category term="investments"/><category term="speculation"/></entry><entry><title>Culture Report, 2020 Edition</title><link href="https://www.anserinae.net/culture-report-2020-edition.html" rel="alternate"/><published>2020-01-03T00:00:00-05:00</published><updated>2020-01-03T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2020-01-03:/culture-report-2020-edition.html</id><summary type="html">&lt;p&gt;Book reports for all!&lt;/p&gt;</summary><content type="html">&lt;h3&gt;Books&lt;/h3&gt;
&lt;p&gt;What follows is a constantly updated list of books I read in 2020. At the very least it will save me the trouble of having to write up a humblebrag post at the end of the year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.amazon.com/Billion-Dollar-Whale-Fooled-Hollywood/dp/031643650X"&gt;Billion Dollar Whale: The Man Who Fooled Wall Street, Hollywood, and the World&lt;/a&gt; by Bradley Hope and Tom Wright: The book details the misadventures of Jho Low as he steals several billion dollars from the Malaysian government by exploiting holes in bank compliance departments across the US, Europe and South East Asia.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.amazon.com/A-Gentleman-in-Moscow/dp/0143110438"&gt;A Gentleman In Moscow&lt;/a&gt; by Amor Towles: Charting a Russian aristocrat's house arrest in the &lt;a href="https://en.wikipedia.org/wiki/Hotel_Metropol_Moscow"&gt;Metropol Hotel&lt;/a&gt; following the 1917 Revolution, it answers a very important question with unusual deftness: can meaning be found when one's circumstances are drastically reduced? The answer is perhaps not surprisingly in the affirmative, but is conveyed in language that somehow simultaneously evokes Wodehouse and Tolstoy. Best line in the book (and there are many, &lt;em&gt;many&lt;/em&gt; candidates): "It is a gentleman's business to change with the times." Highly recommended.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.amazon.com/Super-Pumped-Battle-Mike-Isaac/dp/0393652246"&gt;Super Pumped: The Battle For Uber&lt;/a&gt; by Mike Isaac: Removed from Twitter rage, the book is a surprisingly dispassionate (and probably definitive) retelling of the story of Travis Kalanick's rise and fall at Uber. There aren't really any surprises for anyone who followed the saga as it unfolded, but is nonetheless a good reference summary to have on the bookshelf.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.amazon.com/Sleeping-Giants-Themis-Sylvain-Neuvel/dp/1101886714"&gt;Sleeping Giants&lt;/a&gt; by Sylvain Neuvel: Shadowy unnamed operator/narrator assembles crew of Super Smart Authority Hating Misfits to rebuild a 200 foot gund^H^H^H^H robot left by aliens who are also actually the basis of all gods in Greek mythology. An attempt to consider the geopolitical implications of this discovery is undermined by the author's complete inability to write women: the &lt;em&gt;Beautiful But Tough&lt;/em&gt; main character works out her emotional issues by having sex with her male coworkers who are, respectively, &lt;em&gt;Nice Guy With Jealousy Issues&lt;/em&gt; and &lt;em&gt;Nerd With Secret Tough Streak&lt;/em&gt;. Oof. On the positive side, the book is 300 pages long, and took only an afternoon to read; I will leave you to guess if I will be reading either of the two sequels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.amazon.com/Pakistan-Hard-Country-Anatol-Lieven/dp/1610391454"&gt;Pakistan: A Hard Country&lt;/a&gt; by Anatol Lieven: Merits way more than a little blurb; my notes and review are &lt;a href="/book-talk-pakistan-a-hard-country.html#book-talk-pakistan-a-hard-country"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.amazon.com/Foreign-Devils-Silk-Road-Treasures-ebook/dp/B005HWB2QW"&gt;Foreign Devils on the Silk Road: The Search for the Lost Treasures of Central Asia&lt;/a&gt; by Peter Hopkirk: This retelling of Europe's steady exploration and theft of lost Silk Road civilizations in the early 20th century throws the characters of colonial explorers into bright relief: most were hucksters, and while serious academics did take part in the race for Central Asia, most let their thirst for scholarly fame overwhelm any sense of ethics they may have started out with. While much is made of Chinese laments about stolen cultural artifacts, it is difficult to take them too seriously in light of their contemporary efforts to erase said cultures by driving their Central Asians populations into concentration camps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.amazon.com/Sympathizer-Novel-Pulitzer-Prize-Fiction/dp/0802124941"&gt;The Sympathizer&lt;/a&gt; by Viet Thanh Nguyen: Very close to being one of the best contemplations about the immigrant experience in America I have ever read. The book crystallized for me the fact that a part of me will never be American, and will instead always be lost abroad to a place that neither wants nor needs me. The book is also an excellent mini-history of the Fall of Saigon, and a serious reflection on the nature of American colonialism and how we see and forgive itself in the aftermath of the wars we start abroad.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.amazon.com/dp/B06XQZPVDD/ref=dp-kindle-redirect?_encoding=UTF8&amp;amp;btkr=1"&gt;The Future Is History: How Totalitarianism Reclaimed Russia&lt;/a&gt; by Masha Gessen: Gripping as a history of the revitalization of Russian totalitarianism since 1991, this book is far less convincing when it attempts to cast said revitalization as a manifestation of collective psychological trauma. A background in Hannah Arendt's work is almost mandatory to fully appreciate what Gessen is trying to drive at, but the argument that Russia can only exist in a stable equilibrium as a totalitarian state because of the trauma caused by Stalin almost a century ago never really landed for me.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Music&lt;/h3&gt;
&lt;p&gt;I stumble upon new music via Spotify these days, and don't really actively curate my music collection anymore. Instead, I thought I would just list the best stuff I find here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://open.spotify.com/album/4YGQn2C71NbOEgSQM9fxRt?si=b1MWU7tqTYu4ccVYZLZhgA"&gt;Goldfrapp - Seventh Tree&lt;/a&gt; (2008): I fell off the Goldfrapp train after leaving college fifteen (jesus!) years ago. Listening to this album was an exercise in dissonance, because it doesn't sound anything like &lt;a href="https://open.spotify.com/album/65BnZsIXnhBeBReAHAaF6V?si=sFpXiAIUScuAXjQEG2yRmA"&gt;Supernature&lt;/a&gt; or &lt;a href="https://open.spotify.com/album/1R77epa6N8qyWYFCa41S9f?si=Sh5SCxJBQdiOkSQJdUiCPg"&gt;Black Cherry&lt;/a&gt;. Much poppier than its predecessors (I thought I was listening to a Kylie Minogue album for the first few songs), &lt;em&gt;Seventh Tree&lt;/em&gt; is still lushly produced and a regular fixture on my daily playlist.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://open.spotify.com/album/1wwazNvPsfWfQU6vkmZiR4?si=otDywyveQkqGalZxDOVIoQ"&gt;Goldfrapp - Head First&lt;/a&gt; (2010): Having put out a highly competent pop rock album in the form of &lt;em&gt;Seventh Tree&lt;/em&gt;, Goldfrapp reinvents itself &lt;em&gt;yet again&lt;/em&gt; with peppy lyrics and a heavy dose of synthwave. It must have taken serious balls to release this album into the musical hellscape that was the early 2010s, and I can't help but think that it was a sign of things to come: just a few years later, everyone would be obsessed with &lt;a href="https://www.youtube.com/watch?v=MV_3Dpw-BRY"&gt;Nightcall&lt;/a&gt; and scorpion-logo jackets.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;The resurgence of synthwave in the last half of the 2010s was one of the few redeeming parts of an exceedingly terrible decade... I have a lot to say about it, but this &lt;a href="https://www.vice.com/en_us/article/zm9qg4/synthwave-is-the-next-lofi-hip-hop-radio-to-relaxstudy-to"&gt;Vice article&lt;/a&gt; does a much better job than I ever could.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="life"/><category term="books"/><category term="reading"/><category term="humblebragging"/></entry><entry><title>The Reverse Proxy Test</title><link href="https://www.anserinae.net/the-reverse-proxy-test.html" rel="alternate"/><published>2019-11-21T00:00:00-05:00</published><updated>2019-11-21T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2019-11-21:/the-reverse-proxy-test.html</id><summary type="html">&lt;p&gt;You need to have suffered to know why this article exists&lt;/p&gt;</summary><content type="html">&lt;p&gt;A quick blush to see if my reverse proxy is working correctly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;http://www.anserinae.net/the-reverse-proxy-test.html
https://www.anserinae.net/the-reverse-proxy-test.html
=&amp;quot;https://www.anserinae.net/the-reverse-proxy-test.html&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And then this: &lt;code&gt;="https://www.anserinae.net/the-reverse-proxy-test.html"&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Just saying&lt;/p&gt;</content><category term="technology"/><category term="nginx"/><category term="reverse proxy"/></entry><entry><title>The Cuban Bean Recipe</title><link href="https://www.anserinae.net/the-cuban-bean-recipe.html" rel="alternate"/><published>2019-08-19T12:00:00-05:00</published><updated>2019-08-19T12:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2019-08-19:/the-cuban-bean-recipe.html</id><summary type="html">&lt;p&gt;Striving for high quality, bean-based blogspam&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm working on updating my &lt;a href="/recursive-blogging.html"&gt;blogging workflow&lt;/a&gt;. Instead of making a "test post, please ignore" post, I thought I'd do a "delicious bean recipe, please enjoy" post.&lt;/p&gt;
&lt;h3&gt;Ingredients&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Lots of olive oil&lt;/li&gt;
&lt;li&gt;1 cup of diced purple onions&lt;/li&gt;
&lt;li&gt;As much garlic as you can reasonably see yourself eating&lt;/li&gt;
&lt;li&gt;1 shot glass of oregano&lt;/li&gt;
&lt;li&gt;4 tablespoons of lime juice&lt;/li&gt;
&lt;li&gt;3 cans of Goya black beans&lt;/li&gt;
&lt;li&gt;2 red peppers, sliced into not too small pieces&lt;/li&gt;
&lt;li&gt;3 packets of &lt;a href="https://www.goya.com/en/products/seasonings/sazon"&gt;Goya "sazón"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2 tablespoons of cumin&lt;/li&gt;
&lt;li&gt;3 teaspoons of salt&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Recipe&lt;/h3&gt;
&lt;p&gt;Sautee the onions and olive oil in a dutch oven until they are soft. Mix in the oregano and garlic, and sautee them some more. When it smells delicious (but before the onions start burning), add the remaining ingredients. Close the lid, and put the pot in the oven at 225F for four hours, stirring maybe once an hour to ensure that nothing is burning.&lt;/p&gt;
&lt;p&gt;You can eat it the same day you cook it, but it's much, much better if you let it sit overnight in the fridge and then reheat it the next day.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This is usually the part of the recipe where I'm supposed to reminisce about how my family learned to heal and love after my brother came out as gay at my quadriplegic grandmother's funeral after she died of stage four cancer while whispering the recipe to me on her deathbed.&lt;/p&gt;
&lt;p&gt;Unfortunately, this recipe has no more interesting backstory than "I cook it a lot, it's pretty good, and my children prefer it to chicken nuggets".&lt;/p&gt;
&lt;p&gt;It's probably worth a shot.&lt;/p&gt;</content><category term="life"/><category term="recipes"/><category term="spam"/><category term="beans"/></entry><entry><title>One-Line Timing in Python</title><link href="https://www.anserinae.net/one-line-timing-in-python.html" rel="alternate"/><published>2019-08-19T00:00:00-05:00</published><updated>2019-08-19T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2019-08-19:/one-line-timing-in-python.html</id><summary type="html">&lt;p&gt;When profiling is too heavyweight, a simple timer will sometimes do.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm generally a huge proponent of &lt;a href="/python-profiling-for-babies.html"&gt;profiling&lt;/a&gt; code to discern its performance characteristics, but sometimes dragging a full blown profiler into code is a pain. What if you just want to see how much wall-clock time a particular piece of code takes to run?&lt;/p&gt;
&lt;p&gt;I use the following snippet so frequently for simple timing that I've moved it into a utility library that gets sucked into substantially all of my projects:&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;time&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Timer&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="fm"&gt;__enter__&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clock&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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__exit__&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&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;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;()&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;interval&lt;/span&gt; &lt;span class="o"&gt;=&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;end&lt;/span&gt; &lt;span class="o"&gt;-&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;start&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&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;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When I need to time how long a piece of code takes to run, using it it's as simple as something 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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;do_something&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Not much else to add here, other than the fact that I love context managers.&lt;/p&gt;</content><category term="technology"/><category term="python"/><category term="context manager"/><category term="timing"/></entry><entry><title>Caching Object Graphs Is Terrifying</title><link href="https://www.anserinae.net/caching-object-graphs-is-terrifying.html" rel="alternate"/><published>2019-07-07T00:00:00-05:00</published><updated>2019-07-07T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2019-07-07:/caching-object-graphs-is-terrifying.html</id><summary type="html">&lt;p&gt;A memo to remind myself that I should never, ever attempt to write a caching system again.&lt;/p&gt;</summary><content type="html">&lt;p&gt;All systems start out with a webserver and a database. This arrangement promptly keels over at the first sign of any sustained load, at which point the bright-eyed engineer who &lt;em&gt;really&lt;/em&gt; should know better says "let me just add some caching". Two weeks later, the engineer's daughter is asking why Daddy "has a lot of wizard hairs", and the engineer has a thousand mile stare.&lt;/p&gt;
&lt;p&gt;Caching, as the old saying goes, is hard. Caching &lt;em&gt;graphs of objects&lt;/em&gt; is even harder. Read on to find out why.&lt;/p&gt;
&lt;h2&gt;Example Object Chain&lt;/h2&gt;
&lt;p&gt;We will use the following trie of interdependent objects to guide our discussion; objects further up the trie depend on the ones lower down.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     F(E)   G(E)
       \    / \
        \  /   \           ⬆
       E(B,C)  D(C)     upstream
        /  \   /           ⬆
       /    \ /
     B(A)    C
      |
      |
      A
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Assumptions&lt;/h2&gt;
&lt;p&gt;Our first assumption is that all downstream nodes already exist in the database before we attempt to persist the parent.&lt;/p&gt;
&lt;p&gt;In the above chain, &lt;code&gt;A&lt;/code&gt; would need to exist in the database before &lt;code&gt;B(A)&lt;/code&gt; or &lt;code&gt;C(A)&lt;/code&gt; could be created, and those would in turn have to exist before &lt;code&gt;E(B,C)&lt;/code&gt; or &lt;code&gt;D(B)&lt;/code&gt; could be constituted.&lt;/p&gt;
&lt;p&gt;I like to call the second assumption in this system &lt;strong&gt;The Guarantee&lt;/strong&gt;: when a cache item is created, updated or touched, all objects that depend on it are touched/created &lt;em&gt;before&lt;/em&gt; it is finalized.&lt;/p&gt;
&lt;p&gt;The reasoning behind this is driven by the LRU (least recently used) eviction algorithm used by memcache: since we guarantee that dependencies are going to be touched &lt;em&gt;before&lt;/em&gt; their parents, the nightmare scenario of, say, &lt;code&gt;E(B,C)&lt;/code&gt; being evicted and leaving no way for an invalidation signal to travel from &lt;code&gt;A&lt;/code&gt; to &lt;code&gt;F(E)&lt;/code&gt; or &lt;code&gt;G(E)&lt;/code&gt; can never come to pass. At most, a leaf such as &lt;code&gt;A&lt;/code&gt; can be evicted; this is okay, because it will be recreated in a consistent manner when it is next accessed (see &lt;code&gt;Search&lt;/code&gt; below for details).&lt;/p&gt;
&lt;h2&gt;Caching Primitive&lt;/h2&gt;
&lt;p&gt;Keep it simple: for each node persisted, use a cryptographic hash (I use SHA256, feel free to choose your own poison) of the node as a key, and persist a pickled instance of this object as a value:&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;CacheObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# A direct copy of what&amp;#39;s coming out of the database&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="c1"&gt;# *Upstream* Nodes that need to be notified of changes to this node&lt;/span&gt;
    &lt;span class="n"&gt;dependencies&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;h2&gt;Database Operations&lt;/h2&gt;
&lt;p&gt;Cache operations are married to database operations and transparent to the caller; cache actions for each possible database operation are listed below.&lt;/p&gt;
&lt;h4&gt;Create&lt;/h4&gt;
&lt;p&gt;If a database entry is not available, return an empty cache object. The client can figure out what to do with it.&lt;/p&gt;
&lt;p&gt;Begin by creating an entry in cache.&lt;/p&gt;
&lt;p&gt;If there are the node has downstream entries (e.g. &lt;code&gt;E(B,C)&lt;/code&gt; or &lt;code&gt;D(C)&lt;/code&gt;), DFS traverse the downstream nodes...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating cache entries for each and...&lt;/li&gt;
&lt;li&gt;Adding the parent's key to each child's &lt;code&gt;dependency&lt;/code&gt; array. This will form the basis of the cache update notification/invalidation chain later on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that writes to cache for any given node happen post-traverse, meaning that The Guarantee is honored: each child is updated before its parent.&lt;/p&gt;
&lt;h4&gt;Update&lt;/h4&gt;
&lt;p&gt;Updates are the same for nodes with or without children. Once the node's database and cache entries are updated, upstream cache invalidation is carried out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the &lt;code&gt;dependency&lt;/code&gt; array on the cache object, recursively delete &lt;code&gt;payload&lt;/code&gt; on all upstream cache entries, keeping in mind that...&lt;/li&gt;
&lt;li&gt;Updates on cache items must be pre-traverse to preserve The Guarantee. If, for example we update &lt;code&gt;B(A)&lt;/code&gt;, the following nodes must also be updated, in order: &lt;code&gt;E(B,C)&lt;/code&gt;, &lt;code&gt;F(E)&lt;/code&gt;, &lt;code&gt;G(E)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If retrieval of a key in any &lt;code&gt;dependency&lt;/code&gt; array fails, it refers to an evicted cache item. This is nothing to worry about: just prune the key from the &lt;code&gt;dependency&lt;/code&gt; array to prevent unnecessary future lookups.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Delete&lt;/h4&gt;
&lt;p&gt;Deletes have properties in common with creates &lt;em&gt;and&lt;/em&gt; updates. The sequence of events that follows a delete is complicated enough that an example is illuminating.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Delete node &lt;code&gt;B(A)&lt;/code&gt; in the database and in cache.&lt;/li&gt;
&lt;li&gt;Delete all upstream cache items &lt;code&gt;E(B,C)&lt;/code&gt;, &lt;code&gt;F(E)&lt;/code&gt;, &lt;code&gt;G(E)&lt;/code&gt;. Yes, delete: with the deletion of B(A), the assumptions undergirding all upstream nodes are invalid and it doesn't make sense to maintain cache entries for them.&lt;/li&gt;
&lt;li&gt;Notice that there are still stale references to &lt;code&gt;B(A)&lt;/code&gt; and its deleted, upstream dependents in unrelated downstream nodes such as &lt;code&gt;A&lt;/code&gt; (contains reference to &lt;code&gt;B(A)&lt;/code&gt;) and &lt;code&gt;D(B)&lt;/code&gt; (contains reference to &lt;code&gt;G(E)&lt;/code&gt;). This is ok! Dependencies only matter for delete- and update-related invalidations. Remember: if a node cannot be found for invalidation, it is pruned from the &lt;code&gt;dependency&lt;/code&gt; array during the invalidation process.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Search&lt;/h4&gt;
&lt;p&gt;When a request to retrieve information from the database is received, first check the cache store for it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If a cache entry &lt;strong&gt;is&lt;/strong&gt; available: Return it after checking it for validity. In this context, we define a valid parent node as one whose downstream children contain a reference to the parent in their &lt;code&gt;dependency&lt;/code&gt; arrays.&lt;ul&gt;
&lt;li&gt;If the child node does not exist, it has been evicted. &lt;code&gt;Create&lt;/code&gt; the child, adding the parent to its dependency list, and then execute an &lt;code&gt;Update&lt;/code&gt; on the parent to maintain time-based ordering of the graph.&lt;/li&gt;
&lt;li&gt;If the child node exists but does not contain a reference to the parent, execute an &lt;code&gt;Update&lt;/code&gt; on the child after adding the parent to the child's &lt;code&gt;dependency&lt;/code&gt; array.&lt;/li&gt;
&lt;li&gt;If the child node exists and contains a reference to the parent, you're good to go.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If a cache entry &lt;strong&gt;is not&lt;/strong&gt; available: Create a cache entry from the database (see &lt;code&gt;Create&lt;/code&gt; above) and return it to the caller.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this mind-numbing blog post, I have outlined a (probably) highly inefficient way of consistently caching the database representation of a graph of interdependent nodes using a caching store with memcache-like semantics.&lt;/p&gt;
&lt;p&gt;It occurs to me that my eviction handling scheme breaks down completely once you start running multiple independent memcache servers (each with separate LRU queues) and consistently hashing graph nodes across them. If this is an issue for you, you are probably in much better operational (i.e. you're richer than me) and technical (i.e. you're smarter than me) shape to solve the issue than I am... my problems are tiny enough that simply upgrading the amount of RAM on my caching server is usually enough.&lt;/p&gt;
&lt;p&gt;Anyhow: thanks for accompanying me on this terrifying journey. I'm going to go die some of these wizard hairs now, and possibly teach my four year old some better manners.&lt;/p&gt;</content><category term="technology"/><category term="caching"/><category term="object graph"/><category term="memcache"/></entry><entry><title>Containerize Everything: Food Edition</title><link href="https://www.anserinae.net/containerize-everything-food-edition.html" rel="alternate"/><published>2019-04-20T00:00:00-05:00</published><updated>2019-04-20T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2019-04-20:/containerize-everything-food-edition.html</id><summary type="html">&lt;p&gt;Silicon Valley sponsored &lt;em&gt;e-coli&lt;/em&gt; colon cleanses are closer than you think.&lt;/p&gt;</summary><content type="html">&lt;h3&gt;Programming note&lt;/h3&gt;
&lt;p&gt;Every now and then, I am going to start condensing things that I rant about on Twitter here on my blog.&lt;/p&gt;
&lt;p&gt;This is primarily so that I can index my thoughts in one place, but also because my blog is mine and not likely to disappear anytime soon. I'm not saying Twitter is going to disappear or anything, but I want to maintain the golden master of my words somewhere entirely under my control.&lt;/p&gt;
&lt;p&gt;Onward!&lt;/p&gt;
&lt;h3&gt;In which The Guardian completely misses the point&lt;/h3&gt;
&lt;p&gt;Today's post starts with this tweet about Deliveroo running delivery operations out of containerized "dark kitchens" in the London Docklands:&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-lang="en"&gt;&lt;p lang="en" dir="ltr"&gt;Not one instance of the word &amp;quot;commissary&amp;quot;. &lt;a href="https://t.co/LwGfAtDXXA"&gt;https://t.co/LwGfAtDXXA&lt;/a&gt;&lt;/p&gt;&amp;mdash; Kamil Choudhury (@kchoudhu) &lt;a href="https://twitter.com/kchoudhu/status/1119568825597427713?ref_src=twsrc%5Etfw"&gt;April 20, 2019&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;We should probably take a moment to reflect on how modern journalism can report things while completely missing the larger trends they are symptomatic of, but it's just easier to lay out what's actually going on.&lt;/p&gt;
&lt;h3&gt;Commissaries?&lt;/h3&gt;
&lt;p&gt;Commissary kitchens are industrial kitchens for rent. You buy slices of time in them to prep food for large catering orders, the day's product for a food truck, and increasingly commonly, orders coming off storefront apps like Seamless, Grubhub and Deliveroo.&lt;/p&gt;
&lt;p&gt;Food for food delivery is no longer lovingly crafted in restaurant kitchens. Since commissary kitchens allow for the wholesale separation of the restaurant experience from the icky, irritating business of actually preparing food, the food industry, ever on the lookout for ways to improve its razor thin margins, has been falling over itself to move delivery prep to commissary kitchens.&lt;/p&gt;
&lt;h3&gt;It's not going to happen&lt;/h3&gt;
&lt;p&gt;Anyone who has ever opened a box of wilted fries and lukewarm hamburgers from a delivery driver (i.e. everyone) is scoffing right now: there is no way delivery could replace the experience of going to eat out at a restaurant.&lt;/p&gt;
&lt;p&gt;Please reconsider.&lt;/p&gt;
&lt;p&gt;The aggressively mediocre dining experience at most fast casual restaurants is making the idea of &lt;em&gt;not&lt;/em&gt; getting in the car, &lt;em&gt;not&lt;/em&gt; tipping 20% and &lt;em&gt;not&lt;/em&gt; sitting in mediocre decor while eating reheated TV dinners seem increasingly attractive to a consumer base whose inability to reliably cook for itself is as dependable as its palate is jaded.&lt;/p&gt;
&lt;p&gt;Fast casual dining is an $800 billion industry, and a good chunk of that is at play in the food delivery wars. The US is not the only place this particular war is going to play out: restaurants are at risk in any market with  crappy existing restaurants, excessive tipping and weak labor laws.&lt;/p&gt;
&lt;h3&gt;What do labor laws have to do with any of this?&lt;/h3&gt;
&lt;p&gt;The entire commissary shift hinges on the availability of two pools of cheap labor: one that doesn't raise questions about cooking processes, and another that cheaply whisks food away to the customers.&lt;/p&gt;
&lt;p&gt;Silicon Valley's experience with the first generation of labor law arbitrage apps (Uber, Postmates etc) has made it dangerously competitive to the existing restaurant industry (itself no slouch in the labor abuse department) at provisioning both pools of workers. Competition between the two teams is going to lead to a race to the bottom that guarantees cut corners and questionable adherence to food safety regulations. Big Tech will win this race because it has internalized that it is easier to ask forgiveness than get permission.&lt;/p&gt;
&lt;p&gt;I'm not going to say much else about the food safety issue, other than to link to Sarah Taber, who is the internet's go-to person for thoughts on agriculture and food safety.&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-lang="en"&gt;&lt;p lang="en" dir="ltr"&gt;I don&amp;#39;t I&amp;#39;ve quite articulated why I&amp;#39;m so critical of the tech industry.&lt;br&gt;&lt;br&gt;Tech isn&amp;#39;t just software anymore. They&amp;#39;re coming for ag, food, &amp;amp; manufacturing- &amp;amp; they&amp;#39;re bringing a negligent attitudes towards risk &amp;amp; safety that they learned in the cushy world of apps.&lt;/p&gt;&amp;mdash; Dr Sarah Taber (@SarahTaber_bww) &lt;a href="https://twitter.com/SarahTaber_bww/status/1105256557154127872?ref_src=twsrc%5Etfw"&gt;March 11, 2019&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;She's right. Big Tech isn't going to change its ways just because it changed lanes.&lt;/p&gt;
&lt;h3&gt;Back to containers&lt;/h3&gt;
&lt;p&gt;Contrary to popular opinion, the comical mushrooming of food delivery startups isn't so much techbros trying to remove the last elements of friction in their over-privileged lives as it is Silicon Valley VCs with dollar signs in their eyes trying to parasitically muscle in on an entirely new field and replace the existing owners. The name of the game is containerization: take a business process, make it so that it doesn't matter where it's running, and then replicate the container as far and as fast as you can to drive down prices and (optionally) create shareholder value. Deliveroo couldn't have chosen a better implementation of the metaphor if it tried: I mean seriously, commissaries in containers in parking lots? Yikes.&lt;/p&gt;
&lt;p&gt;"Docker for Food" has such a nice ring to it; I just wish it wasn't ushering in an era of VC-sponsored &lt;em&gt;e-coli&lt;/em&gt; outbreaks.&lt;/p&gt;</content><category term="life"/><category term="meal delivery startups"/><category term="waste of money"/><category term="we're all going to die"/></entry><entry><title>Minimal devpi For Local Python Development</title><link href="https://www.anserinae.net/minimal-devpi-for-local-python-development.html" rel="alternate"/><published>2019-03-20T00:00:00-05:00</published><updated>2019-03-20T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2019-03-20:/minimal-devpi-for-local-python-development.html</id><summary type="html">&lt;p&gt;Someone finally came up with a non-irritating way to manage internal python projects&lt;/p&gt;</summary><content type="html">&lt;h3&gt;Kronk Have Many Package&lt;/h3&gt;
&lt;p&gt;I am currently working on two internal two Python packages: an end-user application called &lt;code&gt;openrelay&lt;/code&gt;, and its library dependency, &lt;code&gt;openarc&lt;/code&gt;. In addition to this internal dependency chain, both packages have numerous external dependencies, and I would like everything to be installed nicely when I type &lt;code&gt;pip install openrelay&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After struggling mightily with assorted hacks, I went searching for prior work. The common theme in all discussions was &lt;code&gt;devpi&lt;/code&gt;, so I gave it a shot.&lt;/p&gt;
&lt;p&gt;The experience was surprisingly intuitive, and I thought I'd write about it for future reference.&lt;/p&gt;
&lt;h3&gt;Setting up &lt;code&gt;devpi&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Pull the &lt;code&gt;devpi&lt;/code&gt; suite in using &lt;code&gt;pip&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip-3.6 install devpi-server devpi-client --user
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Initialize and start &lt;code&gt;devpi-server&lt;/code&gt;. This will start a python package index on &lt;code&gt;http://localhost:3141&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;devpi-server --init --start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Check in on the status of the server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ardentprayer% devpi-server --status
2019-03-20 07:03:53,744 INFO  NOCTX Loading node info from /home/kchoudhu/.devpi/server/.nodeinfo
2019-03-20 07:03:53,745 INFO  NOCTX wrote nodeinfo to: /home/kchoudhu/.devpi/server/.nodeinfo
server is running with pid 109
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Out of the box, &lt;code&gt;devpi&lt;/code&gt; creates a non-editable index &lt;code&gt;root/pypi&lt;/code&gt; which simply proxies requests to external PyPI index and caches the result for future use:&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;pip&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3141&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pypi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="w"&gt;  &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;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3141&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pypi&lt;/span&gt;&lt;span class="o"&gt;/+&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a08&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;b744b7e345cc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;24.4&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Neat.&lt;/p&gt;
&lt;h3&gt;Create an internal-only index&lt;/h3&gt;
&lt;p&gt;A caching package proxy for external PyPI servers is cool and all, but our goal is to transparently distribute internal projects that are &lt;em&gt;not&lt;/em&gt; available on external PyPI servers. To achieve this, we create a secondary index that inherits from &lt;code&gt;root/pypi&lt;/code&gt;, and upload our development work to &lt;em&gt;that&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Some admin work first. Create a user who will be able to upload to our secondary index:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;devpi user -c distuser password=123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Login using the new user:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;devpi login distuser --password=123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And create a new &lt;code&gt;dev&lt;/code&gt; index:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;devpi index -c dev bases=root/pypi volatile=True
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;volatile&lt;/code&gt; keyword tells &lt;code&gt;devpi&lt;/code&gt; that users will be able to upload to this index.&lt;/p&gt;
&lt;h3&gt;Upload projects to the internal-only index&lt;/h3&gt;
&lt;p&gt;Back to our initial problem: &lt;code&gt;openrelay&lt;/code&gt; and &lt;code&gt;openarc&lt;/code&gt;, and their external dependencies.&lt;/p&gt;
&lt;p&gt;Both packages have coherent &lt;code&gt;setup.py&lt;/code&gt; files, and I drop into their development directories and upload them to the &lt;code&gt;devpi-server&lt;/code&gt; (for brevity's sake, only the output for &lt;code&gt;openrelay&lt;/code&gt; is listed).&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;# cd ~/src/openrelay&lt;/span&gt;
&lt;span class="c1"&gt;# devpi upload&lt;/span&gt;
&lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;workdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devpi3&lt;/span&gt;
&lt;span class="n"&gt;copied&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;kchoudhu&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;openrelay&lt;/span&gt;&lt;span class="o"&gt;/.&lt;/span&gt;&lt;span class="n"&gt;git&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="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devpi3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;openrelay&lt;/span&gt;&lt;span class="o"&gt;/.&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;
&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cleaning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;kchoudhu&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;openrelay&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;
&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devpi3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;openrelay&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sdist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;formats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gztar&lt;/span&gt;
&lt;span class="n"&gt;built&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="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;kchoudhu&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;openrelay&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;openrelay&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;1.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SDIST&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TGZ&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.535&lt;/span&gt;&lt;span class="n"&gt;kb&lt;/span&gt;
&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;openrelay&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&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;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3141&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;distuser&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;file_upload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;openrelay&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;1.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&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;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3141&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;distuser&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With both packages now uploaded to &lt;code&gt;devpi&lt;/code&gt;, &lt;code&gt;openrelay&lt;/code&gt; can be installed by issuing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip-3.6 install --index http://localhost:3141/distuser/dev openrelay --user
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;External dependencies are fetched and cached from PyPI, and &lt;code&gt;openarc&lt;/code&gt; and &lt;code&gt;openrelay&lt;/code&gt; were pulled in from &lt;code&gt;devpi-server&lt;/code&gt; using one command.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://upload.wikimedia.org/wikipedia/commons/5/50/USS_Abraham_Lincoln_%28CVN-72%29_Mission_Accomplished.jpg"&gt;Mission accomplished&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Configure &lt;code&gt;pip&lt;/code&gt; to use &lt;code&gt;devpi&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Telling pip to use the new index with every invocation is tiresome, remove the need to do so by editing &lt;code&gt;~/.pip/pip.conf&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;[global]&lt;/span&gt;
&lt;span class="na"&gt;index-url&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;http://localhost:3141/distuser/dev&lt;/span&gt;

&lt;span class="k"&gt;[search]&lt;/span&gt;
&lt;span class="na"&gt;index&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;http://localhost:3141/distuser/dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Do not&lt;/em&gt; put trailing slashes on the URLs; &lt;code&gt;pip&lt;/code&gt; gets very testy indeed if you do.&lt;/p&gt;
&lt;h3&gt;Kronk Happy&lt;/h3&gt;
&lt;p&gt;I made developing multiple internal Python packages slightly little easier for myself, and heartily recommend that you follow in my footsteps. It is rare for a tool to improve productivity after, like, 6 commands, but here we are: &lt;code&gt;devpi&lt;/code&gt; rules.&lt;/p&gt;
&lt;p&gt;You should use it.&lt;/p&gt;</content><category term="technology"/><category term="python"/><category term="devpi"/></entry><entry><title>Scripting FreeBSD Updates</title><link href="https://www.anserinae.net/scripting-freebsd-updates.html" rel="alternate"/><published>2019-03-06T00:00:00-05:00</published><updated>2019-03-06T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2019-03-06:/scripting-freebsd-updates.html</id><summary type="html">&lt;p&gt;In which I come back from holiday and rant about freebsd-update&lt;/p&gt;</summary><content type="html">&lt;p&gt;A small set of nits that I stumbled upon while attempting to script &lt;code&gt;freebsd-update&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Paging Dr. Obvious&lt;/h3&gt;
&lt;p&gt;The worst part, bar none, is that it &lt;em&gt;demands&lt;/em&gt; interaction from the user. If you're doing manual upgrades, this is fine: you probably want to know what changed, when, and how. In a scripted environment where you are running &lt;code&gt;freebsd-update&lt;/code&gt; on several thousand hosts and containers, this is...inefficient, especially when you are confident that the messages are informational only, and don't need any kind of user intervention [1].&lt;/p&gt;
&lt;p&gt;It is not immediately obvious how to get past all the prompts. My usual approach of piping the output of &lt;code&gt;yes&lt;/code&gt; to the program didn't work because, it turns out, &lt;code&gt;freebsd-update&lt;/code&gt; displays its prompts using the system &lt;em&gt;&lt;code&gt;$PAGER&lt;/code&gt;&lt;/em&gt;. Obviously.&lt;/p&gt;
&lt;p&gt;From 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="c1"&gt;# Set a pager if the user doesn&amp;#39;t&lt;/span&gt;
&lt;span class="k"&gt;if&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;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$PAGER&lt;/span&gt;&lt;span class="s2"&gt;&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;PAGER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/more
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The fix here is to set, somewhere in the calling script, &lt;code&gt;$PAGER&lt;/code&gt; to something that allows output, but doesn't wait for user input. I ended up using &lt;code&gt;cat&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="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PAGER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/cat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hooray: output, but no more prompts.&lt;/p&gt;
&lt;h3&gt;Go Away, It Worked For Me&lt;/h3&gt;
&lt;p&gt;The second sticking point: scripts seemed to work fine on my development machine, but then fail silently when deployed to the remote machine. Kind of a bummer if your stated goal is bulletproof upgrades.&lt;/p&gt;
&lt;p&gt;I will save how debugging this problem revealed a desperate need for better logging in my systems for another day, but the root of the problem is that &lt;code&gt;freebsd-update&lt;/code&gt; refuses to fetch updates if &lt;em&gt;not&lt;/em&gt; running from a TTY:&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;if&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="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$NOTTYOK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-eq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;`basename &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;` fetch should not &amp;quot;&lt;/span&gt;
&lt;span class="w"&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;be run non-interactively.&amp;quot;&lt;/span&gt;
&lt;span class="w"&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;Run `basename &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;` cron instead.&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This probably merits an explanation in &lt;code&gt;fetch&lt;/code&gt; section of the &lt;code&gt;freebsd-update&lt;/code&gt; man page, which currently reads:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; fetch     Based on the currently installed world and the configuration
           options set, fetch all available binary updates.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The solution (if not the explanation) to our problem turns out to be in the man page: call &lt;code&gt;freebsd-update&lt;/code&gt; with the &lt;code&gt;--not-running-from-cron&lt;/code&gt; option:&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;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cron&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;freebsd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fetch&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;proceed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;there&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;controlling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&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="n"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;automated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;orchestration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Please&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;freebsd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;fetch&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;crontab&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;similar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;see&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;freebsd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cron&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using this option allowed fetch to proceed even when run remotely. Yay.&lt;/p&gt;
&lt;h3&gt;FreeBSD Updates Aren't Ready For Cluster Computing&lt;/h3&gt;
&lt;p&gt;Did you blink a couple of times at the &lt;code&gt;--not-running-from-cron&lt;/code&gt; option?&lt;/p&gt;
&lt;p&gt;I understand that &lt;code&gt;freebsd-update&lt;/code&gt; wants to save the user from footgun scenarios -- but the default behavior is not consistent with how I suspect most people expect upgrades and updates to work on a modern operating system. We are well into the era of treating servers (where FreeBSD is supposed to excel) like cattle; having core functionality assume interactivity by default is weirdly anachronistic and actively hostile to cluster computing.&lt;/p&gt;
&lt;p&gt;I am not sure what way forward is, but whatever path we choose surely should not involve shell hacks and inelegant command line options.&lt;/p&gt;
&lt;p&gt;[1] Your mileage will &lt;em&gt;definitely&lt;/em&gt; vary. In my case, systems are engineered to leave base intact, thus preventing merge conflicts and making automated updates a possibility.&lt;/p&gt;</content><category term="technology"/><category term="freebsd"/><category term="freebsd-update"/></entry><entry><title>Paying Less For Hotels</title><link href="https://www.anserinae.net/paying-less-for-hotels.html" rel="alternate"/><published>2019-02-26T00:00:00-05:00</published><updated>2019-02-26T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2019-02-26:/paying-less-for-hotels.html</id><summary type="html">&lt;p&gt;A cheapskate's guide to paying less for hotel stays.&lt;/p&gt;</summary><content type="html">&lt;p&gt;While planning a recent trip, I didn't stick to &lt;strong&gt;The Plan&lt;/strong&gt; and ended up paying full retail for a hotel stay.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;hate&lt;/em&gt; paying full retail.&lt;/p&gt;
&lt;p&gt;This blog entry is a note to my future self: use this algorithm to get a reasonable percentage off a hotel stay, or to establish the baseline fair value of a hotel stay before attempting fancier savings strategies.&lt;/p&gt;
&lt;h3&gt;Once A Year&lt;/h3&gt;
&lt;p&gt;Load up on Hotels.com gift cards. Don't do anything fancy: once a year, go to &lt;a href="raise.com"&gt;Raise&lt;/a&gt; and buy as many gift certificates as you think you'll need in the coming year (you have a &lt;a href="https://milenomics.com/2018/01/visualizing-family-demand-schedule"&gt;travel demand schedule&lt;/a&gt; right?). You should consistently be able to get them for 93-94 cents on the dollar.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Savings&lt;/em&gt;: &lt;strong&gt;6.5%&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Make sure you go to Raise through a cashback portal. Ebates has been my usual choice of late, but others can also work. Refer to &lt;a href="cashbackmonitor.com"&gt;Cashback Monitor&lt;/a&gt; for more possibilities; prefer payout reliability over an extra 0.5% in savings.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Savings&lt;/em&gt;: &lt;strong&gt;1%&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use the right credit card for the gift cards. Your first port of call should always be the &lt;a href="https://www.bankofamerica.com/credit-cards/products/premium-rewards-credit-card/"&gt;Bank of America Premium Rewards Cashback&lt;/a&gt; card; the way you have set it up, it gives you a flat 2.625% back on every dollar spent. It is possible that using another card will give you more back, but make sure the savings rate you are calculating includes the cash-out value of the points earned &lt;em&gt;only&lt;/em&gt;. Avoid at all costs specious reasoning about the value of what you are saving.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Good Reasoning:&lt;/strong&gt; These 4000 Ultimate Rewards points can be redeemed for a $40 statement credit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bad Reasoning:&lt;/strong&gt; These 10K Membership Rewards points can be redeemed at 10 cents per mile flying first class on Singapore Airlines.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The baseline comparison point for this family's travel is &lt;em&gt;always&lt;/em&gt; economy class.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Savings&lt;/em&gt;: &lt;strong&gt;2.625%&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Total savings, before going anywhere near a booking site&lt;/em&gt;: &lt;strong&gt;10%&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Administrative note: the gift cards are likely to come in small denominations of $50-200. Do yourself a favor and combine them all into one card &lt;a href="https://www.hotels.com/page/gcbalance/"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Booking Individual Hotel Stays&lt;/h3&gt;
&lt;p&gt;The lead up to this section will have tipped you off to the fact that all stays are to be booked through &lt;a href="hotels.com"&gt;Hotels.com&lt;/a&gt;. They have a meta-rewards program that gives you a credit towards a free night whenever you book 10 nights through them, the size of which is equal to the average value of the last 10 paid stays. This avoids the captive vendor syndrome that comes with using chain-specific loyalty programs, and also locks in your savings rate at around &lt;strong&gt;10%&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To start with, go through a cashback portal.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Savings&lt;/em&gt;: &lt;strong&gt;5%&lt;/strong&gt; if booking without a free night credit, &lt;strong&gt;2%&lt;/strong&gt; if booking with one.&lt;/p&gt;
&lt;p&gt;Pay attention to the nightly room rates. If the highest room rate for the stay is within ±5% of the free-night credit, use the credit: ultimately, you want to use as much of the credit as possible without sacrificing earning value on future credits. If you need to, roll extras you know you're going to need (breakfast, late checkout) in order to boost the room rate.&lt;/p&gt;
&lt;p&gt;If you're feeling frisky, split tender between a credit card that offers trip protection (Chase Sapphire Reserve by choice) and the gift card when checking out in order to trigger trip protection. Credit card trip insurance isn't worth much to be honest though, so if you do anticipate an issue, go ahead and get actual trip insurance.&lt;/p&gt;
&lt;h3&gt;Upshot&lt;/h3&gt;
&lt;p&gt;Using this process it is possible to save, in aggregate, across 3-4 trips over the course of a year, about &lt;strong&gt;22&lt;/strong&gt;-&lt;strong&gt;25%&lt;/strong&gt; off baseline hotel prices. That is &lt;em&gt;not bad&lt;/em&gt;.&lt;/p&gt;</content><category term="travel"/><category term="hotels"/><category term="discounts"/><category term="cashback"/></entry><entry><title>Memcache For My Brain</title><link href="https://www.anserinae.net/memcache-for-my-brain.html" rel="alternate"/><published>2018-12-09T00:00:00-05:00</published><updated>2018-12-09T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-12-09:/memcache-for-my-brain.html</id><summary type="html">&lt;p&gt;Don't get old, kids&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am going to keep updating this list because it is annoying to have to repeatedly spend hours googling things that are tantalizingly familiar but still forgotten by my increasingly decrepit brain.&lt;/p&gt;
&lt;h3&gt;That Movie where...&lt;/h3&gt;
&lt;p&gt;... a bunch of sailors on shore leave defraud a casino with their ship's computer&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/The_Honeymoon_Machine"&gt;The Honeymoon Machine&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;... poor Chinese migrants get caught up in the noir-esque travails of a Vietnamese bar singer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/So_Close_to_Paradise"&gt;So Close To Paradise&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;That song...&lt;/h3&gt;
&lt;p&gt;... you listened to 800 times at 2AM while you were working graveyard shift for the 82nd time in 2009, but lost when your iPod went tits up:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=emguA0RIH4k"&gt;Goo Goo Dolls - Black Balloon&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=j82FBbgpUy4"&gt;Dashboad Confessional - Stolen&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;... that plays in &lt;a href=""&gt;Dogtown And The Z-Boys&lt;/a&gt; when the narrator is talking about Venice falling apart:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=3uWHBvLoeAU"&gt;Sneaker Pimps - 6 Underground (Fila Brazilia Remix)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;... that plays at the end of Dogtown and the Z Boys when they're showing what happened to all the Z Boys and is supposedly a remix of "Exchange" by Massive Attack:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=S96yLSJ1hgc"&gt;Massive Attack - Exchange (Dogtown and Z Boys Version)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Yes, I wrote to Stacy Peralta to find out what it was and where I could get a copy.&lt;/p&gt;
&lt;h3&gt;That quote...&lt;/h3&gt;
&lt;p&gt;... by Kim Stanley Robinson in &lt;a href="https://en.wikipedia.org/wiki/Sixty_Days_and_Counting"&gt;Sixty Days And Counting&lt;/a&gt; on what to tell oil companies when they resist changing their ways.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"None of the alternatives are competitive [to oil]."&lt;/p&gt;
&lt;p&gt;Charlie’s pencil tip snapped. “Competitive for what?” he demanded. He had not spoken until that point, and now the edge in his voice stopped the discussion.&lt;/p&gt;
&lt;p&gt;Everyone was staring at him. He stared back at the World Bank guys.&lt;/p&gt;
&lt;p&gt;"Damage from carbon dioxide emission costs about $35 a ton, but in your model no one pays it. The carbon that British Petroleum burns per year, by sale and operation, runs up a damage bill of fifty billion dollars. BP reported a profit of twenty billion,so actually it’s thirty billion in the red, every year. Shell reported a profit of twenty-three billion, but if you added the damage cost it would be eight billion in the red. These companies should be bankrupt. You support their exteriorizing of costs, so your accounting is bullshit. You’re helping to bring on the biggest catastrophe in human history. If the oil companies burn the five hundred gigatons of carbon that you are describing as inevitable because of your financial shell games, then two-thirds of the species on the planet will be endangered, including humans. But you keep talking about fiscal discipline and competitive edges in profit differentials. It's the stupidest head-in-the-sand response possible."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;... and why everyone has a problem with the Baby Boomers now:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Lots of [people] are locked into an Oedipal hatred of the baby-boomer generation. They hate the boomers for what we were given when we were young, the world gone for just the briefest moment out of its mind into a realm of sex, drugs and rock and roll, of revolution and war and history right there in our hands, a time of excess and joy, a feeling that things could still change -- a freedom that was so extreme no one who was there can even remember it properly, and no one who wasn't there can imagine it, because it was before AIDS and crack and meth and terrorism had returned everything to something like the weird and violent Victorian repression/transgression state of fear that we've all been living in these past years. So I see a fair bit of resentment. You old Vietnam vet, I see their eeys saying, you old hippie, you got lucky and were born in the right little window and got to grab all the surplus happiness that history ever produced, and you blew it, and you stood around and did nothing while the right Reaganed back into power and shut down all possibility of change for an entire generation. You blew it in a ten year party and staggered off stoned and complicit. You neither learned to do machine politics nor dismantled the machine. Not one of you imagined what had to be done. And so the backlash came down, the reactionary power structure stronger than ever, and now we're the ones who have to pay the price for that.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It was probably a follow on from his thoughts on change and those who make it possible in &lt;a href="https://en.wikipedia.org/wiki/The_Years_of_Rice_and_Salt"&gt;Years Of Rice And Salt&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In every group a Ka and a Ba, Ka always complaining with the kaw of a the crow, the cough of the cat, the cry of coyote, kaw, kaw, that fundamental protest; and then Ba always Ba, the banal baa of the water buffalo, the sound of the plow bound to the earth, the bleat of hope and fear, the bone inside. The one who missed the missing Ka. The world was changed by the Kungs, but the Baos had to try to hold it together.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Heady stuff.&lt;/p&gt;</content><category term="life"/><category term="memory aids"/></entry><entry><title>Python Profiling for Babies</title><link href="https://www.anserinae.net/python-profiling-for-babies.html" rel="alternate"/><published>2018-10-05T00:00:00-05:00</published><updated>2018-10-05T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-10-05:/python-profiling-for-babies.html</id><summary type="html">&lt;p&gt;Performance is not an accident&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you have ever written a big stack of Python code and wondered why it is stupidly slow, you need a profiler. My go-to choice is &lt;a href="https://github.com/joerick/pyinstrument"&gt;&lt;code&gt;pyinstrument&lt;/code&gt;&lt;/a&gt;, which samples your program while it is running and prints out pretty trees of where your program is spending time.&lt;/p&gt;
&lt;p&gt;Installing it is pretty easy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip-3.6&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pyinstrument&lt;span class="w"&gt; &lt;/span&gt;--user
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And then you pop into your python code and have it sample the code you want to profile:&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;big_terrible_piece_of_code&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pyinstrument&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Profiler&lt;/span&gt;
    &lt;span class="n"&gt;profiler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Profiler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;profiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Do a whole of stuff&lt;/span&gt;

    &lt;span class="n"&gt;profiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unicode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That'll output a big old graph 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="m"&gt;2&lt;/span&gt;.449&lt;span class="w"&gt; &lt;/span&gt;None&lt;span class="w"&gt;  &lt;/span&gt;None
└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;_handle_and_close_when_done&lt;span class="w"&gt;  &lt;/span&gt;gevent/baseserver.py:24
&lt;span class="w"&gt;   &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;handle&lt;span class="w"&gt;  &lt;/span&gt;gevent/pywsgi.py:1498
&lt;span class="w"&gt;      &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;handle&lt;span class="w"&gt;  &lt;/span&gt;gevent/pywsgi.py:442
&lt;span class="w"&gt;         &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;handle_one_request&lt;span class="w"&gt;  &lt;/span&gt;gevent/pywsgi.py:592
&lt;span class="w"&gt;            &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;handle_one_response&lt;span class="w"&gt;  &lt;/span&gt;gevent/pywsgi.py:945
&lt;span class="w"&gt;               &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;run_application&lt;span class="w"&gt;  &lt;/span&gt;gevent/pywsgi.py:907
&lt;span class="w"&gt;                  &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;__call__&lt;span class="w"&gt;  &lt;/span&gt;flask/app.py:1995
&lt;span class="w"&gt;                     &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;wsgi_app&lt;span class="w"&gt;  &lt;/span&gt;flask/app.py:1952
&lt;span class="w"&gt;                        &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;full_dispatch_request&lt;span class="w"&gt;  &lt;/span&gt;flask/app.py:1600
&lt;span class="w"&gt;                           &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;dispatch_request&lt;span class="w"&gt;  &lt;/span&gt;flask/app.py:1578
&lt;span class="w"&gt;                              &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;wrapfn&lt;span class="w"&gt;  &lt;/span&gt;wwwd.py:214
&lt;span class="w"&gt;                                 &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;view_home_thread&lt;span class="w"&gt;  &lt;/span&gt;wwwd.py:952
&lt;span class="w"&gt;                                    &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.452&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;listcomp&amp;gt;&lt;span class="w"&gt;  &lt;/span&gt;wwwd.py:995
&lt;span class="w"&gt;                                       &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.426&lt;span class="w"&gt; &lt;/span&gt;__next__&lt;span class="w"&gt;  &lt;/span&gt;openarc/graph.py:404
&lt;span class="w"&gt;                                          &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.397&lt;span class="w"&gt; &lt;/span&gt;next&lt;span class="w"&gt;  &lt;/span&gt;openarc/graph.py:169
&lt;span class="w"&gt;                                             &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.209&lt;span class="w"&gt; &lt;/span&gt;_set_attrs_from_cframe&lt;span class="w"&gt;  &lt;/span&gt;openarc/_rdf.py:344
&lt;span class="w"&gt;                                                &lt;/span&gt;└─&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.818&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt;  &lt;/span&gt;openarc/_rdf.py:144
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The most expensive code paths are near the top of the tree -- optimize those first if you can.&lt;/p&gt;
&lt;p&gt;There are other tools in my optimization toolkit, but this is the one I find myself turning to most often. It is insanely easy to use, and in my book that trumps sophisticated tools any day of the week: sometimes a general, birdseye view is all you need to notice that your program is doing something stupid.&lt;/p&gt;
&lt;p&gt;Previously: performance is &lt;a href="/stupid-systems-monitoring-tricks.html"&gt;not&lt;/a&gt; an &lt;a href="diagramming-python-object-graphs.html"&gt;accident&lt;/a&gt;&lt;/p&gt;</content><category term="technology"/><category term="python"/><category term="profiling"/></entry><entry><title>Recursive Blogging</title><link href="https://www.anserinae.net/recursive-blogging.html" rel="alternate"/><published>2018-10-04T00:00:00-05:00</published><updated>2018-10-04T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-10-04:/recursive-blogging.html</id><summary type="html">&lt;p&gt;Yeah, I'm going to blog about blogging&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is my submission for that most irritating genre of technology writing, the blog post about setting up a blog.&lt;/p&gt;
&lt;p&gt;It's more a note to future me on how specifically to get this blog running on a new machine than it is a general guide on how to run a Pelican-based website... but if it helps anyone else, well, you do you.&lt;/p&gt;
&lt;p&gt;Preliminaries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;gmake&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;python36&lt;span class="w"&gt; &lt;/span&gt;py36-pip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Clone the blog respository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://www.github.com/kchoudhu/src/blog
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Setup has gotten easier recently, so hit up the &lt;code&gt;setup&lt;/code&gt; target. This will install the &lt;code&gt;flex&lt;/code&gt; theme and ensure that all plugins are available to &lt;code&gt;pelican&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gmake&lt;span class="w"&gt; &lt;/span&gt;setup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Start the devserver, which will serve on port 8000:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gmake&lt;span class="w"&gt; &lt;/span&gt;html
gmake&lt;span class="w"&gt; &lt;/span&gt;devserver
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Work on a new article:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gmake&lt;span class="w"&gt; &lt;/span&gt;newblog&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ASUM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;a-dash-separated-slug
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will create a new branch which will be used to create the new article:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;branch
*&lt;span class="w"&gt; &lt;/span&gt;a-dash-separated-slug
&lt;span class="w"&gt;  &lt;/span&gt;master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If at any time you want to create a new article while working on the first, issue a new &lt;code&gt;newblog&lt;/code&gt; command; this will commit any outstanding changes to the current article branch, force a switch to &lt;code&gt;master&lt;/code&gt; and create a new blog branch.&lt;/p&gt;
&lt;p&gt;Use rsync to publish everything once you've merged:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gmake&lt;span class="w"&gt; &lt;/span&gt;rsync_upload&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ASUM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;a-dash-separated-slug
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you want to simply push what's committed on &lt;code&gt;master&lt;/code&gt;, leave out the &lt;code&gt;ASUM&lt;/code&gt; parameter.&lt;/p&gt;
&lt;p&gt;Congratulations, you have written stuff on the internet that no one is going to read. Shouldn't you be doing real work instead?&lt;/p&gt;</content><category term="technology"/><category term="pelican"/><category term="blogging"/></entry><entry><title>Stupid Systems Monitoring Tricks</title><link href="https://www.anserinae.net/stupid-systems-monitoring-tricks.html" rel="alternate"/><published>2018-10-03T00:00:00-05:00</published><updated>2018-10-03T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-10-03:/stupid-systems-monitoring-tricks.html</id><summary type="html">&lt;p&gt;Stupid tricks to monitor resource usage&lt;/p&gt;</summary><content type="html">&lt;p&gt;Yes, we are all supposed to have moved into the fourth age of Web 7.0 systems monitoring (I think the cool kids are calling it observability today?), but sometimes you need to know the state of a machine without a collection endpoint on it. Or you are offended by the thought of loading 68MB of HTML and Javascript when a simple shell command will do.&lt;/p&gt;
&lt;p&gt;Print all sockets matching a certain PID:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~&amp;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;true&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sockstat&lt;span class="w"&gt; &lt;/span&gt;-4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;egrep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sleep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Print number of file descriptors in use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~&amp;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;true&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sysctl&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;egrep&lt;span class="w"&gt; &lt;/span&gt;openfiles&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sleep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And as a personal reminder, don't forget to check &lt;code&gt;ulimit&lt;/code&gt; to see what your session allows when you invariably run out of memory/filedescriptors/whatever:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ulimit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a
-t:&lt;span class="w"&gt; &lt;/span&gt;cpu&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;seconds&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;unlimited
-f:&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;blocks&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;unlimited
-d:&lt;span class="w"&gt; &lt;/span&gt;data&lt;span class="w"&gt; &lt;/span&gt;seg&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;kbytes&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;33554432&lt;/span&gt;
-s:&lt;span class="w"&gt; &lt;/span&gt;stack&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;kbytes&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="m"&gt;524288&lt;/span&gt;
-c:&lt;span class="w"&gt; &lt;/span&gt;core&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;blocks&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;unlimited
-m:&lt;span class="w"&gt; &lt;/span&gt;resident&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;kbytes&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;unlimited
-l:&lt;span class="w"&gt; &lt;/span&gt;locked-in-memory&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;kbytes&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;
-u:&lt;span class="w"&gt; &lt;/span&gt;processes&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="m"&gt;8499&lt;/span&gt;
-n:&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;descriptors&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="m"&gt;116946&lt;/span&gt;
-b:&lt;span class="w"&gt; &lt;/span&gt;socket&lt;span class="w"&gt; &lt;/span&gt;buffer&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;bytes&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;unlimited
-v:&lt;span class="w"&gt; &lt;/span&gt;virtual&lt;span class="w"&gt; &lt;/span&gt;memory&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;kbytes&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;unlimited
-p:&lt;span class="w"&gt; &lt;/span&gt;pseudo-terminals&lt;span class="w"&gt;                &lt;/span&gt;unlimited
-w:&lt;span class="w"&gt; &lt;/span&gt;swap&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;kbytes&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;unlimited
-k:&lt;span class="w"&gt; &lt;/span&gt;kqueues&lt;span class="w"&gt;                         &lt;/span&gt;unlimited
-N&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;:&lt;span class="w"&gt;                              &lt;/span&gt;unlimited
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="technology"/><category term="pids"/><category term="fds"/><category term="sockets"/></entry><entry><title>A New Cycle</title><link href="https://www.anserinae.net/a-new-cycle.html" rel="alternate"/><published>2018-08-11T00:00:00-05:00</published><updated>2018-08-11T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-08-11:/a-new-cycle.html</id><summary type="html">&lt;p&gt;And we're off again&lt;/p&gt;</summary><content type="html">&lt;p&gt;As the days melt into months and the months into years, we are on the move again. Two years, two children, so many miles; there are days when I wonder if we'll ever 
stop. And then we are on the road again: lock the door, turn left on Springfield, merge on to the 215, south to Los Angeles, get a on a flight to Doha and far, far 
beyond. &lt;/p&gt;
&lt;p&gt;I see the way open in front of me and I cannot help walking down it. &lt;/p&gt;</content><category term="life"/></entry><entry><title>Diagramming Python Object Graphs</title><link href="https://www.anserinae.net/diagramming-python-object-graphs.html" rel="alternate"/><published>2018-06-06T00:00:00-05:00</published><updated>2018-06-06T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-06-06:/diagramming-python-object-graphs.html</id><summary type="html">&lt;p&gt;Finding memory leaks in python, episode 2847&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you ever want to look at the dependency graph of a python object:&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;objgraph&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objgraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;by_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;OAG_AutoNode1a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;objgraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show_backrefs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;depgraph.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will dump the dependency graph for the first OAG_AutoNode1a object into a file &lt;code&gt;depgraph.png&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Useful for finding memory leaks. Requires the &lt;code&gt;objgraph&lt;/code&gt; python module and the &lt;code&gt;graphviz&lt;/code&gt; (system) package:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip-3.6&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;objgraph&lt;span class="w"&gt; &lt;/span&gt;--user
pkg&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;graphviz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="technology"/><category term="python"/><category term="objgraph"/><category term="notes for the future"/></entry><entry><title>Evolution on KDE</title><link href="https://www.anserinae.net/evolution-on-kde.html" rel="alternate"/><published>2018-06-01T00:00:00-05:00</published><updated>2018-06-01T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-06-01:/evolution-on-kde.html</id><summary type="html">&lt;p&gt;Get outta here -- Microsoft Exchange on FreeBSD?&lt;/p&gt;</summary><content type="html">&lt;h3&gt;Data loss shuffle&lt;/h3&gt;
&lt;p&gt;After a few too many instances of NTFS eating my work-in-progress on Windows, I recently decided to move my day-to-day workflow over to a ZFS-on-root FreeBSD system.&lt;/p&gt;
&lt;p&gt;I installed KDE 5 (a story for another day), and started sifting through my options for mail management. A few short years ago, this would have been a showstopper as my company uses Microsoft Exchange. Now that Microsoft has inexplicably started shitting interoperability rainbows, I can simply install &lt;a href="https://www.freshports.org/mail/evolution"&gt;mail/evolution&lt;/a&gt; and be off to the races:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;evolution&lt;span class="w"&gt; &lt;/span&gt;evolution-ews
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Evolution started up beautifully, although it did look a little out of place... it is a GNOME application running on KDE after all.&lt;/p&gt;
&lt;h3&gt;Irritant&lt;/h3&gt;
&lt;p&gt;While I did not have any problems setting up my account, whenever I refreshed my inbox, despite selecting the "store my credentials in the GNOME keyring" option, Evolution wanted me to re-enter my password&lt;/p&gt;
&lt;p&gt;Oh wait, &lt;em&gt;I'm on KDE&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;The Fix&lt;/h3&gt;
&lt;p&gt;Give Evolution exactly what it wants:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;gnome-keyring
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Set up a &lt;a href="https://www.freebsd.org/doc/en_US.ISO8859-1/articles/rc-scripting/rcng-daemon.html"&gt;little &lt;code&gt;rc&lt;/code&gt; script&lt;/a&gt; for the newly installed package:&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;# nano /usr/local/etc/rc.d/gnome_keyring&lt;/span&gt;
&lt;span class="c1"&gt;#!/bin/sh&lt;/span&gt;

.&lt;span class="w"&gt; &lt;/span&gt;/etc/rc.subr

&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gnome_keyring
&lt;span class="nv"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gnome-keyring-daemon
&lt;span class="nv"&gt;rcvar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gnome_keyring_enable

&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/local/bin/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;bin&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

load_rc_config&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;
run_rc_command&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Enable it in &lt;code&gt;/etc/rc.conf&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="nv"&gt;gnome_keyring_enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;YES&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And start the keyring:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;service&lt;span class="w"&gt; &lt;/span&gt;gnome_keyring&lt;span class="w"&gt; &lt;/span&gt;start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I haven't seen a password prompt in about two days now, so I'm assuming the case is closed.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=qfpg-YoQj60"&gt;Bring in the dancing lobsters&lt;/a&gt;.&lt;/p&gt;</content><category term="technology"/><category term="kde5"/><category term="freebsd"/><category term="evolution"/><category term="exchange"/></entry><entry><title>Dynamically Altering Classes in Python</title><link href="https://www.anserinae.net/dynamically-altering-classes-in-python.html" rel="alternate"/><published>2018-05-26T00:00:00-05:00</published><updated>2018-05-26T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-05-26:/dynamically-altering-classes-in-python.html</id><summary type="html">&lt;p&gt;Classic black magic, explained&lt;/p&gt;</summary><content type="html">&lt;h3&gt;The Very Basics&lt;/h3&gt;
&lt;p&gt;Everything in Python is an object. Don't believe me?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;In&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Out&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&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;Suffice to say that more complicated things are also classes. Derive from &lt;code&gt;object&lt;/code&gt; to declare your own classes, and then create an instance:&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;Cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Add a property&lt;/span&gt;
    &lt;span class="nd"&gt;@property&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;legs&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
    &lt;span class="c1"&gt;# Add a method&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;meow&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;meow&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cat_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;cat_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# meow&lt;/span&gt;
&lt;span class="n"&gt;cat_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legs&lt;/span&gt;
&lt;span class="c1"&gt;# 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Running &lt;code&gt;dir()&lt;/code&gt; on will show you what methods and properties are on the object:&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;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;legs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;meow&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Adding methods dynamically&lt;/h3&gt;
&lt;p&gt;One of the best things about Python is its delightfully adult attitude towards runtime mutability: if you have ever found yourself asking "can I add a method to an object after initializing it?", you should know not only that the answer is "yes", but that Python also makes it easy (and relatively safe) to do so by using the &lt;code&gt;MethodType&lt;/code&gt; method from the &lt;code&gt;types&lt;/code&gt; module:&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;types&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;pounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pounce!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pounce&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cat_1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;legs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;meow&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pounce&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;It's important to note that the new method only applies to the current instance of the class:&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;# cat_2 is not affected by the changes made to cat_1&lt;/span&gt;
&lt;span class="n"&gt;cat_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;legs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;meow&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;But what if you want to add a method dynamically to all instances of the class?&lt;/p&gt;
&lt;h3&gt;Enter metaprogramming&lt;/h3&gt;
&lt;p&gt;Remember how we said that everything in Python is an object? Take a look at this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;In&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&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="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&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;If you are like me, it'll will take a while to internalize the message being conveyed here, which is that the &lt;code&gt;Cat&lt;/code&gt; class is an instance of the &lt;code&gt;type&lt;/code&gt; &lt;em&gt;metaclass&lt;/em&gt;. And as we saw in the last section, &lt;em&gt;anything&lt;/em&gt; that is an instance can be manipulated at runtime:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pounce&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Checking in on &lt;code&gt;cat_1&lt;/code&gt;, we see an unsurprising result -- we did after all previously bind the &lt;code&gt;pounce&lt;/code&gt; method to it:&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;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;legs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;meow&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pounce&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;&lt;code&gt;cat_2&lt;/code&gt; on the other hand never had a &lt;code&gt;pounce&lt;/code&gt; method attached to it, but now shows one inherited from the class definition:&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;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;legs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;meow&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pounce&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;cat_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lovely.&lt;/p&gt;
&lt;h3&gt;Footgun Alert&lt;/h3&gt;
&lt;p&gt;A few warnings for those of you who, like me, are occasionally taken unawares by things that others perhaps wouldn't be surprised by.&lt;/p&gt;
&lt;p&gt;Firstly, class implementations of methods can be overridden on a per-instance basis:&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;alternate_pounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;alternate pounce!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pounce&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alternate_pounce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;cat_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;alternate_pounce&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;cat_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Secondly, any changes made at the class level do not override local changes at the instance level:&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;alternate_pounce_2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;alternate pounce 2!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pounce&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alternate_pounce_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# We overrode cat_2&amp;#39;s &amp;#39;pounce&amp;#39; method with alternate_pounce and&lt;/span&gt;
&lt;span class="c1"&gt;# changing the class definition of &amp;#39;pounce&amp;#39; isn&amp;#39;t visible from cat_2&lt;/span&gt;
&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;cat_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;alternate&lt;/span&gt; &lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;

&lt;span class="c1"&gt;# A newly instantiated object shows the new &amp;#39;pounce&amp;#39; output, however&lt;/span&gt;
&lt;span class="n"&gt;cat_3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;ln&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;cat_3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pounce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;alternate&lt;/span&gt; &lt;span class="n"&gt;pounce&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, changes to the class last as long as the process is alive. This particular footgun can cause unintuitive behavior when using (and mutating) the class in unit tests that are not run in separate processes. You may think that the class is going to start out fresh, but really the definition of the class at the beginning of a test is what it was at the end of the last test that was run. Spare yourself hours of debugging test order and &lt;code&gt;unittest&lt;/code&gt; internals, and reset the class in your test class' &lt;code&gt;setUp&lt;/code&gt; and &lt;code&gt;tearDown&lt;/code&gt; methods.&lt;/p&gt;
&lt;h3&gt;Why does this exist?&lt;/h3&gt;
&lt;p&gt;Every Python metaprogramming article seems to include this quote from Tim Peters:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Under most circumstances, altering code while it is running increases its complexity and makes reasoning about it more difficult. There are, however, certain circumstances where it is warranted: while developing the database persistence portion of &lt;a href="https://github.com/kchoudhu/openarc"&gt;OpenARC&lt;/a&gt;, for example, it was necessary to &lt;a href="https://github.com/kchoudhu/openarc/blob/202a55be6466f361fde9fe7255ba08075d5cde84/openarc/_db.py#L93"&gt;map dynamically changing relationships between database objects&lt;/a&gt;. Metaprogramming fit the bill in that very limited use case, and I happily used it.&lt;/p&gt;
&lt;p&gt;Knowing your options is good. Don't let black magic scare you.&lt;/p&gt;</content><category term="technology"/><category term="footgun"/><category term="python"/><category term="classes"/></entry><entry><title>On Marriage</title><link href="https://www.anserinae.net/on-marriage.html" rel="alternate"/><published>2018-03-29T00:00:00-05:00</published><updated>2018-03-29T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-03-29:/on-marriage.html</id><summary type="html">&lt;p&gt;Small reminders of warmth&lt;/p&gt;</summary><content type="html">&lt;p&gt;Being married in your 30s isn't easy, and adding young children to the mix makes things exponentially more difficult. As the trappings of courtship and early wedded life are slowly stripped away, your life is concentrated down to the few things matter most: mid-career effort, raising your children and the moments you steal from the first two in order to keep your marriage alive until Spring comes again. The ruthless calculus of effort and exhaustion demands that everything else be dropped.&lt;/p&gt;
&lt;p&gt;Under these straightened circumstances, a successful marriage isn't necessarily an architectural marvel to behold from afar; rather, it is a particularly strong foundation that allows the other things in your life to flourish. And just as no one appreciates the strength of a foundation until they are caught in a building that is collapsing, a strong marriage is unobtrusive until it starts unravelling.&lt;/p&gt;
&lt;p&gt;If you are lucky (and I am), you will receive gentle reminders of these truisms without being party to a disintegrating marriage. I was given one such reminder a short while ago: my wife got up from bed to visit the restroom after we fell asleep holding hands. My sleep-addled mind was struck by how cold my hand felt without her holding it -- so much so that two days later I still feel the need to commit my recollection of the experience to paper.&lt;/p&gt;
&lt;p&gt;Anyhow... this code isn't going to write itself. I do love my wife though, and that makes the grind just that bit more tolerable.&lt;/p&gt;</content><category term="life"/><category term="family"/><category term="warmth"/></entry><entry><title>Using Python Decorators for Authentication</title><link href="https://www.anserinae.net/using-python-decorators-for-authentication.html" rel="alternate"/><published>2018-03-19T16:30:00-05:00</published><updated>2018-03-19T16:30:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-03-19:/using-python-decorators-for-authentication.html</id><summary type="html">&lt;p&gt;When the language hands you a hammer, everything starts looking like a nail&lt;/p&gt;</summary><content type="html">&lt;h3&gt;A Refresher&lt;/h3&gt;
&lt;p&gt;Python decorators are bits of syntactic sugar that allow you to execute complementary code every time a function is executed. Given this function...&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;super_useful_decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&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;wrapper&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hello world&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;fn&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;wrapper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...this:&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;hello_world&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;

&lt;span class="n"&gt;super_useful_decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hello_world&lt;/span&gt;&lt;span class="p"&gt;())()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...is exactly the same as this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@super_useful_decorator&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;hello_world&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;

&lt;span class="n"&gt;hello_world&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Who Cares?&lt;/h3&gt;
&lt;p&gt;At this point, most decorator tutorials wax lyrical about how python functions are first class and then spend a lot of time talking about closures. This is &lt;em&gt;interesting&lt;/em&gt;, but not necessarily immediately &lt;em&gt;useful&lt;/em&gt;. Here instead, is the cliffnotes version:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Function decorators let you do things automatically when you call a function.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Keeping this in mind, let's move on to an actual use case for decorators.&lt;/p&gt;
&lt;h3&gt;Authenticating API Calls&lt;/h3&gt;
&lt;p&gt;Say you're setting up a REST API which requires an authenticated token from the user:&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;token_is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Replace with a call to your token validation service&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;42&amp;#39;&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;authenticate_access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&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;token_is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&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;rest_function_1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;=&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;authenticate_access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code] = 401&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payload&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input_1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;input_2&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rv&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;rest_function_2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;=&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;authenticate_access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payload&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input_1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;input_2&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is perfectly workable code, but there are a couple of problems with it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Boredom&lt;/strong&gt;: Authentication code has to be written for each REST API call. This is boring for the programmer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brittleness&lt;/strong&gt;: Changing the authentication code in any way means that every REST API function has to be altered.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repetition&lt;/strong&gt;: This follows from the last problem, but bears repetition (heh). Saying the same thing over and over in authentication code is dangerous: sooner or later, you're going to say it wrong and let a bunch of people use your service without authentication.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python decorators are a pretty elegant solution to this problem:&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;token_is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# No seriously, replace this with your own validation code&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;42&amp;#39;&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;authenticate_access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&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;wrapfn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;token_is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
            &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payload&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapfn&lt;/span&gt;

&lt;span class="nd"&gt;@authenticate_access&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;rest_function_1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_2&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;input_1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;input_2&lt;/span&gt;

&lt;span class="nd"&gt;@authenticate_access&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;rest_function_2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_2&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;input_1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;input_2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's try executing this:&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;In&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;rest_function_1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;43&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&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="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;rest_function_1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;42&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;payload&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;rest_function_2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;42&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;payload&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Neat.&lt;/p&gt;
&lt;p&gt;As an additional bonus, there is now clear separation of concerns between authentication and whatever it is the REST API functions are supposed to be doing. The code just &lt;em&gt;looks&lt;/em&gt; cleaner.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;If you are using Python, you should be making heavy use of decorators.&lt;/p&gt;
&lt;p&gt;If you want to see what other people have used decorators for, the Python wiki is &lt;a href="https://wiki.python.org/moin/PythonDecoratorLibrary"&gt;kind of amazing&lt;/a&gt;.&lt;/p&gt;</content><category term="technology"/><category term="python"/><category term="decorators"/><category term="authentication"/></entry><entry><title>Testing Done Right</title><link href="https://www.anserinae.net/testing-done-right.html" rel="alternate"/><published>2018-03-18T13:05:00-05:00</published><updated>2018-03-18T13:05:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-03-18:/testing-done-right.html</id><summary type="html">&lt;p&gt;If testing were an artform, SQLite would be Rembrandt&lt;/p&gt;</summary><content type="html">&lt;p&gt;The SQLite project apparently has a 1:730 code-to-test ratio: &lt;a href="https://www.sqlite.org/testing.html"&gt;How SQLite Is Tested&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Does your project need this kind of testing? Probably not. It sure is nice to see what the Platonic Ideal of testing looks like though.&lt;/p&gt;</content><category term="technology"/><category term="sqlite"/><category term="testing"/></entry><entry><title>Niche Sports Sunday</title><link href="https://www.anserinae.net/niche-sports-sunday.html" rel="alternate"/><published>2018-03-18T00:00:00-05:00</published><updated>2018-03-18T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-03-18:/niche-sports-sunday.html</id><summary type="html">&lt;p&gt;Updates from sports no one cares about&lt;/p&gt;</summary><content type="html">&lt;p&gt;Welcome, dear reader, to the first ever edition of Niche Sports Sunday, a blog thing that attempts to cover sports that I follow that no one else really cares about.&lt;/p&gt;
&lt;h5&gt;Princeton Crowned ECAC Champions&lt;/h5&gt;
&lt;p&gt;Congratulations to Princeton Hockey on extending their two month winning streak and capturing the Whitelaw Cup. Winning the postseason tournament is a great achievement, especially when you consider Princeton was ranked 7th in the league coming into it. That they pipped the top three teams in the league (Union, Cornell and Clarkson) on their way to the trophy makes the feat even more impressive.&lt;/p&gt;
&lt;p&gt;The win also gives Princeton ECAC's only autobid to the NCAA tournament. Right now it looks like we'll be playing Notre Dame in the first round of the Mid Western Regionals. Ordinarily I would be nervous about playing the number 2 team in the country in a playoff, but we tied St. Cloud State (ranked first in the country) and beat Cornell (ranked second) in very recent memory. Princeton clearly has the firepower and defensive wherewithal to fight and win against the toughtest opponents; I expect great things from them in the tournament.&lt;/p&gt;
&lt;p&gt;We've been waiting for this run since 2008: go Tigers!&lt;/p&gt;
&lt;h5&gt;Bangladesh and the Nidihas Trophy&lt;/h5&gt;
&lt;p&gt;Half a world away in Sri Lanka, Bangladesh won a heated do-or-die T20 against Sri Lanka to make the finals of the trilateral Nidihas Trophy. These kinds of tournaments are usually total snoozefests designed to meet FTP obligations, but this one has been &lt;em&gt;insane&lt;/em&gt; because of poor player behavior on and off the field. Our 12th man got &lt;a href="http://www.espncricinfo.com/story/_/id/22803467/shakib-nurul-fined-breaching-code-conduct"&gt;into a pushing match&lt;/a&gt; with a group of Sri Lankan players, Shakib Al Hasan caused all sorts of headaches by trying to &lt;a href="https://streamable.com/eu7i0"&gt;call players off the field&lt;/a&gt; for debatably poor umpiring, and the team as a whole caused quite a stir by &lt;a href="http://www.espncricinfo.com/story/_/id/22796391/bangladesh-dressing-room-door-damaged-colombo"&gt;doing some serious damage&lt;/a&gt; to the their dressing room. The team's behavior was so attrocious that the Bangladesh board was then forced to &lt;a href="https://i.redd.it/c3cfv3gbvcm01.jpg"&gt;issue a heretofore unimaginable apology&lt;/a&gt; to their hosts.&lt;/p&gt;
&lt;p&gt;I have on occasion lamented the lack of assertiveness on the part of Bangladesh's cricketers (remember when one of our batsmen asked Steve Waugh for an autograph on the field?), but these kinds of shenanigans are a step too far. Our internet fans are already enough of an embarrassment without the national team also bringing our  cricket establishment into disrepute.&lt;/p&gt;
&lt;p&gt;Woof.&lt;/p&gt;
&lt;h5&gt;Cricket in Nepal&lt;/h5&gt;
&lt;p&gt;We're going to go even more niche now. Topic: Associate Cricket!&lt;/p&gt;
&lt;p&gt;Associate Cricket is a second-league international cricket competition used to promote the next generation of test playing nations.&lt;/p&gt;
&lt;p&gt;After a long period of inactivity (or was it incompetence?), the ICC overcame its anti-expansion tendencies and started promoting less storied cricket programs to the top tier via the World Cup Qualifier program. Ireland got Test status a few months back, and by qualifying for the World Cup, Nepal has earned One Day status for the next few years.&lt;/p&gt;
&lt;p&gt;That's right: a tiny, mountainous country with virtually no cricket culture (and a currently suspended cricket board) has boostrapped itself to international ODI status.&lt;/p&gt;
&lt;p&gt;To give an idea of what cricket in Nepal used to be like: in the late 90s I remember scouring all of Kathmandu to find a large enough and flat enough field in which to play pickup cricket with my neighbors. The only place we could find was a piece of undeveloped land owned by the Pakistan Embassy. We took a &lt;em&gt;lot&lt;/em&gt; of hits to the head that summer because of the undeveloped nature of pitch; overcoming these kinds of infrastructure problems and producing an international grade cricket team is a massive achievement.&lt;/p&gt;
&lt;p&gt;Congratulations to Nepal: I can't wait to see what they do with their hardearned ODI status.&lt;/p&gt;
&lt;h5&gt;What It All Means&lt;/h5&gt;
&lt;p&gt;I am generally down on the state of cricket. The ever-rising tide of T20 money has made most players lose sight of what makes cricket great (hint: it's Tests). While I can't find it in myself to be angry about this -- the players are only human -- the combination of money, incompetent administration and the lasting stench of corruption that has pervaded the game for the last decade means that I haven't enjoyed top tier cricket for a long time.&lt;/p&gt;
&lt;p&gt;A lot of fans seem to have moved down the value chain to find good stories and meaningful competition. Cricket in places like Ireland, Nepal and Papua New Guinea has been a revelation: these teams play because they love the game. And while there may be riches down the road, for now it's all about their pure, unadulterated joy in the game.&lt;/p&gt;
&lt;p&gt;Long live Associate Cricket.&lt;/p&gt;</content><category term="life"/><category term="sports"/></entry><entry><title>A Child's Pain</title><link href="https://www.anserinae.net/a-childs-pain.html" rel="alternate"/><published>2018-03-14T00:00:00-05:00</published><updated>2018-03-14T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2018-03-14:/a-childs-pain.html</id><summary type="html">&lt;p&gt;Things that keep me awake at night.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last night, my eldest daughter slammed the heavy main door of our house on her tiny, two year old hand. The sound that came out of her was simultaneously the most 
piteous and 
terrifying thing I have ever heard in my life. &lt;/p&gt;
&lt;p&gt;She's fine now, but yeah: go hug your kids. &lt;/p&gt;</content><category term="life"/><category term="family"/><category term="kids"/></entry><entry><title>What's Cooking, OpenARC Edition</title><link href="https://www.anserinae.net/whats-cooking-openarc-edition.html" rel="alternate"/><published>2017-11-17T00:00:00-05:00</published><updated>2017-11-17T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2017-11-17:/whats-cooking-openarc-edition.html</id><summary type="html">&lt;p&gt;"Your November fix of OpenARC information"&lt;/p&gt;</summary><content type="html">&lt;h3&gt;Dude, you're lazy&lt;/h3&gt;
&lt;p&gt;Cut me some slack: in the space of the last year, I had another kid, moved to Los Angeles, then Las Vegas, and then back to Doha. Now that the sleep deprivation is slowly wearing off, I have resumed work on &lt;a href="https://github.com/kchoudhu/openarc"&gt;OpenARC&lt;/a&gt;, and am happy to report that the project has  gone from being a shitty ORM with lots of boilerplate overhead to a shitty ORM with slightly less boilerplate overhead.&lt;/p&gt;
&lt;h3&gt;Introducing Minimal Definition OAGs&lt;/h3&gt;
&lt;p&gt;With &lt;a href="https://github.com/kchoudhu/openarc/commit/9d8c296e24074a58514e1c4590ee82556dd19640"&gt;this commit&lt;/a&gt;, we can now define graph nodes (OAGs) like so:&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;OAG_SubNode1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OAG_RootNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@staticproperty&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;dbstreams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field_sn1&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;int&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;OAG_SubNode2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OAG_RootNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@staticproperty&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;dbstreams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field_sn2&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;int&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;OAG_AutoNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OAG_RootNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@staticproperty&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;dbstreams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field2&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;int&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field3&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;int&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode1&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode1&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode2&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode2&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@oagprop&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;CalcDeriv&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="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;subnode1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_sn1&lt;/span&gt; &lt;span class="o"&gt;+&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;subnode2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_sn2&lt;/span&gt;

&lt;span class="n"&gt;newOAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAG_AutoNode1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The instantiation of &lt;code&gt;OAG_AutoNode&lt;/code&gt; on the last line results in the cascading creation of tables in the database for &lt;code&gt;OAG_AutoNode&lt;/code&gt;, &lt;code&gt;OAG_SubNode1&lt;/code&gt; and &lt;code&gt;OAG_SubNode2&lt;/code&gt;, along with corresponding basic indicies and foreign key constraints among all the newly created tables.&lt;/p&gt;
&lt;p&gt;In-code definition of database constructs and their automated lazy creation in the database should drastically lower the amount of database administration that needs to be done when creating new OAG types. On an aesthetic level, I find the new definition style much nicer than the &lt;a href="https://github.com/kchoudhu/openarc/blob/9d8c296e24074a58514e1c4590ee82556dd19640/openarc/tests/openlibarc_graph_test.py#L482"&gt;previous&lt;/a&gt; &lt;a href="https://github.com/kchoudhu/openarc/blob/master/sql/openarc.000.001.0000.sql"&gt;state of affairs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is worth pointing out a few things about the new defintion framework:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It supports the addition of new &lt;code&gt;dbstreams&lt;/code&gt;:&lt;/strong&gt; Missing &lt;code&gt;dbstreams&lt;/code&gt; are added to existing tables as they are detected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deleting or changing &lt;code&gt;dbstreams&lt;/code&gt; is NOT supported:&lt;/strong&gt; If you make a mistake and need to delete or rename &lt;code&gt;dbstreams&lt;/code&gt;, manual database surgery is still necessary. The library detects missing and/or removed &lt;code&gt;dbstream&lt;/code&gt;s and thows &lt;code&gt;OAGraphIntegrityError&lt;/code&gt; exceptions, so at least you will know when you are screwed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There is no cycle detection:&lt;/strong&gt; OpenARC object graphs are intended to be &lt;a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph"&gt;directed acyclic graphs&lt;/a&gt;, but the framework does not yet do any kind of cycle detection. To make sure your current graphs are compatible with upcoming functionality, do not introduce cycles in your object graphs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Old-style OAG definitions are still supported:&lt;/strong&gt; A project with only 36 commits should not have legacy baggage, but here we are: OpenARC now has two ways to define OAGs. For now, there are no plans to deprecate or eliminate the old definition style in favor of the new one.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Way Forward&lt;/h3&gt;
&lt;p&gt;OpenARC bills itself as a functional reactive graph database, but has so far only partially delivered on its functional reactive promises. The aim over the next few months is to flesh out the project's functional reactive story, with effort concentrated on the following projects.&lt;/p&gt;
&lt;h6&gt;Project 1: Event Propagation Up The Graph&lt;/h6&gt;
&lt;p&gt;Before jumping into how to extend OpenARC's FR model, a quick review of what it currently looks like is in order:&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;sn1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;field_sn1&amp;#39;&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;sn2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;field_sn2&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;newOAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;\
    &lt;span class="n"&gt;OAG_AutoNode1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field2&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field3&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode1&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sn1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode2&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sn2&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;newOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalcDeriv&lt;/span&gt;
&lt;span class="c1"&gt;# output: 3&lt;/span&gt;

&lt;span class="n"&gt;newOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_sn1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;newOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_sn2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;newOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalcDeriv&lt;/span&gt;
&lt;span class="c1"&gt;# output: 5&lt;/span&gt;

&lt;span class="n"&gt;newOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;newOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalcDeriv&lt;/span&gt;
&lt;span class="c1"&gt;# output: 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Events on subnodes (in this case a change of value for several dbstreams) are "pulled" up the graph in order to calculate a desired derived value (the &lt;code&gt;CalcDeriv&lt;/code&gt; &lt;code&gt;oagprop&lt;/code&gt; on &lt;code&gt;newOAG&lt;/code&gt;). While the &lt;code&gt;clear()&lt;/code&gt; call gives us the ability to remove in-memory edits on the graph and carry out rapid "what if" experiments, applications looking for state changes are still limited to inefficiently polling calculated &lt;code&gt;oagprops&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Allowing events to be bubbled up the DAG as dbstreams are edited mitigates this limitation and ensures that work is done only when needed. A sample session would look something 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="c1"&gt;# Define event handlers&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;OAG_AutoNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OAG_RootNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@staticproperty&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;dbstreams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field2&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;int&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="mi"&gt;0&lt;/span&gt;    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;none&lt;/span&gt;                  &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field3&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;int&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="mi"&gt;0&lt;/span&gt;    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;none&lt;/span&gt;                  &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode1&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode1&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="n"&gt;subnode_update_handler&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode2&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode2&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="n"&gt;subnode_update_handler&lt;/span&gt;&lt;span class="p"&gt;]&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;subnode_update_handler&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="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;A subnode has been updated. CalcDeriv is now [&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;]&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&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;CalcDeriv&lt;/span&gt;

    &lt;span class="nd"&gt;@oagprop&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;CalcDeriv&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="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;subnode1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_sn1&lt;/span&gt; &lt;span class="o"&gt;+&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;subnode2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_sn2&lt;/span&gt;

&lt;span class="n"&gt;sn1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;field_sn1&amp;#39;&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;sn2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;field_sn2&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;newOAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;\
    &lt;span class="n"&gt;OAG_AutoNode1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field2&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field3&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode1&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sn1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode2&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sn2&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;newOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calcDeriv&lt;/span&gt;
&lt;span class="c1"&gt;# output: 3&lt;/span&gt;

&lt;span class="n"&gt;newOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subnode2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_sn2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="c1"&gt;# aysnc output: A subnode has been updated. CalcDeriv is now 4.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On-graph event propagation will simplify any programming task that requires calculations carried out off the back of event changes, all for the marginal cost of defining an extra event handler.&lt;/p&gt;
&lt;h6&gt;Project 2: Remotely Accessible OAGs&lt;/h6&gt;
&lt;p&gt;Currently, an OAG is accessible only from the process that created it. After this change, OAGs will be globally addressable and accessible over the network.&lt;/p&gt;
&lt;p&gt;A typical session would look something like this: on the first host, an OAG is instantiated and made remotely accessible:&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;exportOAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;field_sn1&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;exportOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oagurl&lt;/span&gt;
&lt;span class="c1"&gt;# Throws OAError(&amp;quot;OAG is not accessible remotely&amp;quot;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;exportOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;public&lt;/span&gt;
&lt;span class="c1"&gt;# output: False&lt;/span&gt;

&lt;span class="n"&gt;exportOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;public&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;exportOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oagurl&lt;/span&gt;
&lt;span class="c1"&gt;# output: oag://ardentprayer.anserinae.net:7843/df0e896590484364ac1704893f5e4fb1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On the second host, &lt;code&gt;exportOAG&lt;/code&gt; can be accessed and incorporated into local OAGs as if it is local, allowing for the same event propagation semantics as fully local graphs:&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;importOAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAG_SubNode1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oagurl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;oag://ardentprayer.anserinae.net:7843/df0e896590484364ac1704893f5e4fb1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;importOAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field_sn1&lt;/span&gt;
&lt;span class="c1"&gt;# output: 1024&lt;/span&gt;

&lt;span class="n"&gt;superOAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;\
    &lt;span class="n"&gt;OAG_SubNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field2&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;field3&amp;#39;&lt;/span&gt;   &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode1&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;importOAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;subnode2&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sn2&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The implications of this change are massive: changing dbstreams on one host can trigger events on another, making OpenARC an implicit RPC framework that supports distributed functional reactive programming.&lt;/p&gt;
&lt;h6&gt;Project 3: Defining the OAG Protocol&lt;/h6&gt;
&lt;p&gt;Centralizing and codifying the protocol used by OAGs to talk to each other and the underlying storage layer will make it possible to implement graph nodes in any language and use any storage backend. Codification will involve not only a written spec, but also a test suite against which any other OpenARC protocol implementation can be tested.&lt;/p&gt;
&lt;p&gt;The motivation for this project is simple: OpenARC is currently implemented in Python, which does not offer parallelism, and we must plan for its eventual re-implementation in a language that does (Rust perhaps?) if we hope to support truly high-performance applications. Clarifying inter-OAG interfaces and providing a clear implementation test suite will make this (and any other) re-implementation more comprehensive and less error-prone.&lt;/p&gt;
&lt;h6&gt;Project 4: Documentation&lt;/h6&gt;
&lt;p&gt;This is self explanatory: high quality projects have high quality documentation.&lt;/p&gt;
&lt;p&gt;The project aspires to documentation that is thorough and example-driven. OpenARC is already in use over at &lt;a href="http://www.levelcompute.com"&gt;Levelcompute&lt;/a&gt;, and to the extent that it is possible I will be pulling actual production examples to show how the project can be used in production in a safe and robust manner.&lt;/p&gt;
&lt;h3&gt;Timelines&lt;/h3&gt;
&lt;p&gt;As you may have guessed, OpenARC is being actively developed as part of my work at &lt;a href="http://www.levelcompute.com"&gt;Levelcompute&lt;/a&gt;. There is a pressing business need for its development, so you can expect a fairly rapid development cadence. We are hoping for feature-rich minor releases once a month, followed by an 1.0 release by mid-year 2018.&lt;/p&gt;
&lt;p&gt;If you want to talk to me about the project, you can reach out to me on &lt;a href="https://www.twitter.com/kchoudhu"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.github.com/kchoudhu/openarc&amp;quot;"&gt;Github&lt;/a&gt; or via &lt;a href="mailto:kamil.choudhury@anserinae.net"&gt;e-mail&lt;/a&gt;: I'm always open to feedback!&lt;/p&gt;</content><category term="technology"/><category term="openarc"/><category term="python"/><category term="functional reactive programming"/></entry><entry><title>Multiple SSIDs Using Hostapd and FreeBSD</title><link href="https://www.anserinae.net/multiple-ssids-using-hostapd-and-freebsd.html" rel="alternate"/><published>2017-09-06T00:00:00-05:00</published><updated>2017-09-06T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2017-09-06:/multiple-ssids-using-hostapd-and-freebsd.html</id><summary type="html">&lt;p&gt;"You too can make contributions to -CURRENT!"&lt;/p&gt;</summary><content type="html">&lt;h4&gt;We All Run Crap On Our Networks&lt;/h4&gt;
&lt;p&gt;Part of the reason I run my own wireless infrastructure is because I have, over time, obtained a fair number of "smart" doodads that require wireless connectivity. Remember the &lt;a href="https://en.wikipedia.org/wiki/2016_Dyn_cyberattack"&gt;2016 Dyn attack&lt;/a&gt;? This stuff needs to be on a segregated network with no access to anything valuable on my home network. The IoT devices go on their own SSID, live on a separate subnet, and are firewalled from anything useful. In this post I am going to elide the PF shenanigans that make this possible (the &lt;a href="https://www.freebsd.org/doc/handbook/firewalls-pf.html"&gt;FreeBSD handbook&lt;/a&gt; does a better job of this than I ever could), and instead concentrate on the business of running two wireless access points using only one wireless card.&lt;/p&gt;
&lt;h4&gt;Preliminaries&lt;/h4&gt;
&lt;p&gt;The idea here is that we have two SSIDs, one called &lt;code&gt;Crystalnet-int&lt;/code&gt; and another called &lt;code&gt;Crystalnet-iot&lt;/code&gt;. I leave it as an exercise to the reader to deduce which SSID my wayward IoT devices will be connecting to. As always, all commands are issued as root.&lt;/p&gt;
&lt;p&gt;Our underlying wireless device shows up when we execute the following command:&lt;/p&gt;
&lt;div class=highlight&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# sysctl net.interfaces.wlan
# ath0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We then start adding things to &lt;code&gt;rc.conf&lt;/code&gt;. First we define our new virtual wireless interfaces:&lt;/p&gt;
&lt;div class=highlight&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wlans_ath0="wlan0 wlan1"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We also tell the system that the virtual interfaces will be operating as access points:&lt;/p&gt;
&lt;div class=highlight&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;create_args_wlan0="wlanmode hostap bssid"
create_args_wlan1="wlanmode hostap bssid"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;bssid&lt;/code&gt; argument above is critical: it ensures that the two new interfaces have different MAC addresses, and cannot be mistaken for each other by connecting clients.&lt;/p&gt;
&lt;p&gt;Restart networking:&lt;/p&gt;
&lt;div class=highlight&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# service netif restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Issuing ifconfig now shows two new interfaces: &lt;code&gt;wlan0&lt;/code&gt; and &lt;code&gt;wlan1&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Nothing Is Ever Simple&lt;/h4&gt;
&lt;p&gt;Next we configure the builtin &lt;code&gt;hostapd(8)&lt;/code&gt; module to manage our new virtual wireless interfaces. The underlying binary takes multiple configuration files before daemonizing itself, one each for every interface being managed. My configurations are simple, and taken directly from the &lt;a href="https://www.freebsd.org/doc/handbook/network-wireless.html"&gt;handbook&lt;/a&gt;:&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;# vi /etc/hostapd-&amp;lt;iface&amp;gt;.conf&lt;/span&gt;
&lt;span class=n&gt;interface&lt;/span&gt;&lt;span class=o&gt;=&amp;lt;&lt;/span&gt;&lt;span class=n&gt;iface&lt;/span&gt;&lt;span class=o&gt;&amp;gt;&lt;/span&gt;
&lt;span class=n&gt;debug&lt;/span&gt;&lt;span class=o&gt;=&lt;/span&gt;&lt;span class=mi&gt;1&lt;/span&gt;
&lt;span class=n&gt;ctrl_interface&lt;/span&gt;&lt;span class=o&gt;=/&lt;/span&gt;&lt;span class=k&gt;var&lt;/span&gt;&lt;span class=o&gt;/&lt;/span&gt;&lt;span class=n&gt;run&lt;/span&gt;&lt;span class=o&gt;/&lt;/span&gt;&lt;span class=n&gt;hostapd&lt;/span&gt;
&lt;span class=n&gt;ctrl_interface_group&lt;/span&gt;&lt;span class=o&gt;=&lt;/span&gt;&lt;span class=n&gt;wheel&lt;/span&gt;
&lt;span class=n&gt;ssid&lt;/span&gt;&lt;span class=o&gt;=&amp;lt;&lt;/span&gt;&lt;span class=n&gt;ssid&lt;/span&gt;&lt;span class=w&gt; &lt;/span&gt;&lt;span class=n&gt;name&lt;/span&gt;&lt;span class=o&gt;&amp;gt;&lt;/span&gt;
&lt;span class=n&gt;wpa&lt;/span&gt;&lt;span class=o&gt;=&lt;/span&gt;&lt;span class=mi&gt;2&lt;/span&gt;
&lt;span class=n&gt;wpa_passphrase&lt;/span&gt;&lt;span class=o&gt;=&amp;lt;&lt;/span&gt;&lt;span class=n&gt;password&lt;/span&gt;&lt;span class=o&gt;&amp;gt;&lt;/span&gt;
&lt;span class=n&gt;wpa_key_mgmt&lt;/span&gt;&lt;span class=o&gt;=&lt;/span&gt;&lt;span class=n&gt;WPA&lt;/span&gt;&lt;span class=o&gt;-&lt;/span&gt;&lt;span class=n&gt;PSK&lt;/span&gt;
&lt;span class=n&gt;wpa_pairwise&lt;/span&gt;&lt;span class=o&gt;=&lt;/span&gt;&lt;span class=n&gt;CCMP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately, the existing &lt;code&gt;hostapd&lt;/code&gt; rc script doesn't allow for multiple access points, so I had to do a bit of surgery. The results are &lt;a href="https://gist.github.com/kchoudhu/9f7a0eb38722d82241518add921c3173"&gt;here&lt;/a&gt;; paste the contents into &lt;code&gt;/etc/rc.d/hostapd&lt;/code&gt;, and add the following to &lt;code&gt;/etc/rc.conf&lt;/code&gt;:&lt;/p&gt;
&lt;div class=highlight&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;hostapd_ifaces="wlan0 wlan1"
hostapd_enable="YES"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Upon starting the service...&lt;/p&gt;
&lt;div class=highlight&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;service hostapd start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;... we see two healthy SSIDs:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Wireless Success!" src="images/wireless-macosx.png"&gt;&lt;/p&gt;
&lt;h4&gt;And They Lived Happily Ever After&lt;/h4&gt;
&lt;p&gt;After my &lt;a href="/adventures-in-wifi-freebsd-edition.html"&gt;pseudo rant&lt;/a&gt; last time round, you could be forgiven for thinking that I was being sarcastic with the title of this section. In this case, however, you would be wrong: shortly after finishing setting up my access point, I sent out an &lt;a href="https://lists.freebsd.org/pipermail/freebsd-wireless/2017-September/007786.html"&gt;email&lt;/a&gt; asking if anyone wanted my multi-SSID modification, received confirmation that there was interest, and filed a &lt;a href="https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=222090"&gt;PR with the patch&lt;/a&gt;. I hope to see the change make it into the base system sooner rather than later.&lt;/p&gt;
&lt;p&gt;Yes, things are sometimes broken on FreeBSD, but it is worth taking a moment to celebrate how easy the Project makes it for even the most unmotivated among us&lt;sup id=sf-multiple-ssids-using-hostapd-and-freebsd-1-back&gt;&lt;a href=#sf-multiple-ssids-using-hostapd-and-freebsd-1 class=simple-footnote title="O HAI"&gt;1&lt;/a&gt;&lt;/sup&gt; to help ourselves.&lt;/p&gt;&lt;ol class=simple-footnotes&gt;&lt;li id=sf-multiple-ssids-using-hostapd-and-freebsd-1&gt;O HAI &lt;a href=#sf-multiple-ssids-using-hostapd-and-freebsd-1-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;</content><category term="technology"/><category term="atheros"/><category term="wifi"/><category term="freebsd"/><category term="hostapd"/></entry><entry><title>Adventures in WiFi, FreeBSD Edition</title><link href="https://www.anserinae.net/adventures-in-wifi-freebsd-edition.html" rel="alternate"/><published>2017-08-23T00:00:00-05:00</published><updated>2017-08-23T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2017-08-23:/adventures-in-wifi-freebsd-edition.html</id><summary type="html">&lt;p&gt;"It's fixed in -CURRENT, go away"&lt;/p&gt;</summary><content type="html">&lt;p&gt;** In Which I Make a Poor Purchase **&lt;/p&gt;
&lt;p&gt;My recent relocation from Doha to Las Vegas &lt;em&gt;sans&lt;/em&gt; computer gear has meant that I have had to build out my Las Vegas home's wifi infrastructure from scratch. I ordered a &lt;a href="https://www.newegg.com/Product/Product.aspx?Item=N82E16833704133"&gt;TP-LINK TL-WDN4800&lt;/a&gt; off Newegg, and thought I would be off to the races.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# sysctl net.interfaces.wlan
#
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Oh.&lt;/p&gt;
&lt;p&gt;I'm not sure what the folks over at &lt;a href="https://calomel.org/freebsd_wireless_access_point.html"&gt;calomel.org&lt;/a&gt; (who bought the same card and are apparently running 10.3-RELEASE) did, but dmesg showed the following error for me on 11.1-RELEASE:&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;ar9300_attach&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;calling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ar9300_hw_attach&lt;/span&gt;
&lt;span class="n"&gt;ar9300_hw_attach&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;calling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ar9300_eeprom_attach&lt;/span&gt;
&lt;span class="n"&gt;ar9300_flash_map&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unimplemented&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="n"&gt;now&lt;/span&gt;
&lt;span class="n"&gt;Restoring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&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;DRAM&lt;/span&gt;
&lt;span class="n"&gt;Restoring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&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;EEPROM&lt;/span&gt;
&lt;span class="n"&gt;ar9300_hw_attach&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ar9300_eeprom_attach&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="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;ath0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ath_getchannels&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unable&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;collect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;channel&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;hal&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;
&lt;span class="n"&gt;device_attach&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ath0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attach&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="mi"&gt;22&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;According to &lt;a href="https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=194336#c20"&gt;this comment on PR 194336&lt;/a&gt;, the failure to attach occurs due to the card reporting being in the Canadian regulatory domain, and support not being availble in the AR9300 driver for said regulatory domain.&lt;/p&gt;
&lt;p&gt;** Once More Down the Rabbit Hole**&lt;/p&gt;
&lt;p&gt;First of all, why is Newegg selling Canadian hardware in the US?&lt;/p&gt;
&lt;p&gt;Second of all, &lt;em&gt;shit&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Back to the PR, &lt;a href="https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=194336#c26"&gt;this comment&lt;/a&gt; indicates that a fix was committed to -CURRENT in 2016 and that it works, but that it had not yet made it into any production release. I'll have more to say on this in a moment, but first let's walk through actually fixing the problem at hand: we need to recompile our kernel with the fix.&lt;/p&gt;
&lt;p&gt;I could track 11-stable, but I want to stay as close as possible to the current -RELEASE so that future security updates are as predictable as possible from a feature-set perspective. In light of this requirement, we check out the 11.1-RELEASE branch:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# svnlite checkout https://svn.freebsd.org/base/releng/11.1/ /usr/src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We then merge &lt;code&gt;r306323&lt;/code&gt; (which contains the wifi driver fix) into our local source tree:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# cd /usr/src/head/sys/dev/ath
# svnlite merge -r 305614:306323 https://svn.freebsd.org/base/head/sys/dev/ath/ .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;r305614&lt;/code&gt; is the last revision for directory sys/dev/ath in the 11-STABLE branch, from which the 11.1-RELEASE branch was originally cut. We would like our mergeset to include any changes to the directory between this initial revision and the fix eventually committed in &lt;code&gt;r306323&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create a custom build configuration and kick off a build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# cd /usr/src/sys/amd64/conf
# cp GENERIC ARCBSD
# make buildkernel KERNCONF=ARCBSD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The last command will take a while to execute. Go get a cup of tea. If there are compile errors, you fucked up the SVN merge. Revert changes to the tree and try the merge again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# cd /usr/src
# svnlite revert --recursive .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once the build is done, reboot and check that the system is, in fact, using the new kernel:&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="n"&gt;uname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="n"&gt;FreeBSD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arctemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;anserinae&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;11.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;RELEASE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FreeBSD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;11.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;RELEASE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;#0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;r322740M&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Aug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;inara&lt;/span&gt;&lt;span class="nv"&gt;@arctemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;anserinae&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;net&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ARCBSD&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;amd64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;More importantly, check that the system can see the wifi card:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# sysctl net.wlan.devices
net.wlan.devices: ath0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can now follow the &lt;a href="https://www.freebsd.org/doc/handbook/network-wireless.html"&gt;FreeBSD handbook&lt;/a&gt; and use &lt;code&gt;hostapd&lt;/code&gt; to set up a wireless access point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And They Lived Happily Ever After&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Except not really. Now that I am running a custom kernel, I can now longer use &lt;code&gt;freebsd-update&lt;/code&gt; to update this machine. I am doomed to keep recompiling custom kernels with patches every time there is a security update to 11.1-RELEASE. This is work I wouldn't have to do if the FreeBSD developers merged what they built into extant production releases instead of saying "it's fixed in -CURRENT" and leaving users to flounder.&lt;/p&gt;
&lt;p&gt;John Kozubic said this much more eloquently than I ever can in this &lt;a href="https://lists.freebsd.org/pipermail/freebsd-hackers/2012-January/037294.html"&gt;measured, trenchant post&lt;/a&gt; on &lt;code&gt;freebsd-hackers&lt;/code&gt; a few years ago. As time goes by and I deploy more and more FreeBSD servers to production, this line from the post rings so very true: FreeBSD is becoming an operating system by, and for, FreeBSD developers. The rest of us, trying to get work done, necessarily suffer from this lack of focus.&lt;/p&gt;
&lt;p&gt;More on Hackernews &lt;a href="https://news.ycombinator.com/item?id=12368914"&gt;here&lt;/a&gt; and again &lt;a href="https://news.ycombinator.com/item?id=14853466"&gt;here&lt;/a&gt;.&lt;/p&gt;</content><category term="technology"/><category term="freebsd"/><category term="wifi"/><category term="atheros"/><category term="hostapd"/></entry><entry><title>Enabling Nested Virtualization in libvirt Environments</title><link href="https://www.anserinae.net/enabling-nested-virtualization-in-libvirt-environments.html" rel="alternate"/><published>2017-08-21T00:00:00-05:00</published><updated>2017-08-21T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2017-08-21:/enabling-nested-virtualization-in-libvirt-environments.html</id><summary type="html">&lt;p&gt;My mom probably shouldn't have bought me Matryoshka dolls when I was a child.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Friction&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A good chunk of my day to day programming activities involve running the code I am developing in virtual machines. This led to a problem when I &lt;a href="./libvirt-windows-workstation-with-gpu-passthrough.md"&gt;virtualized my desktop&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Only 32 Bits" src="/images/virtualbox-32-bit-only.png"&gt;&lt;/p&gt;
&lt;p&gt;Curious: Virtualbox only allows 32 bit operating systems to be emulated.&lt;/p&gt;
&lt;p&gt;One virtual machine over in my virtualized FreeBSD workstation, I received the following error when attempting to load the &lt;code&gt;bhyve&lt;/code&gt; hypervisor:&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;# kldload vmm&lt;/span&gt;
&lt;span class="n"&gt;vmx_init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;does&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;support&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VMX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;
&lt;span class="n"&gt;module_register_init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MOD_LOAD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vmm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xffffffff81e17169&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Curiouser and curiouser. The &lt;a href="https://wiki.freebsd.org/bhyve"&gt;bhyve wiki&lt;/a&gt; suggests this is related to virtualization platform not supporting EPT, i.e. nested virtualization.&lt;/p&gt;
&lt;p&gt;This is the kind of problem I &lt;em&gt;hate&lt;/em&gt;: here I am, trying to get on with my day and write some code, and now I have to figure out how to enable nested virtualization on &lt;code&gt;libvirt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Half A Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For once, the &lt;a href="https://fedoraproject.org/wiki/How_to_enable_nested_virtualization_in_KVM"&gt;documentation&lt;/a&gt; for this semi-obscure bit of Linux functionality isn't too terrible. First, make sure that nested virtualization is actually not allowed on the hypervisor:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; cat /sys/module/kvm_intel/parameters/nested
N
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Having confirmed this, set &lt;code&gt;kvm-intel.nested=1&lt;/code&gt; for the kernel's boot arguments in &lt;code&gt;/etc/default/grub&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;GRUB_CMDLINE_LINUX=&amp;quot;reboot=acpi kvm-intel.nested=1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Don't forget to update grub:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# update-grub
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After a system reboot, nested virtualization should be allowed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; cat /sys/module/kvm_intel/parameters/nested
Y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we tighten up our guest defintions. KVM offers degrees of fidelity when emulating CPUs, and unless the CPU is &lt;em&gt;perfectly&lt;/em&gt; emulated, nested virtualization won't work. They way to fix this is to edit the virtual machine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# virsh edit net.anserinae.sevenfoldgates
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The CPU model is changed from:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;lt;cpu mode=&amp;#39;host-model&amp;#39;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;lt;cpu mode=&amp;#39;host-passthrough&amp;#39;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After another system reboot, 64-bit virtualization is now possible in Windows:&lt;/p&gt;
&lt;p&gt;&lt;img alt="64 bits yay" src="/images/virtualbox-64-bit-yay.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FreeBSD&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, when the same changes are applied to my FreeBSD VM a new, equally inscrutable error is seen when attempting to active &lt;code&gt;bhyve&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="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kldload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vmm&lt;/span&gt;
&lt;span class="n"&gt;vmx_init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;does&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;support&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;desired&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;capabilities&lt;/span&gt;
&lt;span class="n"&gt;module_register_init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MOD_LOAD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vmm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xffffffff81a18592&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;According to &lt;a href="https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=203994"&gt;this PR&lt;/a&gt;, the error occurs because &lt;code&gt;bhyve&lt;/code&gt; is a little too zealous in verifying CPU fidelity. This comment caught my eye though:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Ubuntu 17.04 (kernel 4.10) works without any changes to bhyve&lt;/li&gt;
&lt;li&gt;Ubuntu 15.10 doesn't advertize INVVPID capability. While it would be possible to modify bhyve to work in this mode, I'm loathe to do it since it's such a niche case&lt;/li&gt;
&lt;li&gt;Ubuntu 16.04 seems to have the capabilities that most folk have reported.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The quality of &lt;code&gt;libvirt&lt;/code&gt;'s &lt;code&gt;host-passthrough&lt;/code&gt; implementation seems to be improving over time; could upgrading my hypervisor kernel to a sufficiently recent version do the trick? I followed &lt;a href="http://jensd.be/818/linux/install-a-newer-kernel-in-debian-9-stretch-stable"&gt;this guide&lt;/a&gt; to upgrade my Debian Stretch installation from kernel version 4.9 to 4.11, and saw the following upon attempting to reload the &lt;code&gt;vmm&lt;/code&gt; module in FreeBSD after a hypervisor reboot:&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;# kldload vmm&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;No news is good news, and I was subsequently able to run another virtual machine inside the FreeBSD VM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bhyve&lt;/code&gt;'s relatively short development history means that odd bugs are likely to be encountered in more exotic virtualization situations. In this case, good problem reporting and documentation meant that I was able to find a workaround without wasting &lt;em&gt;too&lt;/em&gt; much time on &lt;a href="https://xkcd.com/349/"&gt;upgrade hell&lt;/a&gt;.&lt;/p&gt;</content><category term="technology"/><category term="guides"/><category term="libvirt"/><category term="nested virtualization"/></entry><entry><title>Booting libvirt VMs from iSCSI Block Devices</title><link href="https://www.anserinae.net/booting-libvirt-vms-from-iscsi-block-devices.html" rel="alternate"/><published>2017-08-18T00:00:00-05:00</published><updated>2017-08-18T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2017-08-18:/booting-libvirt-vms-from-iscsi-block-devices.html</id><summary type="html">&lt;p&gt;We know how to do that, but we'll be damned if we tell you how.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A little bit of background for my &lt;a href="terrible-libvirt-network-performance.html"&gt;previous article&lt;/a&gt;: I am  working on booting all of my &lt;code&gt;libvirt&lt;/code&gt; virtual machines off ZVOLs exposed via iSCSI from a single, FreeBSD-based ZFS pool.&lt;/p&gt;
&lt;p&gt;The FreeBSD handbook does a fine job of explaining how to &lt;a href="https://www.freebsd.org/doc/handbook/network-iscsi.html"&gt;set up iSCSI targets using ctld&lt;/a&gt;, so this article is going to focus on the inadequately documented business of mounting iSCSI block devices on linux, and installing operating systems on them using &lt;code&gt;libvirt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;** Objective **&lt;/p&gt;
&lt;p&gt;Install windows on virtual machine &lt;code&gt;sevenfoldgates.anserinae.net&lt;/code&gt; whose primary disk is a 100G iSCSI LUN residing on &lt;code&gt;highcharity.anserinae.net&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;All commands are executed as root.&lt;/p&gt;
&lt;p&gt;** Create a storage pool backed by iSCSI **&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# virsh pool-define-as sevenfoldgates-1 iscsi \
        --source-host highcharity.anserinae.net \
        --source-dev iqn.2016-04.net.anserinae:sevenfoldgates-1 \
        --target /dev
Pool sevenfoldgates-1 defined
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Build and confirm the pool:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# virsh pool-build sevenfoldgates-1
Pool sevenfoldgates-1 built
# virsh pool-start sevenfoldgates-1
# virsh pool-list --all
Name               State        Autostart
-----------------------------------------
sevenfoldgates-1   active       yes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;sevenfoldgates-1&lt;/code&gt; is a 100G zvol, and now shows as device &lt;code&gt;/dev/sde&lt;/code&gt; on our system:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0  3.7T  0 disk
└─sda1   8:1    0  3.7T  0 part
sdb      8:16   0  3.7T  0 disk
└─sdb1   8:17   0  3.7T  0 part
sdc      8:32   0  3.7T  0 disk
sdd      8:48   0  3.7T  0 disk
└─sdd1   8:49   0  3.7T  0 part
*sde     8:64   1  100G  0 disk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Under normal circumstances, we would create volumes on our shiny new block device using &lt;code&gt;virsh vol-create&lt;/code&gt;, and use the resulting volume to boot our virtual machine. Unfortunately, like so much of &lt;code&gt;libvirt&lt;/code&gt;, iSCSI-based storage pools maddeningly deviate from what is expected:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; virsh vol-create-as sevenfoldgates-1 sevenfoldgates-1_1 100G
error: Failed to create vol sevenfoldgates-1_1
error: this function is not supported by the connection driver: storage pool does not support volume creation
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It turns out that &lt;code&gt;libvirt&lt;/code&gt; automatically maps individual LUNs to volumes, with no need for the  intermediate volume creation step. Thanks, I guess?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# virsh vol&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt;list sevenfoldgates&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt;1&lt;/span&gt;
&lt;span class="c"&gt; Name                 Path&lt;/span&gt;
&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt; unit:0:0:0           /dev/sde&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;** Install the VM **&lt;/p&gt;
&lt;p&gt;It's almost all over now except for the screaming (yes, I still use Windows 7):&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;virt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sevenfoldgates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;anserinae&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;ram&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32768&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sevenfoldgates&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;vcpus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bridge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vmbr0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;graphics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vnc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;listen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&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="mi"&gt;5903&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;target_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;serial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;cdrom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Windows&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SP1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iso&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note the structure of the &lt;code&gt;--disk&lt;/code&gt; option: it tells &lt;code&gt;virt-install&lt;/code&gt; to install the VM on volume &lt;code&gt;unit:0:0:0&lt;/code&gt; of our shiny new &lt;code&gt;sevenfoldgates-1&lt;/code&gt; storage pool.&lt;/p&gt;
&lt;p&gt;VNC into the host on port 5903 and we see this, indicating that our virtual machine has successfully booted off our iSCSI LUN:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sweet Success" src="/images/windows7-install.jpg"&gt;&lt;/p&gt;
&lt;p&gt;** Conclusion **&lt;/p&gt;
&lt;p&gt;As always, I am amazed by how much effort goes into building complex functionality into Linux, and how little effort is expended on documenting said functionality. Most of the information on this post comes from &lt;a href="https://linux.dell.com/files/whitepapers/KVM_Virtualization_in_RHEL_6_Made_Easy_Part2.pdf"&gt;this PDF&lt;/a&gt;, found on the Dell website via the 8th or 9th page of Google search results for 'libvirt iscsi boot'.&lt;/p&gt;
&lt;p&gt;If you ever read this, thanks for the pointers Jose De La Rosa!&lt;/p&gt;</content><category term="technology"/><category term="guides"/><category term="libvirt"/><category term="iscsi"/><category term="linux"/></entry><entry><title>Find Recently Modified Files</title><link href="https://www.anserinae.net/find-recently-modified-files.html" rel="alternate"/><published>2017-08-18T00:00:00-05:00</published><updated>2017-08-18T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2017-08-18:/find-recently-modified-files.html</id><summary type="html">&lt;p&gt;What the hell did I change on my computer?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here's a quick snippet to find files that have been recently modified on a computer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;find&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;-type&lt;span class="w"&gt; &lt;/span&gt;f&lt;span class="w"&gt; &lt;/span&gt;-printf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%TY-%Tm-%Td %TT %p\n&amp;#39;&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;sort&lt;span class="w"&gt; &lt;/span&gt;-r
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Useful when you've made a fleet of changes to a machine and are trying to remember what you changed.&lt;/p&gt;</content><category term="technology"/><category term="unixfu"/></entry><entry><title>Terrible libvirt Network Performance</title><link href="https://www.anserinae.net/terrible-libvirt-network-performance.html" rel="alternate"/><published>2017-08-17T00:00:00-05:00</published><updated>2017-08-17T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2017-08-17:/terrible-libvirt-network-performance.html</id><summary type="html">&lt;p&gt;Bad defaults ruin a reasonable virtualizaton experience on Linux&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have recently started using Linux for virtualization on my desktop, and encountered terrible network performance between the host and (FreeBSD) guest.&lt;/p&gt;
&lt;p&gt;Here are some representative numbers, obtained using iperf:&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;# Host IP: 192.168.3.254&lt;/span&gt;
&lt;span class="c1"&gt;# Guest IP: 192.168.3.1&lt;/span&gt;

&lt;span class="c1"&gt;# Host -&amp;gt; guest&lt;/span&gt;
&lt;span class="c1"&gt;# $ guest ~&amp;gt; iperf -s&lt;/span&gt;
&lt;span class="c1"&gt;# $ host  ~&amp;gt; iperf -c 192.168.3.1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Interval&lt;span class="w"&gt;       &lt;/span&gt;Transfer&lt;span class="w"&gt;     &lt;/span&gt;Bandwidth
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0-11.7&lt;span class="w"&gt; &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.00&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.14&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec

&lt;span class="c1"&gt;# Guest to host&lt;/span&gt;
&lt;span class="c1"&gt;# $ host  ~&amp;gt; iperf -s&lt;/span&gt;
&lt;span class="c1"&gt;# $ guest ~&amp;gt; iperf -c 192.168.3.254&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Interval&lt;span class="w"&gt;       &lt;/span&gt;Transfer&lt;span class="w"&gt;     &lt;/span&gt;Bandwidth
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0-10.0&lt;span class="w"&gt; &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.63&lt;span class="w"&gt; &lt;/span&gt;GBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.14&lt;span class="w"&gt; &lt;/span&gt;Gbits/sec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is a truly abysmal result. Remember, these numbers are for transfer rates between a host and a virtualized guest -- we are not even hitting my network.&lt;/p&gt;
&lt;p&gt;What could the problem be?&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;$&lt;span class="w"&gt; &lt;/span&gt;virsh&lt;span class="w"&gt; &lt;/span&gt;domiflist&lt;span class="w"&gt; &lt;/span&gt;net.anserinae.installation03
&lt;span class="w"&gt;    &lt;/span&gt;Interface&lt;span class="w"&gt;  &lt;/span&gt;Type&lt;span class="w"&gt;       &lt;/span&gt;Source&lt;span class="w"&gt;     &lt;/span&gt;Model&lt;span class="w"&gt;       &lt;/span&gt;MAC
&lt;span class="w"&gt;    &lt;/span&gt;-------------------------------------------------------
&lt;span class="w"&gt;    &lt;/span&gt;vnet0&lt;span class="w"&gt;      &lt;/span&gt;network&lt;span class="w"&gt;    &lt;/span&gt;default&lt;span class="w"&gt;    &lt;/span&gt;rtl8139&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;:54:00:cd:58:29
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Wait what? An &lt;code&gt;rtl8139&lt;/code&gt;? From &lt;code&gt;if_rl.c&lt;/code&gt; on FreeBSD:&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;&lt;span class="o"&gt;*&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;RealTek&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8139&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PCI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NIC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;redefines&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;meaning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;low end.&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&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;probably&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;worst&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PCI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ethernet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ever&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;made&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&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;possible&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;exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&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;FEAST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;made&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SMC&lt;/span&gt;&lt;span class="p"&gt;.&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="mi"&gt;8139&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;supports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&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="n"&gt;DMA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terrible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nullifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;performance&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;gains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DMA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;usually&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offers&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="n"&gt;snip&lt;/span&gt;&lt;span class="o"&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;It&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;impossible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rotten&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;design&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;really&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;achieve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;decent&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;performance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;Mbps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unless&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;happen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="n"&gt;Mhz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PII&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&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="ow"&gt;some&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;equally&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;overmuscled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CPU&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;drive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's use something slightly less terrible for our guest's NIC:&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;virsh&lt;span class="w"&gt; &lt;/span&gt;edit&lt;span class="w"&gt; &lt;/span&gt;net.anserinae.installation03
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Change this...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;bridge&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;[random&lt;span class="w"&gt; &lt;/span&gt;output]
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;rtl8139&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/inteface&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;bridge&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;[random&lt;span class="w"&gt; &lt;/span&gt;output]
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;virtio&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/inteface&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Restart the virtual machine:&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;virsh&lt;span class="w"&gt; &lt;/span&gt;reset&lt;span class="w"&gt; &lt;/span&gt;net.anserinae.installation03
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;...and retry the performance tests again:&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;# Host -&amp;gt; guest&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Interval&lt;span class="w"&gt;       &lt;/span&gt;Transfer&lt;span class="w"&gt;     &lt;/span&gt;Bandwidth
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0-11.7&lt;span class="w"&gt; &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;.9&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;45&lt;/span&gt;.4&lt;span class="w"&gt; &lt;/span&gt;Gbits/sec

&lt;span class="c1"&gt;# Guest -&amp;gt; host&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Interval&lt;span class="w"&gt;       &lt;/span&gt;Transfer&lt;span class="w"&gt;     &lt;/span&gt;Bandwidth
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0-10.0&lt;span class="w"&gt; &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;.7&lt;span class="w"&gt; &lt;/span&gt;GBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;51&lt;/span&gt;.3&lt;span class="w"&gt; &lt;/span&gt;Gbits/sec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Performance is, conservatively speaking, improved by three orders of magnitude.&lt;/p&gt;
&lt;p&gt;I haven't tested &lt;code&gt;rtl8139&lt;/code&gt; performance on any other guest operating systems, but my gut feeling here is to say that the &lt;code&gt;libvirt&lt;/code&gt; maintainers could have chosen a slightly less garbage NIC as the default option for newly created virtual machines.&lt;/p&gt;</content><category term="technology"/><category term="libvirt"/><category term="iperf"/><category term="vtnet0"/><category term="rtl8139"/><category term="e1000"/></entry><entry><title>Fun With SQL Constraints</title><link href="https://www.anserinae.net/fun-with-sql-constraints.html" rel="alternate"/><published>2016-09-06T00:00:00-05:00</published><updated>2016-09-06T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2016-09-06:/fun-with-sql-constraints.html</id><summary type="html">&lt;p&gt;Constraints aren't always specified using the CONSTRAINT keyword.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Say you are managing entities in SQL and each entity has multiple subentities, of which exactly one can be "core":&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;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_entity_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;serial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;primary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subentities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_subentity_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;serial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;primary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_entity_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;references&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_entity_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;subentity_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_entity_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subentity_name&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;Up until yesterday, I would have enforced the one-core-subentity-per-entity in the client code. And while it is fashionable to play fast and loose with data integrity in the name of performance these days, doing so would have bothered me. Relying on anything other than your database engine to enforce data correctness is ultimately a lot like smoking at a gas station: you'll get away with it for a while, but a stray spark will eventually result in the &lt;a href="http://www.economist.com/news/science-and-technology/21656637-coincidental-string-accidents-reminder-how-difficult-space-flight"&gt;rapid, unplanned disassembly&lt;/a&gt; of your data.&lt;/p&gt;
&lt;p&gt;As it turns out, Postgres provides us with a solution in the form of &lt;a href="https://www.postgresql.org/docs/9.5/static/indexes-partial.html"&gt;partial indices&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is what I ended up doing:&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;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_one_active_subent_per_ent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subentities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_entity_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_core&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This creates an index (duh) that filters &lt;code&gt;subentities&lt;/code&gt; for all entities where &lt;code&gt;is_core&lt;/code&gt; is set to &lt;code&gt;TRUE&lt;/code&gt;. And since it's a unique index, there is an implicit constraint that there can only be one such entry per _entity_id. Here is the index in action:&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;--1. prime the entity table&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;values&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;LEVELCOMPUTE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;--2. insert a subentity where is_core=True&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subentities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_entity_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subentity_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LEVELCOMPUTE-1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;True&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;--3. try doing it again&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subentities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_entity_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subentity_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LEVELCOMPUTE-2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;True&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;--4. resume regular service&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subentities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_entity_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subentity_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&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="s1"&gt;&amp;#39;LEVELCOMPUTE-2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;False&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While (4) succeeds, (3) results in an error as it introduces a second &lt;code&gt;is_core=True&lt;/code&gt; for &lt;code&gt;_entity_id=1&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;ERROR&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;duplicate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;violates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;constraint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;u_one_active_se_per_e&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;DETAIL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_entity_id&lt;/span&gt;&lt;span class="o"&gt;)=(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;already&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nice.&lt;/p&gt;
&lt;p&gt;My only wish -- a minor one, natch -- is that all kinds of constraints (even the unusual ones like this) were exposed by the &lt;code&gt;CONSTRAINT&lt;/code&gt; keyword.&lt;/p&gt;</content><category term="technology"/><category term="sql"/><category term="postgres"/><category term="partial indices"/><category term="subset"/></entry><entry><title>Moving Abroad: A Financial Checklist</title><link href="https://www.anserinae.net/moving-abroad-a-financial-checklist.html" rel="alternate"/><published>2016-09-05T00:00:00-05:00</published><updated>2016-09-05T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2016-09-05:/moving-abroad-a-financial-checklist.html</id><summary type="html">&lt;p&gt;An internationally resilient personal finance system isn't as hard to build as you think.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I get a fair number of questions about how expats should set up their finances while living abroad. While the Choudhury family is still fairly new to this, I like to think that we have done a good job of building a resilient system that has worked well as we moved across North America, Europe and Asia over the last eight months.&lt;/p&gt;
&lt;p&gt;Through it all, our guiding principle has been to build redundancy into our planning: fraud-related account freezes are a fact of life when traveling abroad, and we never wanted to be without a backup when they happen.&lt;/p&gt;
&lt;p&gt;Without any further ado, and in no particular order, here is how we dealt with...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mail&lt;/strong&gt;: A US address is mandatory if you intend to keep dealing with US banks while abroad. We updated all of our banking addresses to my inlaws' place in the California, and made sure to sign up for electronic statements so they didn't get overwhelmed with our snail mail. If you don't have a relative you can rely on, services like &lt;a href="https://www.earthclassmail.com/"&gt;Earth Class Mail&lt;/a&gt; or &lt;a href="http://www.virtualpostmail.com/"&gt;Virtual Post Mail&lt;/a&gt; can provide you with virtual addresses. Beware: some financial institutions have cottoned on to this trick, and now refuse to have you as a customer if you use addresses from these services. I'm looking &lt;em&gt;you&lt;/em&gt;, American Express.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Communications&lt;/strong&gt;: Again, a US phone number is essential if you want to deal with US financial institutions. We killed two birds with one stone (the other being our local communications problem) by buying a pair of &lt;a href="https://fi.google.com/"&gt;Google FI&lt;/a&gt; phones before leaving for Qatar. Cumulatively, the plans cost $45 a month, and we can buy extra 4G data in 1GB increments for $10 apiece. The plans come with unlimited texting and free wifi calling back to the US ($0.20 if you use the local cell network). While we have local Qatari SIMs as backup, FI's versatility means that we haven't really had to use them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cash&lt;/strong&gt;: Qatar is a cash-based society, so reliably cheap access to cash via ATM was a huge consideration for us. After some research, we settled on the &lt;a href="https://www.fidelity.com/cash-management/fidelity-cash-management-account/overview"&gt;Fidelity Cash Management&lt;/a&gt; account for our primary checking needs. The account doesn't charge ATM fees anywhere in the world (reimbursing you if someone does charge you one), and charges a flat 1% fee in the unlikely event you decide to use your debit card for a purchase. &lt;a href="http://www.schwab.com/public/schwab/banking_lending/checking_account"&gt;Schwab&lt;/a&gt; offers a similar product, which we use as a backup cash-stash in case Fidelity freezes our accounts (we like redundancy, remember?).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Credit Cards&lt;/strong&gt;: My wife and I each have no-foreign-transaction-fee credit cards from four different banks (Amex, Barclays, Chase and Citi), for a total of 8 isolated lines of credit. This pretty much guarantees that one of them will always be available. Yes, we've been down three cards before one worked, so this paranoia isn't completely unjustified: getting caught without access to cash or credit while you are abroad is &lt;em&gt;unpleasant&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remittances&lt;/strong&gt;: My wife's salary is deposited into our Fidelity checking account, but my situation was a little bit more complicated. When I was on GBP payroll with GS, I had my salary deposited in a UK HSBC account that I had lying around from my last posting to London, and used the &lt;a href="http://personal.fidelity.com/accounts/services/forex.shtml"&gt;Fidelity transfer service&lt;/a&gt; to get the cash back to the US. There is a markup, but I found it to be less than 0.5% off midmarket rates for 10K+ transfers (we usually saved up a little before transferring money back to the US). For the record, I found the effort of timing transfers to take advantage GBP/USD rate fluctuations to not be worth it as everything ended up averaging out over time.&lt;/p&gt;
&lt;p&gt;Once I was moved over to Qatari payroll, I found myself in need of a local bank account. I signed up with &lt;a href="http://www.qnb.com/cs/Satellite/QNBFirst/en_QA/enHome"&gt;QNB&lt;/a&gt;, and found their services to be perfectly respectable. They have international transfers to the US baked into their website, and I have found their exchange rates to be favorable and their execution fees reasonable (about $3 a pop; coming from the US where international wires &lt;em&gt;start&lt;/em&gt; at $30, this was eye-opening).&lt;/p&gt;
&lt;p&gt;Whatever method you use to transfer your money back to the US, &lt;strong&gt;&lt;em&gt;start slow&lt;/em&gt;&lt;/strong&gt;. Transfer small amounts a few times to begin with to get a feel for transfer times, fees and repeatability. Only transfer large amounts once you feel fully comfortable with a transfer service. Trust me on this: there is no worse feeling than the sinking feeling you get in the pit of your stomach after having $20,000 go astray in the international wire system and not knowing who to talk to about it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Travel:&lt;/strong&gt; As anticipated, travel is now a huge part of our life, and I have no intention for paying for my "fair" share of it if at all possible. Qatar Airways is the local flag carrier and is also conveniently a member of the Oneworld alliance, so we decided to concentrate our mileage churning/collection on Oneworld programs. We rotate our credit cards every year, and the sign up bonuses from the credit cards easily cover our annual trips back to the US, as well as a couple of trips around the neighborhood every year. If you aren't already subscribed to &lt;a href="http://reddit.com/r/churning"&gt;/r/churning&lt;/a&gt;, you should be!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Taxes&lt;/strong&gt;: As Americans, we are (alas) taxed on our global income. We do get a hefty 100K per person &lt;a href="https://www.irs.gov/individuals/international-taxpayers/foreign-earned-income-exclusion"&gt;exemption on our foreign income&lt;/a&gt; from the IRS though, which in conjunction with the 0% local income tax in Qatar greatly cuts down on our tax bill.&lt;/p&gt;
&lt;p&gt;While our situation is simple, the interaction between pushier foreign tax authorities and the IRS can be much more complicated and it is generally simpler to pay an accountant to handle the mess for you. It is also worth checking to see if your employer provides international tax preparation: we were surprised to find that Amanda's employer contracts with PwC to provide such a service, and I was happy to let them deal with the unhappiness that is dealing with the IRS on my behalf.&lt;/p&gt;
&lt;p&gt;Whether you get your own accountant or use the services provided by someone else, &lt;em&gt;make sure&lt;/em&gt; your accountant provides guidance on navigating &lt;a href="https://www.irs.gov/businesses/corporations/foreign-account-tax-compliance-act-fatca"&gt;FATCA compliance&lt;/a&gt; (i.e. how to tell the government about your bank accounts abroad). The penalties for falling afoul of these regulations are truly horrific, and can negate whatever financial advantage you gain by moving abroad.&lt;/p&gt;
&lt;p&gt;So there you have it: the basic outline of a plan to manage your money and investments while you live abroad. I am sure I have missed many things in this post, so I will keep updating it as they pop into my mind. Happy travels!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Last updated: 9/5/2016&lt;/em&gt;&lt;/p&gt;</content><category term="money"/><category term="guides"/><category term="qatar"/><category term="expat"/><category term="personalfinance"/></entry><entry><title>OpenVPN In A Time Of Deep Packet Inspection</title><link href="https://www.anserinae.net/openvpn-in-a-time-of-deep-packet-inspection.html" rel="alternate"/><published>2016-08-25T00:00:00-05:00</published><updated>2016-08-25T00:00:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2016-08-25:/openvpn-in-a-time-of-deep-packet-inspection.html</id><summary type="html">&lt;p&gt;Adventures in running OpenVPN where it shouldn't be run&lt;/p&gt;</summary><content type="html">&lt;p&gt;I tunnel some (not all) traffic on my home network in Qatar through an OpenVPN server in the US. The reasons are completely un-nefarious: I have a couple of dev servers in a colo in New York that I didn't feel like uprooting and bringing to Qatar, so I now need to bridge my home network to the US so that I can still access my files. Fair dinkum.&lt;/p&gt;
&lt;p&gt;Within a few moments of connecting to my US OpenVPN server, I noticed this in the server logs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:55:50&lt;span class="w"&gt; &lt;/span&gt;installation00&lt;span class="w"&gt; &lt;/span&gt;openvpn_tap0&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;92375&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;TCP&lt;span class="w"&gt; &lt;/span&gt;connection&lt;span class="w"&gt; &lt;/span&gt;established&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;AF_INET&lt;span class="o"&gt;]&lt;/span&gt;ww.xx.yy.zz:61813
Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:55:52&lt;span class="w"&gt; &lt;/span&gt;installation00&lt;span class="w"&gt; &lt;/span&gt;openvpn_tap0&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;92375&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ww.xx.yy.zz:61813&lt;span class="w"&gt; &lt;/span&gt;WARNING:&lt;span class="w"&gt; &lt;/span&gt;Bad&lt;span class="w"&gt; &lt;/span&gt;encapsulated&lt;span class="w"&gt; &lt;/span&gt;packet&lt;span class="w"&gt; &lt;/span&gt;length&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;peer&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;18245&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;must&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1579&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;please&lt;span class="w"&gt; &lt;/span&gt;ensure&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;--tun-mtu&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;--link-mtu&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;equal&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;both&lt;span class="w"&gt; &lt;/span&gt;peers&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;condition&lt;span class="w"&gt; &lt;/span&gt;could&lt;span class="w"&gt; &lt;/span&gt;also&lt;span class="w"&gt; &lt;/span&gt;indicate&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;possible&lt;span class="w"&gt; &lt;/span&gt;active&lt;span class="w"&gt; &lt;/span&gt;attack&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;TCP&lt;span class="w"&gt; &lt;/span&gt;link&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;Attempting&lt;span class="w"&gt; &lt;/span&gt;restart...&lt;span class="o"&gt;]&lt;/span&gt;
Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:55:52&lt;span class="w"&gt; &lt;/span&gt;installation00&lt;span class="w"&gt; &lt;/span&gt;openvpn_tap0&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;92375&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ww.xx.yy.zz:61813&lt;span class="w"&gt; &lt;/span&gt;Connection&lt;span class="w"&gt; &lt;/span&gt;reset,&lt;span class="w"&gt; &lt;/span&gt;restarting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The client configuration worked fine in the US, so the warning about an active attack is not a red herring: the local telco clearly recognizes OpenVPN connections and attempts to scupper them.&lt;/p&gt;
&lt;p&gt;The next move in the standard playbook is to &lt;a href="https://airvpn.org/topic/16444-stunnel-openvpn-howto-for-airvpn-ssl-tunneling/"&gt;sheathe the VPN connection&lt;/a&gt; in something less suspicious. While tunnelling OpenVPN inside stunnel results in a viably stable connection, it is clear that the telco knows something fishy is going on. Here are the results of an iperf of the VPN's bandwidth on a connection that is nominally rated 30/5Mbps:&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;ID&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Interval&lt;span class="w"&gt; &lt;/span&gt;Transfer&lt;span class="w"&gt; &lt;/span&gt;Bandwidth
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0-60.4&lt;span class="w"&gt; &lt;/span&gt;sec&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.03&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;144&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Kbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0-60.6&lt;span class="w"&gt; &lt;/span&gt;sec&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.87&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.09&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Fortunately, &lt;code&gt;git clone&lt;/code&gt; does not need much bandwidth, so I am happy living with the slow connection for now.&lt;/p&gt;</content><category term="technology"/><category term="travel"/><category term="openvpn"/><category term="security"/></entry><entry><title>Rust: The Development Environment</title><link href="https://www.anserinae.net/rust-the-development-environment.html" rel="alternate"/><published>2016-06-24T01:03:00-05:00</published><updated>2016-06-24T01:03:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2016-06-24:/rust-the-development-environment.html</id><summary type="html">&lt;p&gt;Installing rust, cargo and some other creature comforts.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;This is the first in a series of articles examining the basics of the Rust language; it is not intended to be comprehensive, and is more than likely to be a pale immitation of the &lt;a href="https://doc.rust-lang.org/book/README.html"&gt;Rust Book&lt;/a&gt; which I heartily encourage you to read before using Rust in anger.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;** Installing Rust **&lt;/p&gt;
&lt;p&gt;Download and install Rust directly using the following command: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-sSf&lt;span class="w"&gt; &lt;/span&gt;https://static.rust-lang.org/rustup.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will install, among other things, the Rust compiler &lt;code&gt;rustc&lt;/code&gt;, and the Rust package manager &lt;code&gt;cargo&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;** Hello World! **&lt;/p&gt;
&lt;p&gt;Here is everyone's favorite introductory program in Rust: &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;fn&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Hello world!&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pop that into a file called main.rs and compile using the following command: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rustc&lt;span class="w"&gt; &lt;/span&gt;main.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A list on your directory will show an executable "main" in your home directory: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;edgeofumbra%&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l
-rwxr-xr-x&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inara&lt;span class="w"&gt;  &lt;/span&gt;staff&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;345684&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:03&lt;span class="w"&gt; &lt;/span&gt;main
-rw-r--r--&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inara&lt;span class="w"&gt;  &lt;/span&gt;staff&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;1262&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:01&lt;span class="w"&gt; &lt;/span&gt;main.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Execute the binary: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;edgeofumbra%&lt;span class="w"&gt; &lt;/span&gt;./main
Hello&lt;span class="w"&gt; &lt;/span&gt;world!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Congratulations, you have run your first Rust program. &lt;/p&gt;
&lt;p&gt;** Shipping With Cargo **&lt;/p&gt;
&lt;p&gt;Instead of compiling your programs with &lt;code&gt;rustc&lt;/code&gt;, it makes much more sense to use &lt;code&gt;cargo&lt;/code&gt; to manipulate your project. &lt;/p&gt;
&lt;p&gt;Set up a new application: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cargo&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;app-market&lt;span class="w"&gt; &lt;/span&gt;--bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This creates the following directory structure for you: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;app-market
&lt;span class="p"&gt;|&lt;/span&gt;____.gitignore
&lt;span class="p"&gt;|&lt;/span&gt;____Cargo.toml
&lt;span class="p"&gt;|&lt;/span&gt;____src
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;____main.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nifty. Toss the "Hello World!" code from above into &lt;code&gt;main.rs&lt;/code&gt;, and try this: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cargo&lt;span class="w"&gt; &lt;/span&gt;run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You'll see the following happen: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;edgeofumbra%&lt;span class="w"&gt; &lt;/span&gt;cargo&lt;span class="w"&gt; &lt;/span&gt;run
&lt;span class="w"&gt;   &lt;/span&gt;Compiling&lt;span class="w"&gt; &lt;/span&gt;app-market&lt;span class="w"&gt; &lt;/span&gt;v0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;file:///Users/inara/src/tmp/app-market&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;Running&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;target/debug/app-market&lt;span class="sb"&gt;`&lt;/span&gt;
Hello,&lt;span class="w"&gt; &lt;/span&gt;world!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is &lt;em&gt;way&lt;/em&gt; better than invoking &lt;code&gt;rustc&lt;/code&gt; and running binaries manually. &lt;/p&gt;
&lt;p&gt;There are a couple of other cargo commands that I find myself using frequently: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cargo build&lt;/code&gt; will build, but not run the contents of your project. &lt;/li&gt;
&lt;li&gt;&lt;code&gt;cargo test&lt;/code&gt; will build your project and run any tests you have defined for your project. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;cargo&lt;/code&gt; is very ergonomic: everything works predictably and without surprises. It is totally worth digging around and getting familiar with it. &lt;/p&gt;
&lt;p&gt;** Dependencies **&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Cargo.toml&lt;/code&gt; is used by by &lt;code&gt;cargo&lt;/code&gt; to manage project version information and dependencies. Go ahead and modify it to add a project dependency on the &lt;code&gt;rand&lt;/code&gt; module: &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;[package]&lt;/span&gt;
&lt;span class="na"&gt;name&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;&amp;quot;app-market&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;version&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;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;authors&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;[&amp;quot;Kamil Choudhury &amp;lt;kamil.choudhury@anserinae.net&amp;gt;&amp;quot;]&lt;/span&gt;

&lt;span class="k"&gt;[dependencies]&lt;/span&gt;
&lt;span class="na"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;*&lt;/code&gt; indicates that we want to pull in the latest version of the &lt;code&gt;rand&lt;/code&gt; module from &lt;a href="http://crates.io"&gt;crates.io&lt;/a&gt;. I won't go into it here, but you can use &lt;a href="https://en.wikipedia.org/wiki/Software_versioning#Degree_of_compatibility"&gt;semantic versioning&lt;/a&gt; to pin the app build to a specific version of the dependency. &lt;/p&gt;
&lt;p&gt;Go ahead and build the program: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;edgeofumbra%&lt;span class="w"&gt; &lt;/span&gt;cargo&lt;span class="w"&gt; &lt;/span&gt;build
&lt;span class="w"&gt;    &lt;/span&gt;Updating&lt;span class="w"&gt; &lt;/span&gt;registry&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;https://github.com/rust-lang/crates.io-index&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;Compiling&lt;span class="w"&gt; &lt;/span&gt;libc&lt;span class="w"&gt; &lt;/span&gt;v0.2.12
&lt;span class="w"&gt;   &lt;/span&gt;Compiling&lt;span class="w"&gt; &lt;/span&gt;rand&lt;span class="w"&gt; &lt;/span&gt;v0.3.14
&lt;span class="w"&gt;   &lt;/span&gt;Compiling&lt;span class="w"&gt; &lt;/span&gt;app-market&lt;span class="w"&gt; &lt;/span&gt;v0.1.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;file:///Users/inara/src/tmp/app-market&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nice. If you have spent any amount of time playing around with autoconf and its ilk, this is definitely a step up. &lt;/p&gt;
&lt;p&gt;** Autocompletion **&lt;/p&gt;
&lt;p&gt;If you are planning on using Rust for larger projects, the &lt;a href="https://github.com/phildawes/racer"&gt;RACER&lt;/a&gt; utility is great for Sublime Text 3 autocompletion. First up, install RACER using cargo: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cargo&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;racer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Verify that racer was installed and can be found by your shell; you may need to add the .cargo directory to your PATH for this to work: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;edgeofumbra%&lt;span class="w"&gt; &lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;racer
/Users/inara/.cargo/bin/racer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Install the rust source to a convenient location: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/path/to
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/rust-lang/rust&lt;span class="w"&gt; &lt;/span&gt;/path/to
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, use ST3 Package Control to install the &lt;a href="https://packagecontrol.io/packages/RustAutoComplete"&gt;RustAutoComplete&lt;/a&gt; package, and edit the package preferences to point to the RACER binary and the newly installed Rust source. In my case, the preferences file ended up looking 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="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Copy&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;place&lt;/span&gt; &lt;span class="n"&gt;into&lt;/span&gt; &lt;span class="n"&gt;your&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;User&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;full&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;racer&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;racer&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;already&lt;/span&gt;
    &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;fine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;racer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/Users/inara/.cargo/bin/racer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;generally&lt;/span&gt; &lt;span class="n"&gt;just&lt;/span&gt;
    &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;rust&lt;/span&gt; &lt;span class="n"&gt;compiler&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;search_paths&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;/path/to/rust/src&amp;quot;&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;At this point autocompletion options for the Rust standard library should show up when editing &lt;code&gt;.rs&lt;/code&gt; files. You can add to the &lt;code&gt;search_paths&lt;/code&gt; parameter to enable autocompletion for other, non-core libraries.&lt;/p&gt;
&lt;p&gt;** Conclusion **&lt;/p&gt;
&lt;p&gt;This post has shown you how to set up a basic Rust development environment. Next up: the Rust type system. &lt;/p&gt;</content><category term="technology"/><category term="guides"/><category term="rust"/><category term="cargo"/><category term="racer"/></entry><entry><title>Brexit Thoughts</title><link href="https://www.anserinae.net/brexit-thoughts.html" rel="alternate"/><published>2016-06-22T07:38:00-05:00</published><updated>2016-06-22T07:38:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2016-06-22:/brexit-thoughts.html</id><summary type="html">&lt;p&gt;Britain will be much poorer if they leave the EU&lt;/p&gt;</summary><content type="html">&lt;p&gt;The Choudhury family is happy to now be in Spain now after six weeks in London. Our visit was marked by a few trips to the doctor (highly recommended, we should get single player in the US!), unfamiliar weather (so much rain!) and truly terrible food (I can only assume Britain's empire building efforts were an effort to find better food). &lt;/p&gt;
&lt;p&gt;There was also some talk about Britain leaving the EU while we were there. There really isn't much I can add to the debate, apart from observing that it is not necessarily Far Right racists driving the push to leave the Union. I had several surprising conversations with highly educated, otherwise liberal people who pointed out that as long as the immigration quota is taken up by low-skilled immigrants from less well-off parts of Europe, it makes it very difficult to make room for the more desirable (read: educated, skilled) kind of immigrant. Fair enough: self determination is a big part of being a democracy. &lt;/p&gt;
&lt;p&gt;That said, Britain is (and has been for most of the last six hundred years) a trading nation, and leaving the EU would impose horrendous losses upon their society. I am reminded of Captain van Hoek's harrangue to his crew before they sailed from Manilla to Acapulco in Neal Stephenson's &lt;a href="https://www.amazon.com/Confusion-Baroque-Cycle-Vol/dp/0060733357/ref=sr_1_1?ie=UTF8&amp;amp;qid=1466613202&amp;amp;sr=8-1&amp;amp;keywords=the+confusion"&gt;&lt;em&gt;The Confusion&lt;/em&gt;&lt;/a&gt;: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Of all the enterprises to which a man can devote his energies [...] long-distance trade is the most profitable. It is what every Jew, Puritan, Dutchman, Huguenot, Armenian, and Banyan aspires to it is what built the Navies and palaces of Europe, the Court of the Great Mogul in Shahjahanabad, and many other prodigies besides. And yet in the world of trade, it is common knowledge that no circuit, not the slave trade of the Caribbean, not the spice trade of the Indies, exceeds the Manila-to-Acapulco run in sheer profit. The wealthiest Banyans in Surat and bankers in Genoa lay their perfumed heads on silken pillows at night, and dream of sending a few bales of cargo across the Pacific on the Manila Galleon. Even with all the dangers, and the swingeing duty that must be shelled out to the Viceroy, the profits never fall below four hundred percent. That city is founded upon such dreams, Jack. We are all going to go there now."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead of bales of silk and baskets of spices, Britain now deals in credit default swaps and cloud computing. But the basic message remains the same: the city on the hill -- London at close, Great Britain at large -- is founded on trade. Will the British really turn their backs on such fabulous wealth to keep out a few people from Europe looking to partake in the fountains of wealth created by British trading acumen? &lt;/p&gt;
&lt;p&gt;I guess we will find out tomorrow. &lt;/p&gt;</content><category term="travel"/><category term="Brexit"/></entry><entry><title>Android SDK, Best Served Insecure</title><link href="https://www.anserinae.net/android-sdk-best-served-insecure.html" rel="alternate"/><published>2016-02-25T17:30:00-05:00</published><updated>2016-02-25T17:30:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2016-02-25:/android-sdk-best-served-insecure.html</id><summary type="html">&lt;p&gt;If Google can't do software distribution right, who can?&lt;/p&gt;</summary><content type="html">&lt;p&gt;The internet has spent several days excoriating Linux Mint for insecure software distribution practices. Fortunately, the potential damage is limited to the vanishingly small population of people who 1) use desktop Linux, 2) bothered to install Linux Mint, and 3) downloaded an ISO for Mint over a fairly small window of time.&lt;/p&gt;
&lt;p&gt;The rage seems out of proportion to the offense; it certainly did not merit a line item on the Yahoo! home page:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Really, Yahoo?" src="/images/yahoo-linux-mint.png"&gt;&lt;/p&gt;
&lt;p&gt;It's also not as if everyone else is doing any better. On a recent excursion to the Android developer site, I was confronted by this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Good job, Google" src="/images/google-insecure.png"&gt;&lt;/p&gt;
&lt;p&gt;Yes, Google (supposedly the best software shop in the world), developer of Android (the most popular operating system in the world) is serving executables over HTTP, validated by unsigned checksums &lt;em&gt;also&lt;/em&gt; served over HTTP.&lt;/p&gt;
&lt;p&gt;Honestly, if Google can't distribute sotware securely, what hope do the rest of us have?&lt;/p&gt;</content><category term="Technology"/><category term="android"/><category term="security"/><category term="technology"/></entry><entry><title>Disputing UK Air Passenger Duty Charges</title><link href="https://www.anserinae.net/disputing-uk-air-passenger-duty-charges.html" rel="alternate"/><published>2016-02-15T06:15:00-05:00</published><updated>2016-02-15T06:15:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2016-02-15:/disputing-uk-air-passenger-duty-charges.html</id><summary type="html">&lt;p&gt;Travel taxes suck, but sometimes you can get around them&lt;/p&gt;</summary><content type="html">&lt;p&gt;This summer, the Choudhury household is summering in Spain, and I just used a good chunk of our American Airlines mile stash to book the &lt;code&gt;LHR-SJU&lt;/code&gt; portion of our &lt;code&gt;GRX-LHR-SJU-LAX&lt;/code&gt; itinerary. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Total cost:&lt;/strong&gt; 135000 AAdvantage miles and $1532 in taxes and fees. &lt;/p&gt;
&lt;p&gt;As airline awards go, this isn't a great redemption: it worked out to 1.9 cents per mile at prevailing economy fares. The flight is direct though, and with the arrival of our child, my wife and I have softened our stance on never paying BA's notorious fees and taxes. &lt;/p&gt;
&lt;p&gt;That doesn't mean that we just ponied up the cash though. A careful look at the final receipt revealed that a huge chunk of the bill was made up of the &lt;a href="https://www.gov.uk/government/publications/excise-notice-550-air-passenger-duty/excise-notice-550-air-passenger-duty#connected-flights"&gt;Air Passenger Duty&lt;/a&gt;: a distance based tax charged on all departures from UK airports, magnified by the class of service flown.&lt;/p&gt;
&lt;p&gt;On our itinerary, the APD came to a whopping $211 for each of our three tickets. Yikes.  &lt;/p&gt;
&lt;p&gt;Going back to the HRMC document: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Where the second of 2 flights (flight B) is an international flight (that is from the UK to an international destination), flight B is treated as connected if its booked time of departure falls within 24 hours of the scheduled time of arrival of the first flight (flight A). &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This describes our situation perfectly: our &lt;code&gt;GRX-LHR&lt;/code&gt; leg on Iberia (flight A) arrives in London at 6PM, 21 hours before my 3.25PM &lt;code&gt;LHR-SJU&lt;/code&gt; leg (flight B) departs the next day. Legally, we had standing to ask for the removal of the APD from our ticket; the question now was whether AA's rate desk would concur. &lt;/p&gt;
&lt;p&gt;After giving a very helpful American Airlines agent my Iberia ticket numbers starting in &lt;code&gt;075-&lt;/code&gt;, I asked if she couldn't get her rate desk to waive the APD on the award tickets. It took half an hour on hold, but the refund eventually came through. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Total savings:&lt;/strong&gt; $633. &lt;/p&gt;
&lt;p&gt;The redemption still isn't spectacular, but $633 is nothing to sneeze at, especially since all it took was the perusal of some very nicely formatted government documents and half an hour on the phone. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;TLDR;&lt;/em&gt; Don't ever be afraid to call in to get a better deal. &lt;/p&gt;</content><category term="travel"/><category term="awardtravel"/><category term="churning"/></entry><entry><title>Test Post, Please Ignore</title><link href="https://www.anserinae.net/test-post-please-ignore.html" rel="alternate"/><published>2016-01-08T10:20:00-05:00</published><updated>2016-01-08T10:20:00-05:00</updated><author><name>Kamil</name></author><id>tag:www.anserinae.net,2016-01-08:/test-post-please-ignore.html</id><summary type="html">&lt;p&gt;Oh yeah, we're back&lt;/p&gt;</summary><content type="html">&lt;p&gt;Well, it looks like we're back in the blogging business. Let's see if it sticks this time. &lt;/p&gt;</content><category term="Admin"/><category term="circlejerk"/></entry></feed>