<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Malte Krupa</title>
<link>https://nafn.de/</link>
<description>Malte Krupa</description>
<atom:link href="https://nafn.de/feed.xml" rel="self" type="application/rss+xml"/>
<item>
<title>Nerves on AWS</title>
<link>https://nafn.de/blog/nerves-on-aws</link>
<guid isPermaLink="true">https://nafn.de/blog/nerves-on-aws</guid>
<pubDate>Mon, 29 Sep 2025 00:00:00 +0000</pubDate>
<description>Notes and experiments on running Nerves-based Elixir systems on AWS infrastructure.</description>
<content:encoded><![CDATA[<p>Assuming you know what <a href="https://nerves-project.org/">Nerves</a> is and how to
change the configuration and the underlaying linux kernel, we just get
started.</p>
<h2>Prepare disk image</h2>
<p>As a base, we'll use the <a href="https://github.com/nerves-project/nerves_system_x86_64">x86_64</a>
system configuration.</p>
<p>Create a new project, make sure to set <code>MIX_TARGET=x86_64</code> and also make sure
the following drivers are built into the linux kernel. Check the
<a href="https://hexdocs.pm/nerves/customizing-systems.html#buildroot-package-configuration">documentation</a>
for details on how to build a custom kernel.</p>
<ul>
<li><code>CONFIG_NVME_CORE</code></li>
<li><code>CONFIG_BLK_DEV_NVME</code></li>
<li><code>CONFIG_PCI_MSI</code></li>
<li><code>CONFIG_ENA_ETHERNET</code></li>
</ul>
<p>This will make storage (NVME) and networking (Elastic Network Adapters) work in
AWS.</p>
<p>Then do <a href="https://github.com/nerves-project/nerves_system_x86_64?tab=readme-ov-file#running-in-qemu">the usual dance</a> to build an <code>img</code> file:</p>
<pre><code class="makeup bash"><span class="kd">export</span><span class="w"> </span><span class="nv">MIX_TARGET</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">x86_64</span><span class="p">&quot;</span><span class="w">
</span><span class="nf">mix</span><span class="n"> deps.get</span><span class="w">
</span><span class="nf">mix</span><span class="n"> firmware</span><span class="w">
</span><span class="nf">qemu-img</span><span class="n"> create</span><span class="nv"> -</span><span class="nv">f</span><span class="n"> raw disk.img 1G</span><span class="w">
</span><span class="nf">fwup</span><span class="nv"> -</span><span class="nv">d</span><span class="n"> disk.img _build/x86_64_dev/nerves/images/</span><span class="o">&lt;</span><span class="n">your_project</span><span class="o">&gt;</span><span class="n">.fw</span><span class="w">
</span></code></pre>
<h2>Create amazon machine image (AMI)</h2>
<p>We need to do the following things:</p>
<ul>
<li>Create a S3 bucket (or use one that already exists)</li>
<li>Upload the disk image to the bucket</li>
<li>Create a role <code>vmimport</code> for the <code>import-snapshot</code> job</li>
<li>Create an EBS snapshot from the disk image</li>
<li>Create an AMI from the EBS snapshot</li>
</ul>
<p>Once everything is done you can use the AMI to create new VMs running nerves.</p>
<h3>Preparation</h3>
<p>Export bucket name and AWS region</p>
<pre><code class="makeup bash"><span class="kd">export</span><span class="w"> </span><span class="nv">BUCKET_NAME</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">&lt;bucket-name&gt;</span><span class="p">&quot;</span><span class="w">
</span><span class="kd">export</span><span class="w"> </span><span class="nv">AWS_REGION</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">&lt;region&gt;</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<h3>Create a s3 bucket</h3>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> s3 mb s3://</span><span class="nv">$</span><span class="nv">BUCKET_NAME</span><span class="w">
</span></code></pre>
<h3>Upload disk image to s3 bucket</h3>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> s3 cp disk.img s3://</span><span class="nv">$</span><span class="nv">BUCKET_NAME</span><span class="w">
</span></code></pre>
<h3>Create vmimport role</h3>
<p>We need to create a role which has the required permissions to import a disk
image as a snapshot or image. By default the <code>aws ec2 import-snapshot</code> and <code>aws ec2 import-image</code> commands use a role called <code>vmimport</code>. You can change the name
on both commands using the <code>--role-name</code> parameter.</p>
<p>For this guide we stick with the name <code>vmimport</code>.</p>
<p>First we create a role with this trust policy which you should save to a file
called <code>trust-policy.json</code>:</p>
<pre><code class="makeup json"><span class="p">{</span><span class="w">
</span><span class="w">  </span><span class="p">&quot;</span><span class="s2">Version</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">2012-10-17</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">  </span><span class="p">&quot;</span><span class="s2">Statement</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">    </span><span class="p">{</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Effect</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">Allow</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Principal</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">Service</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">vmie.amazonaws.com</span><span class="p">&quot;</span><span class="w">
</span><span class="w">      </span><span class="p">}</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Action</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">sts:AssumeRole</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Condition</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">StringEquals</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">          </span><span class="p">&quot;</span><span class="s2">sts:Externalid</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">vmimport</span><span class="p">&quot;</span><span class="w">
</span><span class="w">        </span><span class="p">}</span><span class="w">
</span><span class="w">      </span><span class="p">}</span><span class="w">
</span><span class="w">    </span><span class="p">}</span><span class="w">
</span><span class="w">  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Then create a role using this policy:</p>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> iam create-role</span><span class="nv"> --</span><span class="nv">role-name</span><span class="n"> vmimport</span><span class="nv"> --</span><span class="nv">assume-role-policy-document</span><span class="n"> file://trust-policy.json</span><span class="w">
</span></code></pre>
<p>Now we need to allow this role to do certain things. Save the following JSON to
a file called <code>role-policy.json</code>. Make sure to replace <code>YOUR_BUCKET_NAME</code> with
the name of your bucket.</p>
<pre><code class="makeup json"><span class="p">{</span><span class="w">
</span><span class="w">  </span><span class="p">&quot;</span><span class="s2">Version</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">2012-10-17</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">  </span><span class="p">&quot;</span><span class="s2">Statement</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">    </span><span class="p">{</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Effect</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">Allow</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Action</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">s3:GetBucketLocation</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">s3:GetObject</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">s3:ListBucket</span><span class="p">&quot;</span><span class="w">
</span><span class="w">      </span><span class="p">]</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Resource</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">arn:aws:s3:::YOUR_BUCKET_NAME</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">arn:aws:s3:::YOUR_BUCKET_NAME/*</span><span class="p">&quot;</span><span class="w">
</span><span class="w">      </span><span class="p">]</span><span class="w">
</span><span class="w">    </span><span class="p">}</span><span class="p">,</span><span class="w">
</span><span class="w">    </span><span class="p">{</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Effect</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">Allow</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Action</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">s3:GetBucketLocation</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">s3:GetObject</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">s3:ListBucket</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">s3:PutObject</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">s3:GetBucketAcl</span><span class="p">&quot;</span><span class="w">
</span><span class="w">      </span><span class="p">]</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Resource</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">arn:aws:s3:::YOUR_BUCKET_NAME</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">arn:aws:s3:::YOUR_BUCKET_NAME/*</span><span class="p">&quot;</span><span class="w">
</span><span class="w">      </span><span class="p">]</span><span class="w">
</span><span class="w">    </span><span class="p">}</span><span class="p">,</span><span class="w">
</span><span class="w">    </span><span class="p">{</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Effect</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">Allow</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Action</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">ec2:ModifySnapshotAttribute</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">ec2:CopySnapshot</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">ec2:RegisterImage</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">ec2:Describe*</span><span class="p">&quot;</span><span class="w">
</span><span class="w">      </span><span class="p">]</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">Resource</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">*</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="p">}</span><span class="w">
</span><span class="w">  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>And finally we assign the policy to the role we already created.</p>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> iam put-role-policy</span><span class="nv"> --</span><span class="nv">role-name</span><span class="n"> vmimport</span><span class="nv"> --</span><span class="nv">policy-name</span><span class="n"> vmimport</span><span class="nv"> --</span><span class="nv">policy-document</span><span class="n"> file://role-policy.json</span><span class="w">
</span></code></pre>
<h3>Create an EBS snapshot from the disk image</h3>
<p>This command will import a snapshot from the <code>disk.img</code> file. The <code>vmimport</code>
role will be used by default as <a href="https://docs.aws.amazon.com/cli/latest/reference/ec2/import-snapshot.html">shown in the documentation</a>.</p>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> ec2 import-snapshot </span><span class="p">\
</span><span class="nv">  --</span><span class="nv">description</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">nerves-</span><span class="nv">$</span><span class="p">(</span><span class="nf">date</span><span class="s2"> +</span><span class="p">&quot;</span><span class="s2">%Y%m%d%H%M%S</span><span class="p">&quot;</span><span class="p">)</span><span class="w"> </span><span class="esc">\
</span><span class="s2">  --disk-container &#39;{
</span><span class="w">    </span><span class="p">&quot;</span><span class="n">Format</span><span class="p">&quot;</span><span class="s2">: </span><span class="p">&quot;</span><span class="n">RAW</span><span class="p">&quot;</span><span class="s2">,
</span><span class="w">    </span><span class="p">&quot;</span><span class="n">UserBucket</span><span class="p">&quot;</span><span class="s2">: {
</span><span class="w">      </span><span class="p">&quot;</span><span class="n">S3Bucket</span><span class="p">&quot;</span><span class="s2">: </span><span class="p">&quot;</span><span class="p">&#39;</span><span class="s1">${BUCKET_NAME}</span><span class="p">&#39;</span><span class="p">&quot;</span><span class="s2">,
</span><span class="w">      </span><span class="p">&quot;</span><span class="n">S3Key</span><span class="p">&quot;</span><span class="s2">: </span><span class="p">&quot;</span><span class="n">disk.img</span><span class="p">&quot;</span><span class="w">
</span><span class="s2">    }
</span><span class="s2">  }&#39;
</span></code></pre>
<p>Check the status of the import via this command:</p>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> ec2 describe-import-snapshot-tasks</span><span class="w">
</span></code></pre>
<p>The output might look like this:</p>
<pre><code class="makeup json"><span class="p">{</span><span class="w">
</span><span class="w">    </span><span class="p">&quot;</span><span class="s2">ImportSnapshotTasks</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">        </span><span class="p">{</span><span class="w">
</span><span class="w">            </span><span class="p">&quot;</span><span class="s2">Description</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">nerves-20250929164312</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">            </span><span class="p">&quot;</span><span class="s2">ImportTaskId</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">import-snap-bea76eae3f6140f2t</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">            </span><span class="p">&quot;</span><span class="s2">SnapshotTaskDetail</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">                </span><span class="p">&quot;</span><span class="s2">DiskImageSize</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">1073741824</span><span class="mf">.</span><span class="mf">0</span><span class="p">,</span><span class="w">
</span><span class="w">                </span><span class="p">&quot;</span><span class="s2">Format</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">RAW</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">                </span><span class="p">&quot;</span><span class="s2">Progress</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">19</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">                </span><span class="p">&quot;</span><span class="s2">SnapshotId</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">                </span><span class="p">&quot;</span><span class="s2">Status</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">active</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">                </span><span class="p">&quot;</span><span class="s2">StatusMessage</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">downloading/converting</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">                </span><span class="p">&quot;</span><span class="s2">UserBucket</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">                    </span><span class="p">&quot;</span><span class="s2">S3Bucket</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">mnesia-test</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">                    </span><span class="p">&quot;</span><span class="s2">S3Key</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">disk.img</span><span class="p">&quot;</span><span class="w">
</span><span class="w">                </span><span class="p">}</span><span class="w">
</span><span class="w">            </span><span class="p">}</span><span class="p">,</span><span class="w">
</span><span class="w">            </span><span class="p">&quot;</span><span class="s2">Tags</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="p">]</span><span class="w">
</span><span class="w">        </span><span class="p">}</span><span class="w">
</span><span class="w">    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Wait until the <code>Status</code> field shows <code>completed</code> and take note of the
<code>SnapshotId</code> value which will appear once the job completed.</p>
<pre><code class="makeup bash"><span class="kd">export</span><span class="w"> </span><span class="nv">SNAPSHOT_ID</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">snap-0538c2e855cb369b0</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<h3>Create an AMI from the EBS snapshot</h3>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> ec2 register-image </span><span class="p">\
</span><span class="nv">    --</span><span class="nv">name</span><span class="n"> nerves-</span><span class="nv">$</span><span class="p">(</span><span class="nf">date</span><span class="n"> +</span><span class="p">&quot;</span><span class="s2">%Y%m%d%H%M%S</span><span class="p">&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">\
</span><span class="nv">    --</span><span class="nv">root-device-name</span><span class="n"> /dev/sda1 </span><span class="p">\
</span><span class="nv">    --</span><span class="nv">block-device-mappings</span><span class="n"> DeviceName=/dev/sda1,Ebs=</span><span class="p">{</span><span class="n">SnapshotId=</span><span class="nv">$</span><span class="p">{</span><span class="nv">SNAPSHOT_ID</span><span class="p">}</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Now you'll have an AMI from which you can boot nerves on a new EC2 instance.</p>
]]></content:encoded>
<category>nerves</category>
<category>elixir</category>
<category>aws</category>
<category>iot</category>
</item>
<item>
<title>Fix slow default index after ogr2ogr import</title>
<link>https://nafn.de/blog/fix-slow-default-index-after-ogr2ogr-import</link>
<guid isPermaLink="true">https://nafn.de/blog/fix-slow-default-index-after-ogr2ogr-import</guid>
<pubDate>Wed, 20 Mar 2024 00:00:00 +0000</pubDate>
<description>Fixing a slow default index after ogr2ogr imports in PostgreSQL/PostGIS datasets.</description>
<content:encoded><![CDATA[<h2>Background</h2>
<p>The other day I created a basic <a href="https://github.com/maltekrupa/phoenix-protomaps-example">OpenStreetMap related example using the phoenix
framework</a>. This
example uses <a href="protomaps.com">protomaps</a> and <a href="https://maplibre.org/">maplibre-gl</a>
to draw maps in the browser without any external resources.</p>
<p>The example app also uses a small static GeoJSON file to draw some streets which
I downloaded via <a href="https://overpass-turbo.eu/">overpass-turbo</a>. This works well
for small datasets.</p>
<p>Short video: <a href="https://youtu.be/AnjkZntIxW0">https://youtu.be/AnjkZntIxW0</a></p>
<p>But what if I wanted to draw the streets of a complete country instead of just
the city center of Frankfurt? I assume the GeoJSON file would probably be very
big and create slow results.</p>
<p>Instead of a static file I wanted to give <a href="https://postgis.net/">PostGIS</a> a try
to only return the streets in an area around a specific point.</p>
<h2>Setup</h2>
<p>Create a Postgres database and enable PostGIS:</p>
<pre><code class="makeup sql"><span class="k">CREATE</span><span class="w"> </span><span class="k">DATABASE</span><span class="w"> </span><span class="nf">streets</span><span class="n">;
</span><span class="n">CREATE EXTENSION IF NOT </span><span class="o">EXISTS</span><span class="n"> postgis;
</span></code></pre>
<p>Now we need data to import. I downloaded a slice of the world from
<a href="https://download.geofabrik.de/">Geofabrik</a>.</p>
<p>There are many ways to import the data into the database. I choose
<a href="https://gdal.org/programs/ogr2ogr.html"><code>ogr2ogr</code></a> for no particular reason.</p>
<pre><code class="makeup shell"><span class="nf">ogr2ogr</span><span class="nv"> -</span><span class="nv">f</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">PostgreSQL</span><span class="p">&quot;</span><span class="n"> PG:</span><span class="p">&quot;</span><span class="s2">dbname=streets user=postgres</span><span class="p">&quot;</span><span class="nv"> -</span><span class="nv">sql</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">SELECT osm_id FROM lines WHERE highway IN (&#39;motorway&#39;,&#39;trunk&#39;,&#39;primary&#39;,&#39;secondary&#39;,&#39;residential&#39;,&#39;living_street&#39;,&#39;tertiary&#39;)</span><span class="p">&quot;</span><span class="nv"> -</span><span class="nv">nln</span><span class="w"> </span><span class="o">&lt;</span><span class="n">table name</span><span class="o">&gt;</span><span class="w"> </span><span class="o">&lt;</span><span class="n">filename</span><span class="o">&gt;</span><span class="w">
</span></code></pre>
<p>Replace <code>&lt;table name&gt;</code> with the table name you want to use and
<code>&lt;filename&gt;</code> with the name of the <code>pbf</code> file you just downloaded.</p>
<p>Once the command has finished you should have a rather boring table containing
three columns.</p>
<ul>
<li>ogc_fid: A PostGIS internal ID</li>
<li>osm_id: The corresponding OpenStreetMap ID</li>
<li>wkb_geometry: The streets we want in <a href="https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry#Well-known_binary"><code>WKB</code></a> representation</li>
</ul>
<h2>The problem</h2>
<p>The import with <code>ogr2ogr2</code> creates an index for the geometries that looks
something like this:</p>
<pre><code class="makeup sql"><span class="n">streets</span><span class="o">=</span><span class="c1">#</span><span class="c1"> \d index_test
</span><span class="n">                                           Table </span><span class="p">&quot;</span><span class="s2">public.index_test</span><span class="p">&quot;</span><span class="w">
</span><span class="n">    Column    |           Type            | Collation | Nullable |
</span><span class="kd">Default</span><span class="w">
</span><span class="c1">--</span><span class="c1">------------+---------------------------+-----------+----------+---------------------------------------------
</span><span class="n"> ogc_fid      | </span><span class="kt">integer</span><span class="n">                   |           | </span><span class="o">not</span><span class="w"> </span><span class="kc">null</span><span class="n"> | nextval(</span><span class="p">&#39;</span><span class="s1">index_test_ogc_fid_seq</span><span class="p">&#39;</span><span class="n">::regclass)
</span><span class="n"> osm_id       | character varying         |           |          |
</span><span class="n"> wkb_geometry | geometry(LineString,</span><span class="m">4326</span><span class="n">) |           |          |
</span><span class="n">Indexes:
</span><span class="w">    </span><span class="p">&quot;</span><span class="s2">index_test_pkey</span><span class="p">&quot;</span><span class="w"> </span><span class="kd">PRIMARY KEY</span><span class="n">, btree (ogc_fid)
</span><span class="w">    </span><span class="p">&quot;</span><span class="s2">index_test_wkb_geometry_geom_idx</span><span class="p">&quot;</span><span class="n"> gist (wkb_geometry)
</span></code></pre>
<p>Running queries with this index results in poor performance, because it looks
like the index is just ignored.</p>
<pre><code class="makeup sql"><span class="n">streets_dev</span><span class="o">=</span><span class="c1">#</span><span class="c1"> SELECT osm_id FROM index_test WHERE ST_DWithin(wkb_geometry,
</span><span class="p">&#39;</span><span class="s1">POINT(8.68417 50.11552)</span><span class="p">&#39;</span><span class="n">::geography, </span><span class="m">100</span><span class="n">) </span><span class="k">ORDER BY</span><span class="n"> wkb_geometry </span><span class="o">&lt;</span><span class="o">-</span><span class="o">&gt;</span><span class="w">
</span><span class="p">&#39;</span><span class="s1">POINT(8.68417 50.11552)</span><span class="p">&#39;</span><span class="n">::geography;
</span><span class="w">
</span><span class="n">  osm_id
</span><span class="c1">--</span><span class="c1">---------
</span><span class="w"> </span><span class="m">5069357</span><span class="w">
</span><span class="w"> </span><span class="m">4834447</span><span class="w">
</span><span class="w"> </span><span class="m">686648950</span><span class="w">
</span><span class="n">(</span><span class="m">3</span><span class="n"> rows)
</span><span class="w">
</span><span class="kt">Time</span><span class="n">: </span><span class="m">417</span><span class="n">.</span><span class="m">628</span><span class="n"> ms
</span></code></pre>
<p>Adding <code>explain (analyze true, verbose true, buffers true)</code> in front of the
query shows a rather long query plan which does not involve the index.</p>
<h2>The solution</h2>
<p>Luckily an <a href="https://stackoverflow.com/a/67475428/2376817">answer on
StackOverflow</a> explains very well
what the issue is (I replaced the column names to fit my example):</p>
<blockquote>
<p>The problem is that you are mixing geometry and geography, and PostgreSQL casts
wkb_geometry to geography so that they match.</p>
</blockquote>
<blockquote>
<p>Now you have indexed wkb_geometry, but not wkb_geometry::geography, which is
something different.</p>
</blockquote>
<blockquote>
<p>Either use 'SRID=4326;POINT(-0.145872 51.509691)'::geometry as second operand or
create the GiST index on ((geom_bounding_box::geography)) (note the double
parentheses).</p>
</blockquote>
<p>After dropping the old index and creating a new one, the query look much better.</p>
<pre><code class="makeup sql"><span class="n">streets</span><span class="o">=</span><span class="c1">#</span><span class="c1"> DROP INDEX index_test_wkb_geometry_geom_idx;
</span><span class="k">DROP</span><span class="w"> </span><span class="k">INDEX</span><span class="w">
</span><span class="kt">Time</span><span class="n">: </span><span class="m">6</span><span class="n">.</span><span class="m">262</span><span class="n"> ms
</span><span class="w">
</span><span class="n">streets</span><span class="o">=</span><span class="c1">#</span><span class="c1"> CREATE INDEX index_test_wkb_geometry_geom_idx ON index_test USING
</span><span class="n">GIST ((wkb_geometry::geography));
</span><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w">
</span><span class="kt">Time</span><span class="n">: </span><span class="m">1154</span><span class="n">.</span><span class="m">404</span><span class="n"> ms (</span><span class="m">00</span><span class="n">:</span><span class="m">01</span><span class="n">.</span><span class="m">154</span><span class="n">)
</span><span class="w">
</span><span class="n">streets</span><span class="o">=</span><span class="c1">#</span><span class="c1"> SELECT osm_id FROM index_test WHERE ST_DWithin(wkb_geometry,
</span><span class="p">&#39;</span><span class="s1">POINT(8.68417 50.11552)</span><span class="p">&#39;</span><span class="n">::geography, </span><span class="m">100</span><span class="n">) </span><span class="k">ORDER BY</span><span class="n"> wkb_geometry </span><span class="o">&lt;</span><span class="o">-</span><span class="o">&gt;</span><span class="w">
</span><span class="p">&#39;</span><span class="s1">POINT(8.68417 50.11552)</span><span class="p">&#39;</span><span class="n">::geography;
</span><span class="n">  osm_id
</span><span class="c1">--</span><span class="c1">---------
</span><span class="w"> </span><span class="m">5069357</span><span class="w">
</span><span class="w"> </span><span class="m">4834447</span><span class="w">
</span><span class="w"> </span><span class="m">686648950</span><span class="w">
</span><span class="n">(</span><span class="m">3</span><span class="n"> rows)
</span><span class="w">
</span><span class="kt">Time</span><span class="n">: </span><span class="m">1</span><span class="n">.</span><span class="m">509</span><span class="n"> ms
</span></code></pre>
<p>This reduced the query time from ~410ms to ~1.5ms. Yay.</p>
]]></content:encoded>
<category>postgresql</category>
<category>postgis</category>
<category>ogr2ogr</category>
<category>performance</category>
</item>
<item>
<title>Install old and deprecated ports on FreeBSD</title>
<link>https://nafn.de/blog/freebsd-install-old-and-deprecated-ports</link>
<guid isPermaLink="true">https://nafn.de/blog/freebsd-install-old-and-deprecated-ports</guid>
<pubDate>Thu, 01 Feb 2024 00:00:00 +0000</pubDate>
<description>How to install old or deprecated ports on FreeBSD when newer packages are unavailable.</description>
<content:encoded><![CDATA[<h3>The problem</h3>
<p>One of my clients runs an <strong>old</strong> database. How old? It's MySQL 5.7.</p>
<p>That is a problem because MySQL 5.7 has been <a href="https://endoflife.date/mysql">end of
life</a> since October 2023 and the FreeBSD port has
been <a href="https://cgit.freebsd.org/ports/commit/?id=e716c4e0ead482d5823056ddbb895d69bf3132da">removed on
2023-12-31</a>.</p>
<p>But we need a test system. Something that looks and feels like the production
system. And we need to find a way to install MySQL 5.7.</p>
<h3>The solution</h3>
<p>Turns out you can download an old version of the ports tree and start there.
There is no guarantee it will work but it is a start.</p>
<p>The FreeBSD ports are hosted on multiple platforms. You can choose
whatever you like most.</p>
<ul>
<li><a href="https://cgit.freebsd.org/ports/">cgit</a></li>
<li><a href="https://codeberg.org/FreeBSD/freebsd-ports">Codeberg</a></li>
<li><a href="https://gitlab.com/FreeBSD/freebsd-ports">GitLab</a></li>
<li><a href="https://github.com/FreeBSD/freebsd-ports">GitHub</a></li>
</ul>
<p>The name of the port I'm using in this example is <code>mysql57-server</code>.</p>
<ol>
<li>Search for your port on
<a href="https://www.freshports.org/databases/mysql57-server">freshports</a></li>
<li>Open the last commit which removed the port. It's probably the first entry on
freshports.
(<a href="https://cgit.freebsd.org/ports/commit/?id=e716c4e0ead482d5823056ddbb895d69bf3132da">cgit</a>,
<a href="https://codeberg.org/FreeBSD/freebsd-ports/commit/e716c4e0ead482d5823056ddbb895d69bf3132da">Codeberg</a>,
<a href="https://gitlab.com/FreeBSD/freebsd-ports/commit/e716c4e0ead482d5823056ddbb895d69bf3132da">GitLab</a>,
<a href="https://github.com/FreeBSD/freebsd-ports/commit/e716c4e0ead482d5823056ddbb895d69bf3132da">GitHub</a>)</li>
<li>Go to parent commit where the port has not been removed yet
(<a href="https://cgit.freebsd.org/ports/commit/?id=d904ca2a2c1bd339d0a75daddf5c29976e8a4790">cgit</a>,
<a href="https://codeberg.org/FreeBSD/freebsd-ports/commit/d904ca2a2c1bd339d0a75daddf5c29976e8a4790">Codeberg</a>,
<a href="https://gitlab.com/FreeBSD/freebsd-ports/-/commit/d904ca2a2c1bd339d0a75daddf5c29976e8a4790">GitLab</a>,
<a href="https://github.com/freebsd/freebsd-ports/commit/d904ca2a2c1bd339d0a75daddf5c29976e8a4790">GitHub</a>)</li>
<li>On cgit you can now download a <code>.tar.gz</code> or <code>.zip</code> of the parent commit. On
the other platforms, you need to click on &quot;Browser files&quot; or &quot;Browse source&quot;
before you can download the source.</li>
<li>Extract the archive</li>
<li>Move to the right directory: <code>cd databases/mysql57-server</code></li>
<li>Compile and install it: <code>ALLOW_UNSUPPORTED_SYSTEM=&quot;YES&quot;; BATCH=&quot;YES&quot;; make install clean</code></li>
</ol>
<p><code>ALLOW_UNSUPPORTED_SYSTEM</code> let's you install the port on a different system than
it was planned to be installed on.</p>
<p><code>BATCH</code> uses the default values for every prompt that might come up while
installing a port.</p>
<p>Now you just need to wait for the port and all of its dependencies to be
compiled.</p>
<hr />
<p>Update from 2024-04-23:</p>
<p>I was made aware about a couple of fixes to make <code>mysql57-server</code> compile on 14.0:</p>
<ol>
<li>Replace <code>-DWITH_EDITLINE=system</code> with <code>-DWITH_EDITLINE=bundled</code> in <code>Makefile</code></li>
<li>Install openssl (<code>pkg install openssl</code>)</li>
<li>Set default to openssl in make.conf (<code>echo &quot;DEFAULT_VERSIONS+=ssl=openssl&quot; &gt;&gt; /etc/make.conf</code>)</li>
</ol>
<p>Thank you Chr. Schmidt!</p>
]]></content:encoded>
<category>freebsd</category>
<category>ports</category>
<category>package-management</category>
<category>legacy</category>
</item>
<item>
<title>How to scale a big SVG on a website</title>
<link>https://nafn.de/blog/how-to-scale-a-big-svg-on-a-website</link>
<guid isPermaLink="true">https://nafn.de/blog/how-to-scale-a-big-svg-on-a-website</guid>
<pubDate>Mon, 29 Jan 2024 00:00:00 +0000</pubDate>
<description>Techniques to scale very large SVG files on websites without breaking layout and performance.</description>
<content:encoded><![CDATA[<p>When I tried to add a rather big <a href="https://en.wikipedia.org/wiki/SVG">SVG</a> file
to a website, I had to find a way to scale it properly (to my current knowledge).</p>
<p>The SVG in question is a <a href="https://upload.wikimedia.org/wikipedia/commons/8/8e/BlankMap_World_simple.svg">map of the
world</a>.</p>
<p>When you open the link you can already see the problem that also happens when
you try to embed it into a website. It will be shown in its full size.</p>
<p>The file starts with the following <code>&lt;svg&gt;</code> tag:</p>
<pre><code class="makeup xml"><span class="p">&lt;</span><span class="nt">svg</span><span class="w"> </span><span class="na">xmlns</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">http://www.w3.org/2000/svg</span><span class="p">&quot;</span><span class="w"> </span><span class="na">height</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">1200</span><span class="p">&quot;</span><span class="w"> </span><span class="na">width</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">2370</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span></code></pre>
<p>The obvious change would be to alter <code>width</code> and <code>height</code> but this resulted
in the SVG only showing parts of the drawn graphic. It kind of zoomes into the
image.</p>
<p>Then I tried to transform the content with an additional element:</p>
<pre><code class="makeup xml"><span class="p">&lt;</span><span class="nt">g</span><span class="w"> </span><span class="na">transform</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">scale(0.8)</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span></code></pre>
<p>But this turned out to be a bit static. Viewing the website on a mobile device
did not change the size of the SVG at all. So it was too small for a desktop
size screen and too big for a mobile device screen.</p>
<p>The solution was to add a <code>div</code> element and some attributes for the SVG tag:</p>
<pre><code class="makeup html"><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">style</span><span class="p">=</span><span class="p">&quot;</span><span class="nb">max-width</span><span class="p">:</span><span class="mi">300</span><span class="mi">px</span><span class="p">;</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;</span><span class="nt">svg</span><span class="w"> </span><span class="na">xmlns</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">http://www.w3.org/2000/svg</span><span class="p">&quot;</span><span class="w"> </span><span class="na">width</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">100%</span><span class="p">&quot;</span><span class="w"> </span><span class="na">viewBox</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">0 0 2370 1200</span><span class="p">&quot;</span><span class="w"> </span><span class="na">preserveAspectRatio</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">xMinYMax meet</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="n">[...]
</span></code></pre>
<p>The initial width and height attributes are now part of a <code>viewBox</code> attribute.
The width is set to <code>100%</code> to make it as big as the parent <code>div</code> element which
has a <code>max-width</code> of <code>300px</code>. Finally, the <code>preserveAspectRatio=&quot;xMinYMax meet&quot;</code>
is something that I do not fully understand yet, but it was the solution to
align the SVG the the left side of the screen.</p>
<p>Sources:</p>
<ul>
<li><a href="https://stackoverflow.com/questions/19484707/how-can-i-make-an-svg-scale-with-its-parent-container">https://stackoverflow.com/questions/19484707/how-can-i-make-an-svg-scale-with-its-parent-container</a></li>
<li><a href="https://stackoverflow.com/questions/8639383/how-do-i-center-a-svg-in-a-div">https://stackoverflow.com/questions/8639383/how-do-i-center-a-svg-in-a-div</a></li>
</ul>
]]></content:encoded>
<category>svg</category>
<category>web</category>
<category>frontend</category>
<category>performance</category>
</item>
<item>
<title>img2webp for quick image conversion</title>
<link>https://nafn.de/blog/img2webp-for-quick-image-conversion</link>
<guid isPermaLink="true">https://nafn.de/blog/img2webp-for-quick-image-conversion</guid>
<pubDate>Fri, 26 Jan 2024 00:00:00 +0000</pubDate>
<description>Using img2webp to quickly convert and optimize images before publishing them online.</description>
<content:encoded><![CDATA[<p>Whenever I post an image online, I try to reduce the size by converting it to
<a href="https://en.wikipedia.org/wiki/WebP">webp</a>.</p>
<p>To speed up this process, I wrote a
<a href="https://en.wikipedia.org/wiki/One-liner_program">one-liner</a> using
<a href="https://en.wikipedia.org/wiki/FFmpeg">ffmpeg</a>:</p>
<pre><code class="makeup bash"><span class="nf">$</span><span class="n"> which img2webp</span><span class="w">
</span><span class="nf">img2webp</span><span class="w"> </span><span class="p">(</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">        </span><span class="nf">ffmpeg</span><span class="nv"> -</span><span class="nv">hide_banner</span><span class="nv"> -</span><span class="nv">loglevel</span><span class="n"> error</span><span class="nv"> -</span><span class="nv">i</span><span class="w"> </span><span class="nv">$</span><span class="nv">1</span><span class="nv"> -</span><span class="nv">c</span><span class="n">:v libwebp </span><span class="nv">$</span><span class="p">{</span><span class="nv">1</span><span class="o">/</span><span class="esc">\.</span><span class="o">*</span><span class="o">/</span><span class="esc">\.</span><span class="n">webp</span><span class="p">}</span><span class="w"> </span><span class="o">&gt;&gt;</span><span class="n"> /dev/null</span><span class="w">
</span><span class="w">        </span><span class="nf">ls</span><span class="nv"> -</span><span class="nv">lah</span><span class="w"> </span><span class="nv">$</span><span class="nv">1</span><span class="w">
</span><span class="w">        </span><span class="nf">ls</span><span class="nv"> -</span><span class="nv">lah</span><span class="w"> </span><span class="nv">$</span><span class="p">{</span><span class="nv">1</span><span class="o">/</span><span class="esc">\.</span><span class="o">*</span><span class="o">/</span><span class="esc">\.</span><span class="n">webp</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Let's see it in use:</p>
<pre><code class="makeup bash"><span class="nf">$</span><span class="n"> img2webp IMG_2228.jpeg</span><span class="w">
</span><span class="nf">-rw-r--r--@</span><span class="n"> 1 maltekrupa  staff   2.4M Jan 26 08:15 IMG_2228.jpeg</span><span class="w">
</span><span class="nf">-rw-r--r--</span><span class="n">  1 maltekrupa  staff   570K Jan 26 08:16 IMG_2228.webp</span><span class="w">
</span></code></pre>
<p>It usually takes less than a second on a three year old M1 Mac and since ffmpeg
is such an amazing tool, it works with basically any image format.</p>
]]></content:encoded>
<category>image-conversion</category>
<category>images</category>
<category>cli</category>
<category>tooling</category>
</item>
<item>
<title>Update patch level of FreeBSD jails</title>
<link>https://nafn.de/blog/freebsd-patch-updates-for-jails</link>
<guid isPermaLink="true">https://nafn.de/blog/freebsd-patch-updates-for-jails</guid>
<pubDate>Wed, 06 Dec 2023 00:00:00 +0000</pubDate>
<description>How to update FreeBSD patch levels for jails in a safe and repeatable way.</description>
<content:encoded><![CDATA[<p>In this example we have <code>14.0-RELEASE-p1</code> installed and want to update to
<code>14.0-RELEASE-p2</code>.</p>
<p>First, check the current version of every jail:</p>
<pre><code class="makeup shell"><span class="nf">$</span><span class="n"> for i in </span><span class="nv">$</span><span class="p">(</span><span class="nf">jls</span><span class="n"> name</span><span class="p">)</span><span class="o">;</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="nf">sudo</span><span class="n"> freebsd-version</span><span class="nv"> -</span><span class="nv">j</span><span class="w"> </span><span class="nv">$</span><span class="nv">i</span><span class="o">;</span><span class="w"> </span><span class="k">done</span><span class="w">
</span><span class="nf">[..]</span><span class="w">
</span><span class="nf">14.0-RELEASE-p1</span><span class="w">
</span><span class="nf">14.0-RELEASE-p1</span><span class="w">
</span><span class="nf">14.0-RELEASE-p1</span><span class="w">
</span></code></pre>
<p>Then, run an unattended update on all jails:</p>
<pre><code class="makeup shell"><span class="nf">$</span><span class="n"> for i in </span><span class="nv">$</span><span class="p">(</span><span class="nf">jls</span><span class="n"> name</span><span class="p">)</span><span class="o">;</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="nf">sudo</span><span class="n"> PAGER=</span><span class="p">&quot;</span><span class="s2">cat</span><span class="p">&quot;</span><span class="n"> freebsd-update</span><span class="nv"> -</span><span class="nv">j</span><span class="w"> </span><span class="nv">$</span><span class="nv">i</span><span class="n"> fetch install</span><span class="o">;</span><span class="w"> </span><span class="k">done</span><span class="w">
</span></code></pre>
<blockquote>
<p><code>PAGER</code> is usually set to <code>less</code> which results in a dialog you have to close
manually. With <code>cat</code> the output is just printed and everything continues without
interaction.</p>
</blockquote>
<p>Once the updates are done, restart all the jails.</p>
<blockquote>
<p>Keep in mind: This command will stop all jails in the order as they appear in
<code>/etc/jail.conf</code>. Once all of them are stopped, they are started again.</p>
</blockquote>
<pre><code class="makeup shell"><span class="nf">$</span><span class="n"> sudo service jail restart</span><span class="w">
</span></code></pre>
<p>Done.</p>
]]></content:encoded>
<category>freebsd</category>
<category>jails</category>
<category>patching</category>
<category>operations</category>
</item>
<item>
<title>Rotate PostgreSQL credentials</title>
<link>https://nafn.de/blog/rotate-postgresql-password</link>
<guid isPermaLink="true">https://nafn.de/blog/rotate-postgresql-password</guid>
<pubDate>Wed, 04 Oct 2023 00:00:00 +0000</pubDate>
<description>A practical way to rotate PostgreSQL credentials with minimal service disruption.</description>
<content:encoded><![CDATA[<p>Most guides on how to rotate database credentials create a parent role and
inherit permissions to two new roles.</p>
<p>To rotate the credentials they:</p>
<ol>
<li>enable the new role with a new password</li>
<li>switch all applications to the new credentials</li>
<li>disable the old role</li>
</ol>
<p>Enable/Disable means to allow or prevent the role from logging in.</p>
<p>I usually do the same ... but with a twist. Instead of having e.g. <code>role_a</code> and
<code>role_b</code> I'll create a new role whenever the credentials need to be rotated.
This new role will look something like this: <code>role_2023_10_04_09_00_00</code>.</p>
<p>The timestamp makes it very clear how long credentials have been in use and
it's a good reminder to rotate them very soon.</p>
<h2>Helper function</h2>
<p>For this I use a helper function called <code>create_rotating_role</code>. This function
takes three arguments:</p>
<ul>
<li><code>username</code>: An arbitrary username which is the prefix for the timestamp</li>
<li><code>passwd</code>: The password for the new user</li>
<li><code>parent</code>: The name of the parent role the new user should become part of</li>
</ul>
<pre><code class="makeup sql"><span class="k">CREATE OR REPLACE</span><span class="w"> </span><span class="k">FUNCTION</span><span class="w"> </span><span class="n">public</span><span class="p">.</span><span class="nf">create_rotating_role</span><span class="n">(
</span><span class="n">    username character varying,
</span><span class="n">    passwd character varying,
</span><span class="n">    parent character varying
</span><span class="n">)
</span><span class="n">RETURNS character varying
</span><span class="n">LANGUAGE plpgsql
</span><span class="o">AS</span><span class="n"> $function$
</span><span class="n">DECLARE
</span><span class="n">    username_complete character varying;
</span><span class="k">BEGIN</span><span class="w">
</span><span class="n">    username_complete </span><span class="o">=</span><span class="n"> username </span><span class="o">||</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">_</span><span class="p">&#39;</span><span class="w"> </span><span class="o">||</span><span class="n"> to_char(</span><span class="nf">current_timestamp</span><span class="n">, </span><span class="p">&#39;</span><span class="s1">YYYY_MM_DD_HH_MM_SS</span><span class="p">&#39;</span><span class="n">);
</span><span class="n">    execute format($$CREATE ROLE %I </span><span class="k">WITH</span><span class="n"> LOGIN PASSWORD </span><span class="p">&#39;</span><span class="s1">%s</span><span class="p">&#39;</span><span class="w"> </span><span class="o">IN</span><span class="n"> ROLE %s$$, username_complete, passwd, parent);
</span><span class="n">    execute format($$ALTER ROLE %I </span><span class="k">SET</span><span class="n"> ROLE %s$$, username_complete, parent);
</span><span class="n">    return username_complete;
</span><span class="k">END</span><span class="n">;
</span><span class="n">$function$;
</span></code></pre>
<p>The function does two things. It creates the new user and changes the default
role for said user to the role of the parent. Every role will therefore act as
the parent role. This is important because it allows all the roles to alter and
delete tables which have been created by previously created roles which might
not exist anymore.</p>
<blockquote>
<p>It's probably best to add the function in the <code>postgres</code> database. Otherwise
you'll need it in every database where you plan to use it (I think).</p>
</blockquote>
<h2>Workflow</h2>
<h3>Initial setup</h3>
<p>Let's assume we start from zero.</p>
<p>We'll create a new database and a new role which we grant the required permissions:</p>
<pre><code class="makeup sql"><span class="k">CREATE</span><span class="w"> </span><span class="k">DATABASE</span><span class="w"> </span><span class="nf">app_db</span><span class="n">;
</span><span class="n">CREATE ROLE app_role_parent </span><span class="k">WITH</span><span class="n"> NOLOGIN;
</span><span class="k">GRANT</span><span class="n"> ALL PRIVILEGES ON DATABASE app_db TO app_role_parent;
</span></code></pre>
<p>This new role is the parent and all future changes to permissions will be done
to it.</p>
<p>It's redundant but more explicit: We specified <code>WITH NOLOGIN</code> on the role, which
is the default for roles create via <code>CREATE ROLE</code> (compared to <code>CREATE USER</code>).
This role will not be used to login. That's what we create the other roles for.</p>
<h3>Repeating task</h3>
<p>With the new function in place we can create a new role by running:</p>
<pre><code class="makeup sql"><span class="k">SELECT</span><span class="n"> create_rotating_role(</span><span class="p">&#39;</span><span class="s1">app_role</span><span class="p">&#39;</span><span class="n">, </span><span class="o">&lt;</span><span class="n">password</span><span class="o">&gt;</span><span class="n">, </span><span class="p">&#39;</span><span class="s1">app_role_parent</span><span class="p">&#39;</span><span class="n">);
</span><span class="w">
</span><span class="n">     create_rotating_role
</span><span class="c1">--</span><span class="c1">----------------------------
</span><span class="n"> app_role_2023_10_04_12_34_56
</span><span class="n">(</span><span class="m">1</span><span class="n"> row)
</span></code></pre>
<p>The output of the function will contain the name of the newly created role.</p>
<p>Now you need to change the credentials in whatever app you're using and once
everything is migrated to the new role you can delete the old one.</p>
<pre><code class="makeup sql"><span class="n">DROP ROLE app_role_2023_09_03_01_23_45;
</span></code></pre>
<p>That's it.</p>
<h2>What I learned along the way</h2>
<p>Imagine working in IT for way too long and still learning the basics. :)</p>
<ul>
<li>Use <a href="https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS">Single
Quotes</a>
for strings</li>
<li>Use <a href="https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS">Double
Quotes</a>
for columns, tables or roles</li>
<li>Use <a href="https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING">Dollar
Quoting</a>
to make text in single quotes more readable</li>
<li><a href="https://www.postgresql.org/docs/current/sql-createfunction.html">How to write functions and declare
variables</a></li>
</ul>
<h2>Sources</h2>
<p>Other people posted about this <em>YEARS</em> ago (in order of how resourceful I think
their posts are):</p>
<ul>
<li><a href="http://davidhollenberger.com/2017/03/16/postgres-credential-rotation/">http://davidhollenberger.com/2017/03/16/postgres-credential-rotation/</a></li>
<li><a href="https://thnee.se/postgresql-rotate-password/">https://thnee.se/postgresql-rotate-password/</a></li>
<li><a href="https://www.jannikarndt.de/blog/2018/08/rotating_postgresql_passwords_with_no_downtime/">https://www.jannikarndt.de/blog/2018/08/rotating_postgresql_passwords_with_no_downtime/</a></li>
</ul>
]]></content:encoded>
<category>postgresql</category>
<category>security</category>
<category>database</category>
<category>operations</category>
</item>
<item>
<title>Project Euler 2</title>
<link>https://nafn.de/blog/project-euler-2</link>
<guid isPermaLink="true">https://nafn.de/blog/project-euler-2</guid>
<pubDate>Fri, 29 Sep 2023 00:00:00 +0000</pubDate>
<description>Solving Project Euler problem 2 in Elixir and discussing the resulting approach.</description>
<content:encoded><![CDATA[<p>The <a href="https://projecteuler.net/problem=2">second problem</a> is supposed to be on
the same level as the first one but I already struggled a bit.</p>
<p>I misread the task and confused &quot;terms in the Fibonacci sequence whose values do
not exceed four million&quot; with &quot;fourth million Fibonacci term&quot;.</p>
<p>Instead of calculating to the 33th term I calculated to the 4_000_000th term. :D</p>
<p>15 minutes later I got a rather big number on the screen and realised I did
something wrong.</p>
<hr />
<p>Initially I created a growing list containing all elements from 1 to <em>n</em> which
then was iterated a last time to calculate the sum of all even-valued terms.
But because of a failure in the exit condition the tests ran into a timeout.</p>
<p>But I didn't see that yet and believed the problem was the list with four
million entries, so I refactored the solution to only work with a two element
tuple and calculate the sum of the even-valued terms on every iteration.</p>
<p>Since this also resulted in a timeout, I checked the exit condition again and
found the real problem. And since I like the two-element-tuple solution more
than the initial one I kept it. :)</p>
<hr />
<p>A quick benchmark suggests that the version using lists is 1.78 times slower
than the ones using tuples.</p>
<pre><code class="makeup bash"><span class="nf">$</span><span class="n"> mix run benchmarks/problem_2.exs</span><span class="w">
</span><span class="nf">Operating</span><span class="n"> System: macOS</span><span class="w">
</span><span class="nf">CPU</span><span class="n"> Information: Apple M1</span><span class="w">
</span><span class="nf">Number</span><span class="n"> of Available Cores: 8</span><span class="w">
</span><span class="nf">Available</span><span class="n"> memory: 16 GB</span><span class="w">
</span><span class="nf">Elixir</span><span class="n"> 1.15.6</span><span class="w">
</span><span class="nf">Erlang</span><span class="n"> 26.0.2</span><span class="w">
</span><span class="w">
</span><span class="nf">Benchmark</span><span class="n"> suite executing with the following configuration:</span><span class="w">
</span><span class="nf">warmup:</span><span class="n"> 2 s</span><span class="w">
</span><span class="nf">time:</span><span class="n"> 5 s</span><span class="w">
</span><span class="nf">memory</span><span class="n"> time: 0 ns</span><span class="w">
</span><span class="nf">reduction</span><span class="n"> time: 0 ns</span><span class="w">
</span><span class="nf">parallel:</span><span class="n"> 1</span><span class="w">
</span><span class="nf">inputs:</span><span class="n"> none specified</span><span class="w">
</span><span class="nf">Estimated</span><span class="n"> total run time: 14 s</span><span class="w">
</span><span class="w">
</span><span class="nf">Benchmarking</span><span class="n"> list ...</span><span class="w">
</span><span class="nf">Benchmarking</span><span class="n"> tuple ...</span><span class="w">
</span><span class="w">
</span><span class="nf">Name</span><span class="n">            ips        average  deviation         median         99th </span><span class="nv">%</span><span class="w">
</span><span class="nf">tuple</span><span class="n">        5.06 M      197.69 ns ±16067.94</span><span class="nv">%</span><span class="n">         125 ns         250 ns</span><span class="w">
</span><span class="nf">list</span><span class="n">         2.84 M      351.74 ns  ±8009.82</span><span class="nv">%</span><span class="n">         292 ns         459 ns</span><span class="w">
</span><span class="w">
</span><span class="nf">Comparison:</span><span class="w">
</span><span class="nf">tuple</span><span class="n">        5.06 M</span><span class="w">
</span><span class="nf">list</span><span class="n">         2.84 M - 1.78x slower +154.05 ns</span><span class="w">
</span></code></pre>
]]></content:encoded>
<category>project-euler</category>
<category>math</category>
<category>elixir</category>
<category>algorithms</category>
</item>
<item>
<title>Fixed the RSS feed</title>
<link>https://nafn.de/blog/fixed-rss</link>
<guid isPermaLink="true">https://nafn.de/blog/fixed-rss</guid>
<pubDate>Sat, 23 Sep 2023 00:00:00 +0000</pubDate>
<description>A quick note on fixing the blog RSS feed after multiple site migrations.</description>
<content:encoded><![CDATA[<p>I migrated this website more times than I can remember. Here and there you can
still find old relicts of features or themes I used many years ago. The last
thing that broke was the RSS feed when I migrated to the
<a href="https://github.com/maltekrupa/hugo-pmfw">hugo-pmfw</a> theme.</p>
<p>This should now be fixed.</p>
<p>That being said, even after years of using hugo it still feels strange from time
to time because I'm not really a web developer nor am I planning to become one
(at least not on the frontend side). Still ... hugo made it possible for me to
at least be a bit happy about the web sites I build. &lt;3</p>
]]></content:encoded>
<category>rss</category>
<category>blog</category>
<category>phoenix</category>
<category>web</category>
</item>
<item>
<title>Project Euler 1</title>
<link>https://nafn.de/blog/project-euler-1</link>
<guid isPermaLink="true">https://nafn.de/blog/project-euler-1</guid>
<pubDate>Fri, 22 Sep 2023 00:00:00 +0000</pubDate>
<description>Solving Project Euler problem 1 in Elixir with a simple and readable approach.</description>
<content:encoded><![CDATA[<p>The <a href="https://projecteuler.net/problem=1">first problem</a> is a rather easy one.</p>
<blockquote>
<p>If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.</p>
</blockquote>
<p>My solution: <a href="https://github.com/maltekrupa/euler/blob/main/lib/problem_1.ex">https://github.com/maltekrupa/euler/blob/main/lib/problem_1.ex</a></p>
<p>I choose to solve this with some recursion which is making use of tail-call optimization.</p>
<p>My biggest &quot;issue&quot; was to remember the <code>Enum.to_list/1</code> function because my text
editor was only showing part of the Enum functions in the code completion. :)</p>
]]></content:encoded>
<category>project-euler</category>
<category>math</category>
<category>elixir</category>
<category>algorithms</category>
</item>
<item>
<title>Project Euler - Intro</title>
<link>https://nafn.de/blog/project-euler-intro</link>
<guid isPermaLink="true">https://nafn.de/blog/project-euler-intro</guid>
<pubDate>Thu, 21 Sep 2023 00:00:00 +0000</pubDate>
<description>A short introduction to Project Euler and the plan to solve selected problems with Elixir.</description>
<content:encoded><![CDATA[<p>Over the last one and a half years I did entertain myself with some elixir
exercises on <a href="https://exercism.org/profiles/computerfoo">exercism</a>.
Recently I realised I need a different kind of challenge and found
<a href="https://projecteuler.net/">projecteuler</a>.</p>
<p><img src="https://projecteuler.net/profile/maltekrupa.png" alt="My score on projecteuler.net" /></p>
<p>I'll track my work in a public git repository:
<a href="https://github.com/maltekrupa/euler">https://github.com/maltekrupa/euler</a></p>
<p>In this repository I created a new elixir application via <code>mix new euler</code> and
then prepared a <a href="https://github.com/maltekrupa/euler/blob/main/lib/mix/tasks/euler/new.ex">mix
task</a>
to create the needed files for every new problem and download the corresponding
problem description into the <code>@moduledoc</code> field of the new module.</p>
<p>I bet the problems get way too hard for me in a matter of days. Let's see.</p>
]]></content:encoded>
<category>project-euler</category>
<category>math</category>
<category>elixir</category>
<category>algorithms</category>
</item>
<item>
<title>FreeBSD bhyve VM on ZFS</title>
<link>https://nafn.de/blog/freebsd-bhyve-vm-on-zfs</link>
<guid isPermaLink="true">https://nafn.de/blog/freebsd-bhyve-vm-on-zfs</guid>
<pubDate>Sun, 06 Aug 2023 00:00:00 +0000</pubDate>
<description>Quick and easy setup to run a Linux VM on FreeBSD via bhyve on ZFS</description>
<content:encoded><![CDATA[<p>The default disk type when using
<a href="https://github.com/churchers/vm-bhyve">vm-bhyve</a> is a file on the filesystem. Changing
it to ZFS is quite easy.</p>
<h2>Setup</h2>
<p>Assuming the following configuration after installing vm-bhyve in <code>/etc/rc.conf</code>:</p>
<pre><code class="makeup bash"><span class="nv">vm_enable</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">YES</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">vm_dir</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">zfs:zroot/vms</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<p>And a vm-bhyve mountpoint set to <code>/usr/local/vms</code>.</p>
<h2>Howto</h2>
<p>First we create a new VM. In this guide we use the name <code>testvm</code>.</p>
<pre><code class="makeup bash"><span class="nf">vm</span><span class="n"> create</span><span class="nv"> -</span><span class="nv">t</span><span class="n"> alpine</span><span class="nv"> -</span><span class="nv">c</span><span class="n"> 1</span><span class="nv"> -</span><span class="nv">m</span><span class="n"> 2G testvm</span><span class="w">
</span></code></pre>
<p>Meaning: <br />
<code>-t alpine</code>: use the alpine template <br />
<code>-c 1</code>: one core <br />
<code>-m 2G</code>: 2GB of memory</p>
<hr />
<p>Delete the disk image file as we will not need it.</p>
<pre><code class="makeup bash"><span class="nf">rm</span><span class="n"> /usr/local/vms/testvm/disk0.img</span><span class="w">
</span></code></pre>
<hr />
<p>Create a ZFS dataset underneath the dataset used for the VM:</p>
<pre><code class="makeup bash"><span class="nf">zfs</span><span class="n"> create</span><span class="nv"> -</span><span class="nv">V20G</span><span class="nv"> -</span><span class="nv">o</span><span class="n"> volmode=dev zroot/testvm/disk0</span><span class="w">
</span></code></pre>
<p>Meaning: <br />
<code>-V20G</code>: Create dataset with a size of 20GB <br />
<code>disk0</code>: Name of the dataset which needs to be referenced in the configuration
file</p>
<hr />
<p>Alter the configuration in <code>/usr/local/vms/testvm/testvm.conf</code> to look like
this:</p>
<pre><code class="makeup bash"><span class="nv">loader</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">uefi</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">cpu</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">1</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">memory</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">2G</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">network0_type</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">virtio-net</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">network0_switch</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">public</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">disk0_type</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">nvme</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">disk0_dev</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">zvol</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">disk0_name</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">disk0</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<p>This is it. Now you can start installing an OS as you would normally do via <code>vm install testvm &lt;iso&gt;</code>.</p>
<hr />
<p>If you plan on installing your OS via a GUI, you might want to add the following
to you configuration to be able to connect via VNC to your VM (requires UEFI!):</p>
<pre><code class="makeup bash"><span class="nv">xhci_mouse</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">yes</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">graphics</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">yes</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">graphics_res</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">800x600</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
]]></content:encoded>
<category>freebsd</category>
<category>bhyve</category>
<category>zfs</category>
<category>virtualization</category>
</item>
<item>
<title>Run alpine Linux via vm-bhyve on FreeBSD</title>
<link>https://nafn.de/blog/freebsd-bhyve-with-alpine-guest</link>
<guid isPermaLink="true">https://nafn.de/blog/freebsd-bhyve-with-alpine-guest</guid>
<pubDate>Tue, 11 Jul 2023 00:00:00 +0000</pubDate>
<description>How to run alpine linux on FreeBSD 13.2 via vm-bhyve.</description>
<content:encoded><![CDATA[<h2>The Issue</h2>
<p>The <a href="https://github.com/churchers/vm-bhyve/blob/master/sample-templates/alpine.conf">sample
template</a> for alpine linux in the <a href="https://github.com/churchers/vm-bhyve">vm-bhyve</a> project is using <code>grub</code> to boot the OS.</p>
<p>With this template I got the following error on every boot:</p>
<pre><code class="makeup txt"><span class="">error: unknown filesystem.
</span><span class="">error: you need to load the kernel first.
</span></code></pre>
<h2><del>Solution</del> Workaround</h2>
<p>After moving from <code>grub</code> to <code>uefi</code> everything worked fine.</p>
<p>Additionally, I changed <code>virtio-blk</code> to <code>nvme</code> because multiple sources
mentioned an enhancement in disk speed.</p>
<p>The template should look something like this:</p>
<pre><code class="makeup bash"><span class="nv">loader</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">uefi</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">cpu</span><span class="o">=</span><span class="s">1</span><span class="w">
</span><span class="nv">memory</span><span class="o">=</span><span class="s">512M</span><span class="w">
</span><span class="nv">network0_type</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">nvme</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">network0_switch</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">public</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">disk0_type</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">nvme</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">disk0_name</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">disk0.img</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<p>The changes in detail:</p>
<ul>
<li>replace <code>loader=&quot;grub&quot;</code> with <code>loader=&quot;uefi&quot;</code></li>
<li>remove all lines containing the word <code>grub_</code></li>
<li>replace <code>disk0_type=&quot;virtio-blk&quot;</code> with <code>disk0_type=&quot;nvme&quot;</code></li>
</ul>
<p>You will also need to install <code>sysutils/bhyve-firmware</code> if you haven't done so
already.</p>
]]></content:encoded>
<category>freebsd</category>
<category>bhyve</category>
<category>alpine</category>
<category>virtualization</category>
</item>
<item>
<title>A rc script for Elixir on FreeBSD</title>
<link>https://nafn.de/blog/elixir-rcd-script</link>
<guid isPermaLink="true">https://nafn.de/blog/elixir-rcd-script</guid>
<pubDate>Tue, 13 Jun 2023 00:00:00 +0000</pubDate>
<description>procname is an important part of a rc script for an elixir application on FreeBSD.</description>
<content:encoded><![CDATA[<h2>The issue</h2>
<p>When I deployed my first elixir based application to a FreeBSD jail I had some
issues stopping the service. Starting worked like a charm, but whenever I tried
to stop it, the following error appeared:</p>
<pre><code class="makeup bash"><span class="c1">#</span><span class="c1"> service app stop</span><span class="w">
</span><span class="nf">app</span><span class="n"> not running</span><span class="o">?</span><span class="n"> (check /var/run/app/app.pid</span><span class="n">)</span><span class="nf">.</span><span class="w">
</span></code></pre>
<p>This was confusing to me, because the process identifier (PID) in the mentioned
file was correct and when I tried to start the process again (while it was
already running) I received this error:</p>
<pre><code class="makeup bash"><span class="c1">#</span><span class="c1"> service app start</span><span class="w">
</span><span class="nf">Starting</span><span class="n"> app.</span><span class="w">
</span><span class="nf">daemon:</span><span class="n"> process already running, pid: 74473</span><span class="w">
</span><span class="nf">/usr/local/etc/rc.d/app:</span><span class="n"> WARNING: failed to start app</span><span class="w">
</span></code></pre>
<p>Something was off and after reading some documentation I found out what it was.</p>
<h2>Background</h2>
<p>Starting an elixir application is usually done by executing a script. I am using
the <a href="https://www.phoenixframework.org/">Phoenix Framework</a> which creates this
script using the <a href="https://hexdocs.pm/phoenix/releases.html#releases-assemble"><code>mix phx.gen.release</code></a>
command. The way I designed the deployment, the generated script eventually
lands at the following location on a FreeBSD host:
<code>/usr/local/app/v1.2.3/bin/server</code>.</p>
<p>When executed, the script will start the erlang virtual machine
(<a href="https://en.wikipedia.org/wiki/BEAM_(Erlang_virtual_machine)">BEAM</a>) with a
very long command. It might look something like this:</p>
<pre><code class="makeup bash"><span class="nf">/usr/local/app/v1.2.3/erts-13.2/bin/beam.smp</span><span class="o"> --</span><span class="n"> -root /usr/local/app/v1.2.3 -bindir /usr/local/app/v1.2.3/erts-13.2/bin -progname erl -- -home /home/app -- -noshell -s elixir start_cli -mode embedded -setcookie I_AM_A_COOKIE -sname app -config /usr/local/app/v1.2.3/releases/v1.2.3/sys -boot /usr/local/app/v1.2.3/releases/v1.2.3/start -boot_var RELEASE_LIB /usr/local/app/v1.2.3/lib -- -extra --no-halt</span><span class="w">
</span></code></pre>
<p>Somehow we need to make the rc script aware of this long command. Or at least
parts of it.</p>
<h2>The solution</h2>
<p>Assuming our service is called <code>app</code>, we will place the following rc script at
<code>/usr/local/etc/rc.d/app</code>.</p>
<pre><code class="makeup bash"><span class="c1">#</span><span class="c1">!/bin/sh</span><span class="w">
</span><span class="w">
</span><span class="c1">#</span><span class="c1"> PROVIDE: app</span><span class="w">
</span><span class="c1">#</span><span class="c1"> REQUIRE: LOGIN DAEMON NETWORKING</span><span class="w">
</span><span class="c1">#</span><span class="c1"> KEYWORD: shutdown</span><span class="w">
</span><span class="w">
</span><span class="nf">.</span><span class="n"> /etc/rc.subr</span><span class="w">
</span><span class="w">
</span><span class="nv">name</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">app</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">rcvar</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">app_enable</span><span class="p">&quot;</span><span class="w">
</span><span class="w">
</span><span class="nv">command</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">/usr/sbin/daemon</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">pidfile</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">/var/run/app/</span><span class="nv">$</span><span class="p">{</span><span class="nv">name</span><span class="p">}</span><span class="s2">.pid</span><span class="p">&quot;</span><span class="w">
</span><span class="w">
</span><span class="nv">task</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">/usr/local/app/v1.2.3/bin/server</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">procname</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">*beam.smp*</span><span class="p">&quot;</span><span class="w">
</span><span class="w">
</span><span class="nv">app_chdir</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">/usr/local/app</span><span class="p">&quot;</span><span class="w">
</span><span class="w">
</span><span class="nv">command_args</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">-p </span><span class="nv">$</span><span class="p">{</span><span class="nv">pidfile</span><span class="p">}</span><span class="s2"> -t </span><span class="nv">$</span><span class="p">{</span><span class="nv">name</span><span class="p">}</span><span class="s2"> -u app -o /var/log/app.log </span><span class="nv">$</span><span class="p">{</span><span class="nv">task</span><span class="p">}</span><span class="p">&quot;</span><span class="w">
</span><span class="w">
</span><span class="nf">load_rc_config</span><span class="w"> </span><span class="nv">$</span><span class="nv">name</span><span class="w">
</span><span class="nf">run_rc_command</span><span class="w"> </span><span class="p">&quot;</span><span class="nv">$</span><span class="nv">1</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<blockquote>
<p>To get a general understanding of rc files I can recommend the <a href="https://docs.freebsd.org/en/articles/rc-scripting/">&quot;Practical
rc.d scripting in BSD&quot;-Guide</a>.</p>
</blockquote>
<p>The important part for an elixir application and this scenario is the
combination of <code>command</code>, <code>command_args</code>, <code>task</code> and <code>procname</code>.</p>
<ul>
<li><code>command</code> defines what is executed on start</li>
<li><code>command_args</code> is passed to the command as argument</li>
<li><code>task</code> only exists to enhance readability of <code>command_args</code></li>
<li><code>procname</code> is by default set to the same value as <code>command</code>. It must contain
the process name at runtime (that is: after it is started).</li>
</ul>
<p>You might already guess the problem: <code>command</code> contains the script which is created by
<code>mix phx.gen.release</code>. And <code>procname</code> must contain the command which is
eventually created by the script in <code>command</code>.</p>
<p>When you do not set <code>procname</code> to e.g. <code>*beam.smp*</code> you can still run <code>service app start</code> to start the service, but running <code>service app stop</code> will fail because it
will check two things:</p>
<ul>
<li>find the process matching the PID specified in the <code>pidfile</code></li>
<li>check if the process name matches whatever is specified in <code>procname</code></li>
</ul>
<p>The first check will succeed because the PID exists. But the second check will
fail because the process name generated to run the BEAM is different from the
value in <code>command</code> and - if not set - <code>procname</code>.</p>
<p>To solve this issue, I set <code>procname</code> to a globbed string containing <code>beam.smp</code>
which works fine. It might become an issue when you have multiple BEAM processes
fighting for PIDs.</p>
<p>Should this becomes an issue, try to make the <code>procname</code> more restrictive like
e.g.: <code>/usr/local/app/v1.2.3/erts-13.2/bin/beam.smp*</code>.</p>
<p>Finally, to enable this service, add the following line to <code>/etc/rc.conf</code>:</p>
<pre><code class="makeup bash"><span class="nv">app_enable</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">YES</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<p>Now you can start and stop your elixir application using the usual <code>service app &lt;start|stop|restart&gt;</code> commands.</p>
]]></content:encoded>
<category>elixir</category>
<category>freebsd</category>
<category>rcd</category>
<category>deployment</category>
</item>
<item>
<title>Install Elixir on FreeBSD</title>
<link>https://nafn.de/blog/freebsd-install-elixir</link>
<guid isPermaLink="true">https://nafn.de/blog/freebsd-install-elixir</guid>
<pubDate>Sun, 27 Nov 2022 00:00:00 +0000</pubDate>
<description>Howto install a more or less up2date elixir version on FreeBSD.</description>
<content:encoded><![CDATA[<h2>Install Elixir on FreeBSD</h2>
<p>TL;DR:</p>
<pre><code class="makeup bash"><span class="c1">#</span><span class="c1"> pkg install elixir-devel erlang-runtime25</span><span class="w">
</span><span class="c1">#</span><span class="c1"> setenv PATH /usr/local/lib/erlang25/bin:$PATH</span><span class="w">
</span></code></pre>
<hr />
<p>I'm still relatively new around FreeBSD, so some things are not as obvious to me
as others. One thing that took me quite some time was the installation of a
relatively recent version of elixir on FreeBSD.</p>
<p>Looking at a <code>pkg search</code> for elixir shows the following packages:</p>
<pre><code class="makeup bash"><span class="nf">elixir-1.13.4_1</span><span class="n">                Functional, meta-programming aware language built on top of Erlang VM</span><span class="w">
</span><span class="nf">elixir-devel-1.14.0</span><span class="n">            Functional, meta-programming aware language built on top of Erlang VM</span><span class="w">
</span><span class="nf">[..]</span><span class="w">
</span></code></pre>
<p>Of course I only saw the first line and didn't bother to look any further. m(</p>
<p>After spending an afternoon learning howto build or update a FreeBSD packages myself
using <a href="https://github.com/freebsd/poudriere/wiki">poudriere</a> I came across
<code>elixir-devel</code> on <a href="https://www.freshports.org/lang/elixir-devel/">freshports</a>.
Not my proudest moment.</p>
<hr />
<p>This elixir version <code>1.14.0</code> isn't the latest but it is close enough for now.</p>
<p>Another thing you'll need is erlang. Let's see what <code>pkg search erlang</code> has to
offer:</p>
<pre><code class="makeup bash"><span class="nf">erlang-24.3.4.5,4</span><span class="n">              Functional programming language from Ericsson</span><span class="w">
</span><span class="nf">[..]</span><span class="w">
</span><span class="nf">erlang-runtime21-21.3.8.24_1</span><span class="n">   Functional programming language from Ericsson</span><span class="w">
</span><span class="nf">erlang-runtime22-22.3.4.26</span><span class="n">     Functional programming language from Ericsson</span><span class="w">
</span><span class="nf">erlang-runtime23-23.3.4.16</span><span class="n">     Functional programming language from Ericsson</span><span class="w">
</span><span class="nf">erlang-runtime24-24.3.4.4</span><span class="n">      Functional programming language from Ericsson</span><span class="w">
</span><span class="nf">erlang-runtime25-25.1</span><span class="n">          Functional programming language from Ericsson</span><span class="w">
</span></code></pre>
<p>Searching for <code>-devel</code> does not help here but a little bit down the list we can
find <code>-runtime</code> versions of the package. They are available in different
versions and even a relatively recent version of Erlang/OTP 25 is available.</p>
<p>This is all I need, so there is no need to change the FreeBSD release channel
for now. Hint: Changing the release channel from <code>quarterly</code> to <code>latest</code> might
be a way to get even newer packages without starting to build our own packages.</p>
<p>After installing <code>erlang-runtime25</code> we're greeted with the following message:</p>
<pre><code class="makeup txt"><span class="">To use this runtime port for development or testing, just prepend
</span><span class="">its binary path (&quot;/usr/local/lib/erlang25/bin&quot;) to your PATH variable.
</span></code></pre>
<p>We can do so via the following command:</p>
<pre><code class="makeup bash"><span class="c1">#</span><span class="c1"> setenv PATH /usr/local/lib/erlang25/bin:$PATH</span><span class="w">
</span></code></pre>
<p>We can now use the <code>iex</code> command to start a shell:</p>
<pre><code class="makeup bash"><span class="c1">#</span><span class="c1"> iex</span><span class="w">
</span><span class="nf">Erlang/OTP</span><span class="n"> 25 </span><span class="k">[</span><span class="n">erts</span><span class="ow">-</span><span class="n">13.1</span><span class="k">]</span><span class="w"> </span><span class="k">[</span><span class="n">source</span><span class="k">]</span><span class="w"> </span><span class="k">[</span><span class="n">64</span><span class="ow">-</span><span class="n">bit</span><span class="k">]</span><span class="w"> </span><span class="k">[</span><span class="n">smp:8:8</span><span class="k">]</span><span class="w"> </span><span class="k">[</span><span class="n">ds:8:8:10</span><span class="k">]</span><span class="w"> </span><span class="k">[</span><span class="n">async</span><span class="ow">-</span><span class="n">threads:1</span><span class="k">]</span><span class="w"> </span><span class="k">[</span><span class="n">jit:ns</span><span class="k">]</span><span class="w"> </span><span class="k">[</span><span class="n">dtrace</span><span class="k">]</span><span class="w"> </span><span class="k">[</span><span class="n">sharing</span><span class="ow">-</span><span class="n">preserving</span><span class="k">]</span><span class="w">
</span><span class="w">
</span><span class="nf">Interactive</span><span class="n"> Elixir (1.14.0</span><span class="n">) </span><span class="nf">-</span><span class="n"> press Ctrl+C to exit (type h(</span><span class="n">) </span><span class="nf">ENTER</span><span class="n"> for help</span><span class="n">)
</span><span class="nf">iex</span><span class="n">(1</span><span class="n">)</span><span class="o">&gt;</span><span class="w">
</span></code></pre>
<p>Hint: Yes, these commands were run in a root shell which you can identify by the <code>#</code>
in front of every command. I ran all of this in a testing jail which was deleted
even before this post was created.</p>
]]></content:encoded>
<category>freebsd</category>
<category>elixir</category>
<category>installation</category>
<category>erlang</category>
</item>
<item>
<title>Filesystem diffs via ZFS snapshots</title>
<link>https://nafn.de/blog/filesystem-diffs-via-zfs-snapshots</link>
<guid isPermaLink="true">https://nafn.de/blog/filesystem-diffs-via-zfs-snapshots</guid>
<pubDate>Sun, 11 Sep 2022 00:00:00 +0000</pubDate>
<description>While automating a task I ran into a situation where ZFS snapshots not only made sense to use but also helped me to find a solution relatively quick.</description>
<content:encoded><![CDATA[<h2>The issue</h2>
<p>Today I needed a to find a way to run a <code>freebsd-update</code> command exactly once
on a certain jail.</p>
<h2>Background</h2>
<p>Let's take a step back shortly to understand why I need this.</p>
<p>Previously the process to build jails looked as follows:</p>
<p>Do once:</p>
<ul>
<li>Download <code>base.txz</code></li>
</ul>
<p>Do for every new jail:</p>
<ul>
<li>Create a ZFS dataset</li>
<li>Extract base.txz into the dataset</li>
<li>Run <code>freebsd-update</code> on the jail</li>
<li>Configure sshd and set <code>authorized_keys</code></li>
<li>Copy <code>resolv.conf</code></li>
<li>Configure <code>jails.conf</code> with IPv6/IPv4 addresses</li>
<li>Start jail</li>
</ul>
<p>This felt tedious and seemed like something that can be improved. Since ZFS
supports very fast and supposedly lightweight snapshots I figured they could
be the right tool. After a quick search I found a blog post called &quot;<a href="https://clinta.github.io/freebsd-jails-the-hard-way/">FreeBSD
jails the hard way</a>&quot; which
describes exactly this scenario. Funny thing: This post is from <em>2015</em> and still
works like a charm. Do you remember the last time a seven year old blog post
helped you solve an issue with kubernetes? It was probably seven years ago.</p>
<p>After a short refactoring I was able to change the process to this:</p>
<p>Do once:</p>
<ul>
<li>Download <code>base.txz</code></li>
<li>Create a ZFS dataset</li>
<li>Extract base.txz into the dataset</li>
<li>Run <code>freebsd-update</code> for the jail</li>
<li>Configure sshd and set <code>authorized_keys</code></li>
<li>Copy <code>resolv.conf</code></li>
<li>Create a ZFS snapshot</li>
</ul>
<p>Do for every new jail:</p>
<ul>
<li>Create a ZFS dataset clone from previous snapshot</li>
<li>Configure <code>jails.conf</code> with IPv6/IPv4 addresses</li>
<li>Start jail</li>
</ul>
<p>Check the
<a href="https://github.com/maltekrupa/ansible-roles-freebsd/commit/e2771cab244920bff9b984d511a4a0df728adb71">commit</a>
if you're interested in the details..</p>
<h2>The solution</h2>
<p>And since I do not want to run the slow <code>freebsd-update</code> command on every
invocation of ansible, I needed to find a way to only run it once.</p>
<p>This command obviously changes files on the file system. To find these files, I
made use of ZFS snapshots and the <code>diff</code> functionality of ZFS.</p>
<p>It's as easy as it sounds.</p>
<ol>
<li>Create snapshot of dataset</li>
<li>Run command that alters files</li>
<li>Create another snapshot of the same dataset</li>
<li>Run <code>zfs diff snapshot1 snapshot2</code> to get a diff</li>
</ol>
<p>And of course, after searching through the list of files that were changed, I
found this thing:</p>
<pre><code class="makeup bash"><span class="nf">root@w0:</span><span class="nf">~</span><span class="w"> </span><span class="c1">#</span><span class="c1"> zfs diff zroot/jails/test2/root@pre-install zroot/jails/test2/root@post-install</span><span class="w">
</span><span class="nf">[..]</span><span class="w">
</span><span class="nf">+</span><span class="n">       /usr/local/jails/test2/root/boot/kernel.old/.freebsd-update</span><span class="w">
</span><span class="nf">[..]</span><span class="w">
</span></code></pre>
<p>The <code>+</code> at the beginning of the line suggests that this is a file that is
present in the second but not the first snapshot. A perfect candidate for
our task.</p>
<p>Finally, the ansible task to run the update looks as follows:</p>
<pre><code class="makeup yaml"><span class="p">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">Update</span><span class="w">
</span><span class="w">  </span><span class="nt">ansible.builtin.command</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="nt">cmd</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">freebsd-update -b /usr/local/jails/{{ jails_version }}/root fetch install --not-running-from-cron</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="nt">creates</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">/usr/local/jails/{{ jails_version }}/root/boot/kernel.old/.freebsd-update</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<p><a href="https://github.com/maltekrupa/ansible-roles-freebsd/blob/e2771cab244920bff9b984d511a4a0df728adb71/jails/tasks/build_template.yml#L19-L22">Source on
github</a></p>
<p>Someone on the <a href="https://wiki.freebsd.org/Discord">FreeBSD Discord</a> recently
mentioned that they're making use of ZFS deduplication for all of their full-jails.
While I do not really want to go the route of using deduplication yet I liked the
idea to save a lot of space when having multiple jails. Maybe on another day.</p>
]]></content:encoded>
<category>zfs</category>
<category>freebsd</category>
<category>snapshots</category>
<category>filesystem</category>
</item>
<item>
<title>Slow starting jails on FreeBSD</title>
<link>https://nafn.de/blog/freebsd-slow-starting-jails</link>
<guid isPermaLink="true">https://nafn.de/blog/freebsd-slow-starting-jails</guid>
<pubDate>Sat, 10 Sep 2022 00:00:00 +0000</pubDate>
<description>A FreeBSD jail without DNS capabilities starts very slow.</description>
<content:encoded><![CDATA[<h2>The issue</h2>
<p>Yesterday I tried to find a way to create jails more or less
automatically from an ansible configuration.</p>
<p>Today I wanted to have a quick look inside of these jails to understand a bit
more about how they work different from the host OS.</p>
<p>But even before I was able to run the first command inside of a jail I had to
deal with an issue that makes it very hard to work with jails. Slow startup
time.</p>
<p>Have a look at this restart of a jail:</p>
<pre><code class="makeup bash"><span class="nf">root@w0:</span><span class="nf">~</span><span class="w"> </span><span class="c1">#</span><span class="c1"> time service jail restart test1</span><span class="w">
</span><span class="nf">Stopping</span><span class="n"> jails: test1.</span><span class="w">
</span><span class="nf">Starting</span><span class="n"> jails: test1.</span><span class="w">
</span><span class="nf">0.176u</span><span class="n"> 0.139s 1:49.75 0.2</span><span class="nv">%</span><span class="n">      151+217k 0+31io 0pf+0w</span><span class="w">
</span></code></pre>
<p>Stopping the jail was very fast. But starting the jail took the majority of the
nearly two minutes.</p>
<p>This is not a playground you can work with, so we need to debug this somehow.
After a minute with google I found a couple of hints in the direction of IPv6
and DNS.</p>
<p>To test both IPv6 and DNS I ran a couple of <code>drill</code>s:</p>
<pre><code class="makeup txt"><span class="">root@test1:/ # time drill nafn.de aaaa @2620:fe::fe
</span><span class="">[..]
</span><span class="">nafn.de.        3525    IN      AAAA    2a00:d0c0:200:0:3493:eeff:fe94:1775
</span><span class="">0.000u 0.004s 0:00.00 0.0%      0+0k 0+0io 0pf+0w
</span></code></pre>
<p>Seems to work fine. What didn't work so fine was the same command using a IPv4
address.</p>
<pre><code class="makeup txt"><span class="">root@test1:/ # time drill nafn.de aaaa @9.9.9.9
</span><span class="">Error: error sending query: Could not send or receive, because of network error
</span><span class="">0.003u 0.000s 0:15.21 0.0%      0+0k 0+0io 0pf+0w
</span></code></pre>
<p>At this point I knew two things:</p>
<ul>
<li>IPv4 traffic is somehow bugged</li>
<li>a jail without DNS resolving capabilities will start very slow</li>
</ul>
<h2>Firewall and NAT</h2>
<p>Since I already worked with a system like this about 14 years ago (back then I
failed misserably), I knew it might have something to do with the firewall
configuration.</p>
<p>The server is running at Hetzner and I'm not interested in buying more IPv4
addresses so IPv4 inside of jail is making use of NAT. And while running these
<code>drill</code> commands, I remembered that I haven't spent much time with
<a href="https://docs.freebsd.org/en/books/handbook/firewalls/#firewalls-pf"><code>pf</code></a> yet.</p>
<p>I think I just took the first example I could find in <code>/usr/share/examples/pf/</code>
and stripped it down to something that I can work with and understand.</p>
<p>Of course I only took care of the things in the configuration file that I
immediately needed at that time, so the file looked <a href="https://github.com/maltekrupa/ansible-roles-freebsd/blob/3bc9bce11bf746ca963c94dd423a7c1d74a6c492/firewall/templates/pf.conf#L7">something like
this</a>:</p>
<pre><code class="makeup bash"><span class="nv">ext_if</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">em0</span><span class="p">&quot;</span><span class="w">
</span><span class="nf">[..]</span><span class="w">
</span><span class="nf">nat</span><span class="n"> on </span><span class="nv">$</span><span class="nv">ext_if</span><span class="n"> from lo0:network to any -</span><span class="o">&gt;</span><span class="n"> (</span><span class="nv">$</span><span class="nv">ext_if</span><span class="n">)
</span><span class="nf">[..]</span><span class="w">
</span></code></pre>
<p>Before we get into why this cannot work, lets see what this line actually means:</p>
<p>&quot;Translate all packets coming from <code>lo0:network</code> to the address associated with
<code>$ext_if</code> when they are going out on <code>$ext_if</code>&quot;</p>
<p>This cannot work for multiple reasons. First, the jail IPs are not associated to
<code>lo0</code>. Also, <code>$ext_if</code> has multiple addresses associated which kind of makes
this bogus. Completely useless so far.</p>
<p>Let's have a look at a configuration <a href="https://github.com/maltekrupa/ansible-roles-freebsd/blob/main/firewall/templates/pf.conf#L9">that actually
works</a>:</p>
<pre><code class="makeup bash"><span class="nv">ext_if</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">em0</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">ext_ipv4</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">185.26.156.224</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">int_network_range</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">10.0.0.0/8</span><span class="p">&quot;</span><span class="w">
</span><span class="nf">[..]</span><span class="w">
</span><span class="nf">nat</span><span class="n"> on </span><span class="nv">$</span><span class="nv">ext_if</span><span class="n"> from </span><span class="nv">$</span><span class="nv">int_network_range</span><span class="n"> to any -</span><span class="o">&gt;</span><span class="w"> </span><span class="nv">$</span><span class="nv">ext_ipv4</span><span class="w">
</span><span class="nf">[..]</span><span class="w">
</span></code></pre>
<p>Meaning:</p>
<p>&quot;Translate all packets coming from <code>$int_network_range</code> to the address
<code>$ext_ipv4</code> when they're going out on <code>$ext_if</code>&quot;</p>
<p>It's a bit more static but since all the relevant information is available via ansible,
it is automatically changed when running on different hosts.</p>
<h2>Did it solve the problem?</h2>
<p>It did. After applying this new configuration to the firewall, the jails started
very quick and IPv4 traffic from inside of the jail was working too.</p>
<pre><code class="makeup txt"><span class="">root@test1:/ # time drill nafn.de aaaa @9.9.9.9
</span><span class="">[..]
</span><span class="">nafn.de.        3600    IN      AAAA    2a00:d0c0:200:0:3493:eeff:fe94:1775
</span><span class="">[..]
</span><span class="">0.000u 0.003s 0:00.03 0.0%      0+0k 0+0io 0pf+0w
</span></code></pre>
<pre><code class="makeup txt"><span class="">root@w0:~ # time service jail restart test1
</span><span class="">Stopping jails: test1.
</span><span class="">Starting jails: test1.
</span><span class="">0.185u 0.162s 0:01.00 34.0%     108+194k 0+31io 0pf+0w
</span></code></pre>
<p>\o/</p>
]]></content:encoded>
<category>freebsd</category>
<category>jails</category>
<category>dns</category>
<category>performance</category>
</item>
<item>
<title>Ansible loop with conditions</title>
<link>https://nafn.de/blog/ansible-loop-with-conditions</link>
<guid isPermaLink="true">https://nafn.de/blog/ansible-loop-with-conditions</guid>
<pubDate>Fri, 09 Sep 2022 00:00:00 +0000</pubDate>
<description>Finding a better solution to a problem because of GNU vs BSD.</description>
<content:encoded><![CDATA[<h3>Update</h3>
<p>Wow, a couple days later I see <a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/unarchive_module.html#parameter-creates">this
option</a>
on the unarchive module. m(</p>
<h3>Original post</h3>
<p>While writing an ansible role
(<a href="https://github.com/maltekrupa/ansible-roles-freebsd">source</a>) that would
create and setup FreeBSD jails, I needed to find a way to only extract the
base.txz file (containing the base operating system) to a directory when it
hasn't been done before.</p>
<p>This should only be done once to prevent overwritten files and save time.</p>
<p>My first attempt was a naive check if the directory or a file in the directory
already exist.</p>
<pre><code class="makeup yaml"><span class="p">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">Check jails for extracted base.txz</span><span class="w">
</span><span class="w">    </span><span class="nt">ansible.builtin.stat</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">/usr/local/jails/{{ item }}/root/etc/passwd</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="nt">loop</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">{{ jails_create.keys() | flatten(levels=1) }}</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="nt">register</span><span class="p">:</span><span class="w"> </span><span class="s">jails_stat</span><span class="w">
</span></code></pre>
<p>This works quite nice.
The next step would be to extract the base.txz file under certain conditions.</p>
<pre><code class="makeup yaml"><span class="p">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">Extract base.txz</span><span class="w">
</span><span class="w">  </span><span class="nt">ansible.builtin.unarchive</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="nt">src</span><span class="p">:</span><span class="w"> </span><span class="s">/tmp/base.txz</span><span class="w">
</span><span class="w">    </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">/usr/local/jails/{{ item.item }}/root</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="nt">remote_src</span><span class="p">:</span><span class="w"> </span><span class="kc">yes</span><span class="w">
</span><span class="w">  </span><span class="nt">loop</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">{{ jails_stat.results }}</span><span class="p">&quot;</span><span class="w">
</span><span class="w">  </span><span class="nt">when</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="p">-</span><span class="w"> </span><span class="s">item is not skipped</span><span class="w">
</span><span class="w">    </span><span class="p">-</span><span class="w"> </span><span class="s">not item.stat.exists</span><span class="w">
</span></code></pre>
<p>Instead of looping over the <code>jails_create</code> list, we iterate over the results of
the previous check. This allows us to access the required information for the
conditions more easily.</p>
<p>Both of these took me about an hour to figure out (of course with the help of
stackoverflow and the ansible documentation). And when I finally ran both of
these without <code>--check --diff</code>, to actually get a result, I ran into the following
error:</p>
<pre><code class="makeup txt"><span class="">Failed to find handler for &quot;/tmp/base.txz&quot;.
</span><span class="">Make sure the required command to extract the file is installed.
</span><span class="w">
</span><span class="">Command &quot;/usr/bin/tar&quot; detected as tar type bsd. GNU tar required.
</span><span class="w">
</span><span class="">Unable to find required &#39;zipinfo&#39; binary in the path.
</span></code></pre>
<p>🧘</p>
<p>So, finally, I replaced both of these with something that is way shorter, easier
to read and less complicated:</p>
<pre><code class="makeup yaml"><span class="p">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">Extract base.txz to jails</span><span class="w">
</span><span class="w">  </span><span class="nt">ansible.builtin.command</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">tar -xvf /tmp/base.txz -C /usr/local/jails/{{ item }}/root</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="nt">args</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="nt">creates</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">/usr/local/jails/{{ item }}/root/etc/passwd</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="nt">loop</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">{{ jails_create.keys() | flatten(levels=1) }}</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<p>That's it. Took me less than five minutes and makes me way more happy.</p>
<p>And of course, there are better solutions to managing jails. But I'll take one
step after another.</p>
]]></content:encoded>
<category>ansible</category>
<category>automation</category>
<category>bsd</category>
<category>linux</category>
</item>
<item>
<title>FreeBSD release cycle</title>
<link>https://nafn.de/blog/freebsd-releases</link>
<guid isPermaLink="true">https://nafn.de/blog/freebsd-releases</guid>
<pubDate>Thu, 08 Sep 2022 00:00:00 +0000</pubDate>
<description>It&apos;s a long road from github to a FreeBSD release.</description>
<content:encoded><![CDATA[<p>Today I wanted to run some tests with jails and while jumping through the
amazing FreeBSD handbook I found some information that you can script
the installation of FreeBSD inside of a jail via the <code>bsdinstall</code>
command.</p>
<p>To script the installation of a FreeBSD server (not a jail) you can make
use of <code>bsdinstall script</code>. Sadly this doesn't work for jails, because
<code>bsdinstall script</code>  wants to configure disks and other things that aren't
necessary and/or available in jails.</p>
<p>The correct command would be <code>bsdinstall jail</code>, which up until now does not
support any scripting.
A <a href="https://github.com/freebsd/freebsd-src/pull/473">PR</a> for this feature is
already merged into <code>current</code>, but has not yet landed anywhere else.</p>
<p>It took me quite a while to figure out why this change from over a year ago is
not yet available on the FreeBSD 13.1-RELEASE machines I have running.</p>
<p>Turns out a quick look into the handbook would've explained it.</p>
<p><a href="https://docs.freebsd.org/en/books/handbook/cutting-edge/#updating-src-obtaining-src">https://docs.freebsd.org/en/books/handbook/cutting-edge/#updating-src-obtaining-src</a></p>
<p>What is named <code>current</code> is the &quot;bleeding-edge&quot; release channel of FreeBSD. It
corresponds with the <code>main</code> branch of the <code>freebsd-src</code> repository.</p>
<p>The next best thing is the <code>stable/X</code> branch (where the change also hasn't been
seen yet 😢) and finally there is the <code>releng/X.Y</code> branch which holds everything
that is part of the OS I'm currently running.</p>
<p>Long story short: It will probably take quite some time until this patch is
available in a FreeBSD RELEASE version.</p>
]]></content:encoded>
<category>freebsd</category>
<category>releases</category>
<category>open-source</category>
<category>operating-system</category>
</item>
<item>
<title>Ubuntu is making fun of me</title>
<link>https://nafn.de/blog/ubuntu-is-making-fun-of-me</link>
<guid isPermaLink="true">https://nafn.de/blog/ubuntu-is-making-fun-of-me</guid>
<pubDate>Wed, 18 May 2022 00:00:00 +0000</pubDate>
<description>Computers should help us doing things. Sometimes we make them do the opposite.</description>
<content:encoded><![CDATA[<p>In recent years I feel more and more like ubuntu is making fun of me.</p>
<p>Have a look at this example where I wanted to use <code>psql</code> to connect to a
postgres database on a Ubuntu 20.04 LTS server.</p>
<pre><code class="makeup txt"><span class="">$ psql
</span><span class="w">
</span><span class="">Command &#39;psql&#39; not found, but can be installed with:
</span><span class="w">
</span><span class="">apt install postgresql-client-common
</span><span class="">Please ask your administrator.
</span></code></pre>
<p>So far so good. Let's install it like the computer tells us to:</p>
<pre><code class="makeup txt"><span class="">$ sudo apt-get install postgresql-client-common
</span><span class="">[...]
</span></code></pre>
<p>In a perfect world you would imagine that we're done now and can continue our
life.</p>
<p>Sadly:</p>
<pre><code class="makeup txt"><span class="">$ psql
</span><span class="">Warning: No existing cluster is suitable as a default target. Please see man
</span><span class="">pg_wrapper(1) how to specify one.
</span><span class="">Error: You must install at least one postgresql-client-&lt;version&gt; package
</span></code></pre>
<p>What? Wtf did I just install?</p>
<pre><code class="makeup txt"><span class="">man 1 pg_wrapper
</span></code></pre>
<p>After wasting time reading this manpage I can tell you:</p>
<p>Apparently I installed a perl wrapper script which intercepts all postgres
related commands. It tries to be smart about something that I didn't even knew I
needed: Having multiple versions of postgres installed at the same time. Cool,
thanks computer.</p>
<p>Eventually, this saved the day:</p>
<pre><code class="makeup txt"><span class="">$ sudo apt-get install postgresql-client-12
</span></code></pre>
<p>Sure, why bother writing about this, right? For years I just ignored every
little failure of whatever Linux distribution I was using at the time, but
after spending some time with FreeBSD I have to say that it's just a pain
to go back to Linux now. Maybe that would've been the better blog post.</p>
]]></content:encoded>
<category>ubuntu</category>
<category>linux</category>
<category>troubleshooting</category>
<category>sysadmin</category>
</item>
<item>
<title>Metaprogramming in Elixir - Part 1</title>
<link>https://nafn.de/blog/elixir-meta-programming-part-1</link>
<guid isPermaLink="true">https://nafn.de/blog/elixir-meta-programming-part-1</guid>
<pubDate>Mon, 02 May 2022 00:00:00 +0000</pubDate>
<description>An exercise on exercism forced me to learn something I never knew I needed.</description>
<content:encoded><![CDATA[<h2>Abstract</h2>
<p>I like challenges and I like them even more if I can work on them whenever I
want instead of having the pressure to do so immediately.</p>
<p>While trying to find a solution for an <a href="https://exercism.org/tracks/elixir/exercises/top-secret">exercism
exercise</a> called
&quot;top-secret&quot; where your task is to decode hidden messages in code, I realized
that it requires me to work more than usual to understand what is going on and
how to solve it.</p>
<p>To quote <a href="https://youtu.be/mpOwAL7cAaE?t=334">Angelika Tyborska</a> while
describing the exercises on exercism: &quot;solving a practice exercise takes
anywhere from five minutes to five hours&quot;. After I spent five hours I thought I
could also write a blogpost and dump even more hours into this topic.</p>
<p>Solving the &quot;top-secret&quot; exercise involves something called <a href="https://en.wikipedia.org/wiki/Metaprogramming">meta
programming</a> which I have heard
of before but never interested me enough to take a look at. Maybe I was scared
of the complexity that the first sentence of the Wikipedia implies:</p>
<blockquote>
<p>Metaprogramming is a programming technique in which computer programs have the
ability to treat other programs as their data.</p>
</blockquote>
<p>It sounds intriguing but also scary. I already have a hard time reading code,
how am I supposed to make the computer read and act on code?! And to be honest,
I haven't read more that the first paragraph of the Wikipedia page yet.</p>
<p>This post describes my journey of learning what I think is required to solve the
challenge.</p>
<h2>The challenge</h2>
<p>Your task is to extract secret messages from code. You do so by making use of
the name and the arity (amount of arguments to a function) of a function. The
arity dictates how many letters of the function name should be taken into
account for the message.</p>
<p>An example:</p>
<pre><code class="makeup elixir"><span class="kd">def</span><span class="w"> </span><span class="nf">invert</span><span class="p" data-group-id="2606940928-1">(</span><span class="n">list</span><span class="p" data-group-id="2606940928-1">)</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">reverse</span><span class="p" data-group-id="2606940928-2">(</span><span class="n">list</span><span class="p" data-group-id="2606940928-2">)</span><span class="w">

</span><span class="kd">def</span><span class="w"> </span><span class="nf">lines</span><span class="p" data-group-id="2606940928-3">(</span><span class="n">list</span><span class="p">,</span><span class="w"> </span><span class="n">nth_element</span><span class="p" data-group-id="2606940928-3">)</span><span class="w"> </span><span class="k" data-group-id="2606940928-4">do</span><span class="w">
  </span><span class="n">list</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">take_every</span><span class="p" data-group-id="2606940928-5">(</span><span class="n">nth_element</span><span class="p" data-group-id="2606940928-5">)</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">length</span><span class="w">
</span><span class="k" data-group-id="2606940928-4">end</span><span class="w">

</span><span class="kd">def</span><span class="w"> </span><span class="nf">keep</span><span class="p" data-group-id="2606940928-6">(</span><span class="n">list</span><span class="p">,</span><span class="w"> </span><span class="n">only_keep</span><span class="p" data-group-id="2606940928-6">)</span><span class="w"> </span><span class="k" data-group-id="2606940928-7">do</span><span class="w">
  </span><span class="n">list</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">filter</span><span class="p" data-group-id="2606940928-8">(</span><span class="k" data-group-id="2606940928-9">fn</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">only_keep</span><span class="w"> </span><span class="k" data-group-id="2606940928-9">end</span><span class="p" data-group-id="2606940928-8">)</span><span class="w">
</span><span class="k" data-group-id="2606940928-7">end</span><span class="w">

</span><span class="kd">def</span><span class="w"> </span><span class="nf">teams</span><span class="p" data-group-id="2606940928-10">(</span><span class="n">list</span><span class="p">,</span><span class="w"> </span><span class="n">team_size</span><span class="p">,</span><span class="w"> </span><span class="n">keep_smaller_teams</span><span class="w"> </span><span class="o">\\</span><span class="w"> </span><span class="no">true</span><span class="p" data-group-id="2606940928-10">)</span><span class="w"> </span><span class="k" data-group-id="2606940928-11">do</span><span class="w">
  </span><span class="n">list</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">split</span><span class="p" data-group-id="2606940928-12">(</span><span class="n">team_size</span><span class="p" data-group-id="2606940928-12">)</span><span class="w">
  </span><span class="c1"># TODO: implement keep_smaller_teams</span><span class="w">
</span><span class="k" data-group-id="2606940928-11">end</span><span class="w">
</span></code></pre>
<p>Hint: In elixir it is common to refer to a function by appending the arity to
it.</p>
<p>Going from top to bottom we have:
<code>invert/1</code> -&gt; <code>i</code>
<code>lines/2</code> -&gt; <code>li</code>
<code>keep/2</code> -&gt; <code>ke</code>
<code>teams/3</code> -&gt; <code>tea</code></p>
<p>Assembling the messages yields <code>iliketea</code>. So far so good. Doing this for the
first couple test cases was easy, but the edge cases made me refactor the code
over and over again and breaking what worked before which was a sign for me that
I missed something along the way and my approach is probably very wrong.</p>
<p>But let's start at the beginning.</p>
<h2>Abstract Syntax Trees</h2>
<p>The last time I dealt with Abstract Syntax Trees (AST) was in university. At
least I think I dealt with them. Maybe they were different trees. Who knows.
As far as I understood it, you might think of ASTs as a way of representing code
as data.</p>
<p>Elixir refers to ASTs as quoted expressions and offers a macro called
<code>quote</code> which turns <em>some</em> code into a tuple with three elements.</p>
<pre><code class="makeup elixir"><span class="gp unselectable">iex(1)&gt; </span><span class="k">quote</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="n">sum</span><span class="p" data-group-id="9414469540-1">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p" data-group-id="9414469540-1">)</span><span class="w">
</span><span class="p" data-group-id="9414469540-2">{</span><span class="ss">:sum</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="9414469540-3">[</span><span class="p" data-group-id="9414469540-3">]</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="9414469540-4">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p" data-group-id="9414469540-4">]</span><span class="w">
</span></code></pre>
<p>The first element (<code>:sum</code>) is an atom describing the operation, the second
argument is a list describing metadata and the last element is a list which
contains other nodes.</p>
<p><code>quote</code> works a bit different atoms, floats, integers, strings, lists and
tuples where it just returns the value. These seem to be the leafs of the tree.</p>
<pre><code class="makeup elixir"><span class="gp unselectable">iex(1)&gt; </span><span class="k">quote</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hello&quot;</span><span class="w">
</span><span class="s">&quot;hello&quot;</span><span class="w">
</span><span class="gp unselectable">iex(2)&gt; </span><span class="k">quote</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="9422575145-1">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p" data-group-id="9422575145-1">]</span><span class="w">
</span><span class="p" data-group-id="9422575145-2">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p" data-group-id="9422575145-2">]</span><span class="w">
</span><span class="gp unselectable">iex(3)&gt; </span><span class="k">quote</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="ss">:hello</span><span class="w"> 
</span><span class="ss">:hello</span><span class="w">
</span></code></pre>
<p>This already feels like everything we need to move code into data.</p>
<p>On a side node: <code>quote</code> somehow cleared up some things for me (not all of
course) when dealing with expressions in Elixir.</p>
<pre><code class="makeup elixir"><span class="gp unselectable">iex(1)&gt; </span><span class="k">quote</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="o">+</span><span class="mi">1</span><span class="w">
</span><span class="p" data-group-id="1989972894-1">{</span><span class="ss">:+</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="1989972894-2">[</span><span class="ss">context</span><span class="p">:</span><span class="w"> </span><span class="nc">Elixir</span><span class="p">,</span><span class="w"> </span><span class="ss">import</span><span class="p">:</span><span class="w"> </span><span class="nc">Kernel</span><span class="p" data-group-id="1989972894-2">]</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="1989972894-3">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p" data-group-id="1989972894-3">]</span><span class="p" data-group-id="1989972894-1">}</span><span class="w">
</span><span class="gp unselectable">iex(2)&gt; </span><span class="k">quote</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="o">==</span><span class="mi">1</span><span class="w">
</span><span class="p" data-group-id="1989972894-4">{</span><span class="ss">:==</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="1989972894-5">[</span><span class="ss">context</span><span class="p">:</span><span class="w"> </span><span class="nc">Elixir</span><span class="p">,</span><span class="w"> </span><span class="ss">import</span><span class="p">:</span><span class="w"> </span><span class="nc">Kernel</span><span class="p" data-group-id="1989972894-5">]</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="1989972894-6">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p" data-group-id="1989972894-6">]</span><span class="p" data-group-id="1989972894-4">}</span><span class="w">
</span></code></pre>
<h2>The first approach</h2>
<p>Let's start by taking the first function of the example from the beginning and
see what we get after feeding it to <code>quote</code>:</p>
<pre><code class="makeup elixir"><span class="gp unselectable">iex(1)&gt; </span><span class="k">quote</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="kd">def</span><span class="w"> </span><span class="nf">invert</span><span class="p" data-group-id="4435888871-1">(</span><span class="n">list</span><span class="p" data-group-id="4435888871-1">)</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">reverse</span><span class="p" data-group-id="4435888871-2">(</span><span class="n">list</span><span class="p" data-group-id="4435888871-2">)</span><span class="w">
</span><span class="p" data-group-id="4435888871-3">{</span><span class="ss">:def</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-4">[</span><span class="ss">context</span><span class="p">:</span><span class="w"> </span><span class="nc">Elixir</span><span class="p">,</span><span class="w"> </span><span class="ss">import</span><span class="p">:</span><span class="w"> </span><span class="nc">Kernel</span><span class="p" data-group-id="4435888871-4">]</span><span class="p">,</span><span class="w">
 </span><span class="p" data-group-id="4435888871-5">[</span><span class="w">
   </span><span class="p" data-group-id="4435888871-6">{</span><span class="ss">:invert</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-7">[</span><span class="ss">context</span><span class="p">:</span><span class="w"> </span><span class="nc">Elixir</span><span class="p" data-group-id="4435888871-7">]</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-8">[</span><span class="p" data-group-id="4435888871-9">{</span><span class="ss">:list</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-10">[</span><span class="ss">if_undefined</span><span class="p">:</span><span class="w"> </span><span class="ss">:apply</span><span class="p" data-group-id="4435888871-10">]</span><span class="p">,</span><span class="w"> </span><span class="nc">Elixir</span><span class="p" data-group-id="4435888871-9">}</span><span class="p" data-group-id="4435888871-8">]</span><span class="p" data-group-id="4435888871-6">}</span><span class="p">,</span><span class="w">
   </span><span class="p" data-group-id="4435888871-11">[</span><span class="w">
     </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="4435888871-12">{</span><span class="p" data-group-id="4435888871-13">{</span><span class="ss">:.</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-14">[</span><span class="p" data-group-id="4435888871-14">]</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-15">[</span><span class="p" data-group-id="4435888871-16">{</span><span class="ss">:__aliases__</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-17">[</span><span class="ss">alias</span><span class="p">:</span><span class="w"> </span><span class="no">false</span><span class="p" data-group-id="4435888871-17">]</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-18">[</span><span class="ss">:Enum</span><span class="p" data-group-id="4435888871-18">]</span><span class="p" data-group-id="4435888871-16">}</span><span class="p">,</span><span class="w"> </span><span class="ss">:reverse</span><span class="p" data-group-id="4435888871-15">]</span><span class="p" data-group-id="4435888871-13">}</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-19">[</span><span class="p" data-group-id="4435888871-19">]</span><span class="p">,</span><span class="w">
      </span><span class="p" data-group-id="4435888871-20">[</span><span class="p" data-group-id="4435888871-21">{</span><span class="ss">:list</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4435888871-22">[</span><span class="ss">if_undefined</span><span class="p">:</span><span class="w"> </span><span class="ss">:apply</span><span class="p" data-group-id="4435888871-22">]</span><span class="p">,</span><span class="w"> </span><span class="nc">Elixir</span><span class="p" data-group-id="4435888871-21">}</span><span class="p" data-group-id="4435888871-20">]</span><span class="p" data-group-id="4435888871-12">}</span><span class="w">
   </span><span class="p" data-group-id="4435888871-11">]</span><span class="w">
 </span><span class="p" data-group-id="4435888871-5">]</span><span class="p" data-group-id="4435888871-3">}</span><span class="w">
</span></code></pre>
<p>This is already quite nested and looks complicated. The <code>metadata</code> field
contains values and makes things harder to read and understand. The function
name <code>invert</code> became an operation and somewhere at the end we find the actual
function call we're running.</p>
<p>In the past I did a lot of web scraping using things like Beautifulsoup and
xpath to get what I need from a nested data structure. Naturally I tried the
same with elixir and ended with a lot of pipelines looking something like this:</p>
<pre><code class="makeup elixir"><span class="n">arity</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">elem</span><span class="p" data-group-id="1408392961-1">(</span><span class="mi">2</span><span class="p" data-group-id="1408392961-1">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">at</span><span class="p" data-group-id="1408392961-2">(</span><span class="mi">0</span><span class="p" data-group-id="1408392961-2">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">elem</span><span class="p" data-group-id="1408392961-3">(</span><span class="mi">2</span><span class="p" data-group-id="1408392961-3">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">length</span><span class="w">
</span><span class="n">message</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">elem</span><span class="p" data-group-id="1408392961-4">(</span><span class="mi">2</span><span class="p" data-group-id="1408392961-4">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">at</span><span class="p" data-group-id="1408392961-5">(</span><span class="mi">0</span><span class="p" data-group-id="1408392961-5">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">elem</span><span class="p" data-group-id="1408392961-6">(</span><span class="mi">0</span><span class="p" data-group-id="1408392961-6">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">Atom</span><span class="o">.</span><span class="n">to_string</span><span class="p" data-group-id="1408392961-7">(</span><span class="p" data-group-id="1408392961-7">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w">
</span><span class="nc">String</span><span class="o">.</span><span class="n">slice</span><span class="p" data-group-id="1408392961-8">(</span><span class="mi">0</span><span class="o">..</span><span class="n">arity</span><span class="o">-</span><span class="mi">1</span><span class="p" data-group-id="1408392961-8">)</span><span class="w">
</span></code></pre>
<p>It works. Sure. But not for long. To account for different structures, I added a
couple of <code>case</code> statements but eventually resigned because I wasn't able to
make it work <em>and</em> have a solution that is readable.</p>
<h2>What next?</h2>
<p>Now I'm sitting here and have done two things:</p>
<ol>
<li>I failed to finish the exercise (&quot;in time&quot;)</li>
<li>I started a blog post without knowing how to continue once I reach the &quot;What
next?&quot; point</li>
</ol>
<p>More to come. Hopefully.</p>
]]></content:encoded>
<category>elixir</category>
<category>metaprogramming</category>
<category>exercism</category>
<category>programming</category>
</item>
<item>
<title>Why I choose Obsidian over Logseq</title>
<link>https://nafn.de/blog/obsidian</link>
<guid isPermaLink="true">https://nafn.de/blog/obsidian</guid>
<pubDate>Mon, 25 Apr 2022 00:00:00 +0000</pubDate>
<description>I started with logseq but it just isn&apos;t for me at the moment.</description>
<content:encoded><![CDATA[<p>I spent one or two days with logseq because a lot of people on the internet
recommended it.</p>
<p>Initially I found my way into this bubble via the
<a href="sprachnachrichten.fm">sprachnachrichten</a> podcast and a <a href="https://www.youtube.com/watch?v=L9SLlxaEEXY">youtube
video</a> where a person explained
their way of &quot;taking notes&quot;.</p>
<p>Let's start with the things I immediately didn't like about logseq:</p>
<ul>
<li>Promotes &quot;privacy-first&quot; but you have to manually disable tracking for
multiple error-tracking-application-analytics-monitoring-sites after the first
start</li>
<li>You also have to disable tracking on every device separately</li>
<li>Has keyboard shortcuts that just don't work for me. E.g. <code>🍎+,</code> is not working</li>
<li>In general it feels way too mouse-centric</li>
<li>vim mode is available via a plugin which makes everything a bit more
complicated</li>
<li>Does not allow to disable auto pairing for brackets and back-ticks (I even had
to <a href="https://somar.nafn.de/computerfoo/status/1518562147072159744/">tweet about
it</a>)</li>
</ul>
<p>Let's compare it to what I like about Obsidian:</p>
<ul>
<li>Has a working vim mode included</li>
<li>Only opens one connection to <code>releases.obsidian.md</code> when opening the application</li>
<li>Has decent keyboard shortcuts that make sense to me</li>
<li>Allows to disable auto pairing for brackets and back-ticks</li>
</ul>
<p>After fifteen minutes with obisidian I was ready to pay them even tough it is
free for personal use.</p>
]]></content:encoded>
<category>obsidian</category>
<category>notes</category>
<category>knowledge-management</category>
<category>productivity</category>
</item>
<item>
<title>Howto configure static IPv4 on FreeBSD on Hetzner Cloud</title>
<link>https://nafn.de/blog/freebsd-static-ip-on-hetzner-cloud</link>
<guid isPermaLink="true">https://nafn.de/blog/freebsd-static-ip-on-hetzner-cloud</guid>
<pubDate>Tue, 22 Mar 2022 00:00:00 +0000</pubDate>
<description>Something that should be easy is sometimes not that straight forward.</description>
<content:encoded><![CDATA[<h2>Abstract</h2>
<p>One would think that it is quite easy to configure a static IPv4 address on a
hetzner cloud server on FreeBSD, but that is not the case. It's not hard either,
but took me a couple of minutes to understand what the issue is.</p>
<p>The <a href="https://docs.hetzner.com/cloud/servers/static-configuration/#freebsd">documentation</a>
contains a paragraph about this topic but only mentions <code>DHCP</code> for IPv4:</p>
<pre><code class="makeup bash"><span class="nv">ifconfig_vtnet0</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">DHCP</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">ifconfig_vtnet0_ipv6</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">inet6 &lt;one IPv6 address from your subnet, e.g.  2001:db8:0:3df1::1&gt;/64</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">ipv6_defaultrouter</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">fe80::1%vtnet0</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<p>They did the same for NetBSD, so my guess was that this is an uncommon configuration.</p>
<p>This post assumes the following:</p>
<ul>
<li>IPv4 address of your host: <code>1.2.3.4/32</code></li>
<li>IPv4 address of the gateway: <code>172.31.1.1</code></li>
<li>Network interface: <code>vtnet0</code></li>
</ul>
<h2>Issue</h2>
<p>Without the static route, you'll run into the following error message when
trying to add the default gateway.</p>
<pre><code class="makeup txt"><span class="">root@hetzner-cloud-test:~ # route add default 172.31.1.1
</span><span class="">route: writing to routing socket: Network is unreachable
</span><span class="">add net default: gateway 172.31.1.1 fib 0: Network is unreachable
</span></code></pre>
<h2>Howto</h2>
<p>Usually it is enough to configure your <code>/etc/rc.conf</code> to something like:</p>
<pre><code class="makeup bash"><span class="nv">ifconfig_vtnet0</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">inet 1.2.3.4 netmask 255.255.255.255</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">defaultrouter</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">172.31.1.1</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<p>But, to get around the <code>Network is unreachable</code> error, we also need to create a
static route like so:</p>
<pre><code class="makeup bash"><span class="nv">static_routes</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">vtnet0</span><span class="p">&quot;</span><span class="w">
</span><span class="nv">route_vtnet0</span><span class="o">=</span><span class="p">&quot;</span><span class="s2">172.31.1.1 -iface vtnet0</span><span class="p">&quot;</span><span class="w">
</span></code></pre>
<p>That's it. Reboot and everything should work.</p>
<p>Alternatively, to make it work at runtime, run the following commands:</p>
<pre><code class="makeup bash"><span class="nf">ifconfig</span><span class="n"> vtnet0 inet 1.2.3.4 netmask 255.255.255.255</span><span class="w">
</span><span class="nf">route</span><span class="n"> add 172.31.1.1</span><span class="nv"> -</span><span class="nv">iface</span><span class="n"> vtnet0</span><span class="w">
</span><span class="nf">route</span><span class="n"> add default 172.31.1.1</span><span class="w">
</span></code></pre>
]]></content:encoded>
<category>freebsd</category>
<category>hetzner</category>
<category>networking</category>
<category>ipv4</category>
</item>
<item>
<title>Cancel/Undo a pending modifcation on AWS RDS</title>
<link>https://nafn.de/blog/cancel-aws-rds-pending-modification</link>
<guid isPermaLink="true">https://nafn.de/blog/cancel-aws-rds-pending-modification</guid>
<pubDate>Mon, 06 Jan 2020 00:00:00 +0000</pubDate>
<description>Canceling a pending modification isn&apos;t described all that well in the AWS documentation, so gave it a try and wrote it down.</description>
<content:encoded><![CDATA[<p>Canceling a pending modification isn't described all that well in the <a href="https://github.com/awsdocs/amazon-rds-user-guide/commit/089debd0c38d41835e9ba8cb4abaab511dce19cc#diff-655ea3cbce997ee63c7da8062ee54e1fR24">AWS
documentation</a>,
so gave it a try and <a href="https://stackoverflow.com/questions/51679351/how-do-you-remove-undo-delete-a-pending-modification-on-amazon-aurora-rds">wrote it
down</a>.</p>
<p>I tested it multiple times with RDS/Aurora instances and it works as expected,
even without the <code>--apply-immediately</code> parameter (at least for the instance
class modifications).</p>
<h2>Full Example</h2>
<p>Let's change the instance class for an Aurora instance called
<code>database-2-instance-1</code> from <code>db.t3.medium</code> to <code>db.r4.large</code> and undo it again
afterwards. None of these commands do impact the availability of the database.</p>
<p>Note: Using <code>jq</code> here to only output the important parts.</p>
<h3>Check instance class</h3>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> rds describe-db-instances</span><span class="nv"> --</span><span class="nv">db-instance-identifier</span><span class="n"> database-2-instance-1</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">jq</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">.DBInstances[].DBInstanceClass</span><span class="p">&#39;</span><span class="w">
</span><span class="nf">&quot;</span><span class="nf">db.t3.medium</span><span class="nf">&quot;</span><span class="w">
</span></code></pre>
<h3>Validate that there is no pending modification</h3>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> rds describe-db-instances</span><span class="nv"> --</span><span class="nv">db-instance-identifier</span><span class="n"> database-2-instance-1</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">jq</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">.DBInstances[].PendingModifiedValues</span><span class="p">&#39;</span><span class="w">
</span><span class="nf">{</span><span class="n">}
</span></code></pre>
<h3>Modify instance class</h3>
<p>This modification will result in a change of the instance class in the next
maintenance window.</p>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> rds modify-db-instance</span><span class="nv"> --</span><span class="nv">db-instance-identifier</span><span class="n"> database-2-instance-1</span><span class="nv"> --</span><span class="nv">db-instance-class</span><span class="n"> db.r4.large</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">jq</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">.DBInstance.PendingModifiedValues</span><span class="p">&#39;</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="w">  </span><span class="nf">&quot;</span><span class="nf">DBInstanceClass</span><span class="nf">&quot;</span><span class="nf">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">db.r4.large</span><span class="p">&quot;</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<h3>Validate again</h3>
<p>Just to be sure, check if everything looks as expected.</p>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> rds describe-db-instances</span><span class="nv"> --</span><span class="nv">db-instance-identifier</span><span class="n"> database-2-instance-1</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">jq</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">.DBInstances[].PendingModifiedValues</span><span class="p">&#39;</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="w">  </span><span class="nf">&quot;</span><span class="nf">DBInstanceClass</span><span class="nf">&quot;</span><span class="nf">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">db.r4.large</span><span class="p">&quot;</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<h3>Undo modify of instance class</h3>
<p>This is the important part which modifies the instance class back to the old
value. The documentation describes that a <code>--apply-immediately</code> is required, but
it turns out that that is not the case. At least in this example.</p>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> rds modify-db-instance</span><span class="nv"> --</span><span class="nv">db-instance-identifier</span><span class="n"> database-2-instance-1</span><span class="nv"> --</span><span class="nv">db-instance-class</span><span class="n"> db.t3.medium</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">jq</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">.DBInstance.PendingModifiedValues</span><span class="p">&#39;</span><span class="w">
</span><span class="nf">{</span><span class="n">}
</span></code></pre>
<h3>Validate the removed pending modification</h3>
<pre><code class="makeup bash"><span class="nf">aws</span><span class="n"> rds describe-db-instances</span><span class="nv"> --</span><span class="nv">db-instance-identifier</span><span class="n"> database-2-instance-1</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">jq</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">.DBInstances[].PendingModifiedValues</span><span class="p">&#39;</span><span class="w">
</span><span class="nf">{</span><span class="n">}
</span></code></pre>
<p>At this point the pending modification is gone.</p>
]]></content:encoded>
<category>aws</category>
<category>rds</category>
<category>postgresql</category>
<category>operations</category>
</item>
<item>
<title>Repair a broken time machine backup</title>
<link>https://nafn.de/blog/timemachine-backup-repair</link>
<guid isPermaLink="true">https://nafn.de/blog/timemachine-backup-repair</guid>
<pubDate>Sun, 05 Jan 2020 00:00:00 +0000</pubDate>
<description>Failing time machine backups are happening quite often. In this post I wrote down how to fix all of the errors that happened to me so far.</description>
<content:encoded><![CDATA[<h2>Abstract</h2>
<p>Over time a time machine backup will throw errors that either state that the
backup cannot be done because of various reasons or the backup needs to be
re-created.</p>
<p>I fixed all errors with the following steps and need to write them down to
prevent me from searching for this information again in the future.</p>
<h3>Error messages</h3>
<h4>Recreate backup</h4>
<pre><code class="makeup txt"><span class="">Time Machine completed a verification of your backups.
</span><span class="">To improve reliability, Time Machine must create a new backup for you.
</span></code></pre>
<h4>Backup cannot be done</h4>
<pre><code class="makeup txt"><span class="">Time Machine could not complete the backup. (error 45)
</span></code></pre>
<h2>Solution</h2>
<p>To speed things up it is recommended to copy the <code>backupbundle</code> (formerly
<code>sparsebundle</code>) file to your workstation, attach the volume and then run the
disk repair command.</p>
<h3>Attach the volume</h3>
<pre><code class="makeup txt"><span class="">$ hdiutil attach -nomount -readwrite foobar.backupbundle
</span><span class="">/dev/disk2              GUID_partition_scheme
</span><span class="">/dev/disk2s1            EFI
</span><span class="">/dev/disk2s2            Apple_HFS
</span></code></pre>
<p>Remember the value of the first column on the the <code>Apple_HFS</code> line!
(<code>/dev/disk2s2</code>)</p>
<p>This is the partition we're going to repair.</p>
<h3>Run the disk repair command</h3>
<pre><code class="makeup txt"><span class="">fsck_hfs -rf /dev/disk2s2
</span></code></pre>
<p><code>fsck_hfs</code> is the &quot;HFS file system consistency check&quot; tool.</p>
<ul>
<li><code>-r</code>: Rebuild the catalog btree.</li>
<li><code>-f</code>: Force fsck_hfs to check and repair journaled HFS+
file systems.</li>
</ul>
<p>The command takes ages to complete and might look something like this:</p>
<pre><code class="makeup txt"><span class="">$ fsck_hfs -rf /dev/disk2s2
</span><span class="">** /dev/rdisk2s2
</span><span class="">   Executing fsck_hfs (version hfs-522.0.9).
</span><span class="">** Checking Journaled HFS Plus volume.
</span><span class="">** Detected a case-sensitive volume.
</span><span class="">   The volume name is Time Machine Backups
</span><span class="">** Checking extents overflow file.
</span><span class="">** Checking catalog file.
</span><span class="">** Rebuilding catalog B-tree.
</span><span class="">** Rechecking volume.
</span><span class="">** Checking Journaled HFS Plus volume.
</span><span class="">** Detected a case-sensitive volume.
</span><span class="">   The volume name is Time Machine Backups
</span><span class="">** Checking extents overflow file.
</span><span class="">** Checking catalog file.
</span><span class="">** Checking multi-linked files.
</span><span class="">** Checking catalog hierarchy.
</span><span class="">** Checking extended attributes file.
</span><span class="">** Checking multi-linked directories.
</span><span class="">** Checking volume bitmap.
</span><span class="">** Checking volume information.
</span><span class="">   Invalid volume file count
</span><span class="">   (It should be 10384127 instead of 10079428)
</span><span class="">   Invalid volume directory count
</span><span class="">   (It should be 1557312 instead of 1514027)
</span><span class="">   Invalid volume free block count
</span><span class="">   (It should be 370750423 instead of 376119386)
</span><span class="">   Volume header needs minor repair
</span><span class="">(2, 0)
</span><span class="">** Repairing volume.
</span><span class="">** Rechecking volume.
</span><span class="">** Checking Journaled HFS Plus volume.
</span><span class="">** Detected a case-sensitive volume.
</span><span class="">   The volume name is Time Machine Backups
</span><span class="">** Checking extents overflow file.
</span><span class="">** Checking catalog file.
</span><span class="">** Checking multi-linked files.
</span><span class="">** Checking catalog hierarchy.
</span><span class="">** Checking extended attributes file.
</span><span class="">** Checking multi-linked directories.
</span><span class="">** Checking volume bitmap.
</span><span class="">** Checking volume information.
</span><span class="">** Trimming unused blocks.
</span><span class="">** The volume Time Machine Backups was repaired successfully.
</span><span class="">$
</span></code></pre>
<p>Now you need to detach the volume and move it back to the location
where it came from (external disk, NAS ...).</p>
<h2>Detach volume</h2>
<pre><code class="makeup txt"><span class="">$ hdiutil detach /dev/disk2
</span><span class="">&quot;disk2&quot; ejected.
</span><span class="">$
</span></code></pre>
<h2>Reset plist</h2>
<p>If your error message states that you need to create a new backup, you must also
change two files inside the <code>backupbundle</code>/<code>sparsebundle</code> file.</p>
<p>Inside the bundle file, you'll find these files:</p>
<ul>
<li>com.apple.TimeMachine.MachineID.plist</li>
<li>com.apple.TimeMachine.MachineID.bckup</li>
</ul>
<p>In both files, you'll need to remove the following two lines (if they're
present):</p>
<pre><code class="makeup xml"><span class="w">        </span><span class="p">&lt;</span><span class="nt">key</span><span class="p">&gt;</span><span class="n">RecoveryBackupDeclinedDate</span><span class="p">&lt;/</span><span class="nt">key</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nt">date</span><span class="p">&gt;</span><span class="n">2020-01-04T09:41:40Z</span><span class="p">&lt;/</span><span class="nt">date</span><span class="p">&gt;</span><span class="w">
</span></code></pre>
<p>You'll also need to change the following line:</p>
<p>Before:</p>
<pre><code class="makeup xml"><span class="w">        </span><span class="p">&lt;</span><span class="nt">key</span><span class="p">&gt;</span><span class="n">VerificationState</span><span class="p">&lt;/</span><span class="nt">key</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nt">integer</span><span class="p">&gt;</span><span class="n">2</span><span class="p">&lt;/</span><span class="nt">integer</span><span class="p">&gt;</span><span class="w">
</span></code></pre>
<p>After:</p>
<pre><code class="makeup xml"><span class="w">        </span><span class="p">&lt;</span><span class="nt">key</span><span class="p">&gt;</span><span class="n">VerificationState</span><span class="p">&lt;/</span><span class="nt">key</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nt">integer</span><span class="p">&gt;</span><span class="n">0</span><span class="p">&lt;/</span><span class="nt">integer</span><span class="p">&gt;</span><span class="w">
</span></code></pre>
<h2>Done</h2>
<p>Good luck.</p>
]]></content:encoded>
<category>macos</category>
<category>timemachine</category>
<category>backup</category>
<category>troubleshooting</category>
</item>
<item>
<title>SQL injection on gruenberg.de</title>
<link>https://nafn.de/blog/infosec-sql-injection-gruenberg.de</link>
<guid isPermaLink="true">https://nafn.de/blog/infosec-sql-injection-gruenberg.de</guid>
<pubDate>Sun, 14 Apr 2019 00:00:00 +0000</pubDate>
<description>The CMS login on gruenberg.de contained a SQL injection vulnerability.</description>
<content:encoded><![CDATA[<p>In the progress of gathering a list of all <a href="https://somar.nafn.de/computerfoo/status/993538695633530880/">websites of towns and
cities</a> in Germany, I
see a lot of old websites that contain more or less obvious vulnerabilities.</p>
<p>On 01.04.2019 I found a SQL injection vulnerability on the CMS login page of the
gruenberg.de website and immediately forwarded this information to the <a href="https://innen.hessen.de/sicherheit/hessen3c/cert">CERT
Hessen</a>.</p>
<p>I tend to contact the corresponding CERT instead of the owner of the
website, because it's hard to deal with them in most cases. Sometimes they do
not answer at all, sometimes they just ignore you and the issue, sometimes they
do not accept emails from outside their domain (this happened multiple times
already!) and most of the time they do not understand what you want from them.</p>
<h3>Timeline</h3>
<ul>
<li>01.04.2019: Found issue and contacted CERT Hessen</li>
<li>02.04.2019: CERT Hessen acknowledged the issue</li>
<li>10.04.2019: CERT Hessen informed me about the issue beeing fixed</li>
</ul>
<h3>CERT Hessen rating</h3>
<p>The CERT Hessen is easy to contact because everything you need you can find with
a simple google search, they react fast and inform you as soon as the problem is
fixed. That's an awesome experience when dealing with this kind of situation
where it is not clear what is going to happen.</p>
]]></content:encoded>
<category>security</category>
<category>sql-injection</category>
<category>web</category>
</item>
<item>
<title>Terraform - Infrastructure as code</title>
<link>https://nafn.de/blog/terraform-synyx</link>
<guid isPermaLink="true">https://nafn.de/blog/terraform-synyx</guid>
<pubDate>Wed, 21 Mar 2018 00:00:00 +0000</pubDate>
<description>Anfang des Jahres war ich bei Synyx, um einen kurzen Vortrag ueber Terraform zu halten.</description>
<content:encoded><![CDATA[<p>Anfang des Jahres war ich bei <a href="https://synyx.de/">Synyx</a> in Karlsruhe zu besuch
und konnte dort einen kurzen Vortrag ueber
<a href="https://www.terraform.io/">Terraform</a> halten.</p>
<p>Aus dem Abstract:</p>
<blockquote>
<p>Terraform erlaubt es dir Infrastruktur in Code abzubilden und diesen Code auf
verschiedene Anbieter anzuwenden.</p>
</blockquote>
<blockquote>
<p>Die Möglichkeit, versionierten Code als Lageplan einer Infrastruktur vorzuhalten,
ist ein Mehrgewinn der seinesgleichen sucht.
Egal ob die gesamte Infrastruktur bei AWS liegt oder ueber mehrere Anbieter verteilt
ist: terraform ist hier eine helfende Hand, um die steigende Komplexitaet ansatzweise
im Griff zu behalten.</p>
</blockquote>
<p>Und da bei Synyx keine halben Sachen gemacht werden, wurde das ganze auch direkt
aufgezeichnet. Danke vielmals fuer diese Erfahrung!</p>
<p>Aufzeichnung: <a href="https://www.youtube.com/watch?v=Ki1mJTmQhqM">https://www.youtube.com/watch?v=Ki1mJTmQhqM</a> <br />
Code+Folien: <a href="https://github.com/temal-/terraform_talk">https://github.com/temal-/terraform_talk</a></p>
]]></content:encoded>
<category>terraform</category>
<category>iac</category>
<category>devops</category>
<category>synyx</category>
</item>
<item>
<title>Simulation of the future</title>
<link>https://nafn.de/blog/simulation-of-the-future</link>
<guid isPermaLink="true">https://nafn.de/blog/simulation-of-the-future</guid>
<pubDate>Thu, 09 Jul 2015 00:00:00 +0000</pubDate>
<description>For the course &apos;Simulation of the future&apos;, my team had to develop a simulation based on a traveling sun and schadows.</description>
<content:encoded><![CDATA[<p>After three weeks of procrastination and a full week of <a href="http://threejs.org">Threejs</a>, <a href="https://github.com/Darot">Daniel</a>
and me finaly finished a 3D simulation of &quot;the sun&quot;.</p>
<p>The simulation is available <a href="https://nafn.de/simdezuk">here</a> and the code is on <a href="https://github.com/temal-/sunsimulation">github</a>.</p>
]]></content:encoded>
<category>threejs</category>
<category>sun</category>
<category>shadow</category>
<category>simdezuk</category>
</item>
<item>
<title>Update a childpage (subpage) in confluence via the REST-API</title>
<link>https://nafn.de/blog/confluence-update-child-page</link>
<guid isPermaLink="true">https://nafn.de/blog/confluence-update-child-page</guid>
<pubDate>Tue, 08 Jul 2014 00:00:00 +0000</pubDate>
<description>Couldn&apos;t find any documentation about this feature, so here it is.</description>
<content:encoded><![CDATA[<p>The magic lies in the <a href="https://docs.atlassian.com/atlassian-confluence/REST/latest/">undocumented</a> parameter &quot;ancestors&quot;.</p>
<p>Update a page via curl:</p>
<pre><code class="makeup bash"><span class="nf">curl</span><span class="nv"> -</span><span class="nv">u</span><span class="w"> </span><span class="nv">$</span><span class="nv">user</span><span class="nv"> -</span><span class="nv">H</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">Content-Type: application/json</span><span class="p">&#39;</span><span class="nv"> -</span><span class="nv">d</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">{&quot;id&quot;:&quot;1234567890&quot;,&quot;type&quot;:&quot;page&quot;,&quot;title&quot;:&quot;Stuff&quot;,&quot;space&quot;:{&quot;key&quot;:&quot;SPACE&quot;},&quot;body&quot;:{&quot;storage&quot;:{&quot;value&quot;:&quot; foo &quot;,&quot;representation&quot;:&quot;storage&quot;}},&quot;version&quot;:{&quot;number&quot;:7}</span><span class="p">&#39;</span><span class="n"> https://confluence.example.com/rest/api/content/1234567890</span><span class="w">
</span></code></pre>
<p>Update a child page via curl:</p>
<pre><code class="makeup bash"><span class="nf">curl</span><span class="nv"> -</span><span class="nv">u</span><span class="w"> </span><span class="nv">$</span><span class="nv">user</span><span class="nv"> -</span><span class="nv">H</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">Content-Type: application/json</span><span class="p">&#39;</span><span class="nv"> -</span><span class="nv">d</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">{&quot;id&quot;:&quot;1234567890&quot;,&quot;type&quot;:&quot;page&quot;,&quot;ancestors&quot;:[{&quot;type&quot;:&quot;page&quot;,&quot;id&quot;:987654321}],&quot;title&quot;:&quot;Stuff&quot;,&quot;space&quot;:{&quot;key&quot;:&quot;SPACE&quot;},&quot;body&quot;:{&quot;storage&quot;:{&quot;value&quot;:&quot; foo &quot;,&quot;representation&quot;:&quot;storage&quot;}},&quot;version&quot;:{&quot;number&quot;:7}</span><span class="p">&#39;</span><span class="n"> https://confluence.example.com/rest/api/content/1234567890</span><span class="w">
</span></code></pre>
<p>via <a href="https://stackoverflow.com/questions/23523705/how-to-create-new-page-in-confluence-using-their-rest-api#comment36097279_23526357">Scott Dudley</a></p>
]]></content:encoded>
<category>confluence</category>
<category>api</category>
<category>rest</category>
</item>
<item>
<title>Fast packaging of deb or rpm with ease and fpm.</title>
<link>https://nafn.de/blog/fpm</link>
<guid isPermaLink="true">https://nafn.de/blog/fpm</guid>
<pubDate>Mon, 10 Mar 2014 00:00:00 +0000</pubDate>
<description>Packaging multiple formats for different linux distributions isn&apos;t as easy as it should be. Till now.</description>
<content:encoded><![CDATA[<p>Do you know this?</p>
<pre><code class="makeup bash"><span class="nf">$</span><span class="n"> curl</span><span class="nv"> -</span><span class="nv">O</span><span class="n"> https://foo.com/bar-1.23.112-alpha-RC0.1.tar.gz</span><span class="w">
</span><span class="nf">$</span><span class="n"> tar xzf bar</span><span class="o">*</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nf">cd</span><span class="n"> bar</span><span class="o">*</span><span class="w">
</span><span class="nf">$</span><span class="n"> ./configure</span><span class="w">
</span><span class="nf">$</span><span class="n"> make</span><span class="w">
</span><span class="nf">$</span><span class="n"> sudo make install</span><span class="w">
</span></code></pre>
<p>Then, I guess, you also know this.</p>
<pre><code class="makeup bash"><span class="nf">$</span><span class="n"> sudo make uninstall</span><span class="w">
</span><span class="nf">make:</span><span class="w"> </span><span class="o">*</span><span class="o">*</span><span class="o">*</span><span class="n"> No rule to make target </span><span class="p">`</span><span class="nf">uninstall</span><span class="nf">&#39;</span><span class="nf">.  Stop.
</span></code></pre>
<p>To prevent this, you could use <a href="https://github.com/jordansissel/fpm">fpm</a> to create a package for your
distribution which is way easier to manage.</p>
<p>After a</p>
<pre><code class="makeup bash"><span class="nf">$</span><span class="n"> make install DESTDIR=/tmp/pkg</span><span class="w">
</span></code></pre>
<p>you can create a deb or rpm with
a:</p>
<pre><code class="makeup bash"><span class="nf">$</span><span class="n"> fpm</span><span class="nv"> -</span><span class="nv">s</span><span class="n"> dir </span><span class="p">\
</span><span class="nv">    -</span><span class="nv">t</span><span class="n"> deb </span><span class="p">\
</span><span class="nv">    -</span><span class="nv">n</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">git</span><span class="p">&quot;</span><span class="w"> </span><span class="p">\
</span><span class="nv">    -</span><span class="nv">v</span><span class="n"> 1.9.0 </span><span class="p">\
</span><span class="nv">    --</span><span class="nv">prefix</span><span class="n"> /usr/local </span><span class="p">\
</span><span class="nv">    -</span><span class="nv">C</span><span class="n"> /tmp/pkg </span><span class="p">\
</span><span class="n">    bin lib libexec share</span><span class="w">
</span><span class="nf">Created</span><span class="n"> deb package </span><span class="p">{</span><span class="n">:path=&gt;</span><span class="p">&quot;</span><span class="s2">git_1.9.0_amd64.deb</span><span class="p">&quot;</span><span class="p">}</span><span class="w">
</span></code></pre>
<ul>
<li>&quot;-s dir&quot; defines the source as a directory.</li>
<li>&quot;-t deb&quot; defines the target as a debian package.</li>
<li>&quot;-n 'git'&quot; defines the name of the package.</li>
<li>&quot;-v 1.9.0&quot; defines the version of the package.</li>
<li>&quot;--prefix /usr/local&quot; defines a prefix for all files installed from the created
package</li>
<li>&quot;-C /tmp/pkg&quot; defines a path to which fpm changes before searching for the
folders at the end of the command.</li>
<li>&quot;bin lib libexec share&quot; defines the names of the directories to pack into the
package.</li>
</ul>
<p>This command creates a deb in your <code>$PWD</code> named <code>git_1.9.0_amd64.deb</code>.
If you want a rpm, just change <code>-t deb</code> to <code>-t rpm</code>.</p>
<p>Thanks a lot to Jordan Sissel for this timesaver!</p>
]]></content:encoded>
<category>deb</category>
<category>rpm</category>
<category>fpm</category>
<category>linux</category>
</item>
<item>
<title>Visualization of bike distribution over Frankfurts boroughs.</title>
<link>https://nafn.de/blog/visualize-bike-data-of-frankfurt</link>
<guid isPermaLink="true">https://nafn.de/blog/visualize-bike-data-of-frankfurt</guid>
<pubDate>Wed, 12 Feb 2014 00:00:00 +0000</pubDate>
<description>After finding the API-Endpoints for one of germanies bike rental service, I started gathering some data for the area of Frankfurt/Main and visualized it.</description>
<content:encoded><![CDATA[<p>I was a big fan of &quot;Rent a bike&quot;-offers in germany for the longest time.
Nowadays it's not as comfortable as it was a couple of years ago, because of
the fee you have to pay if you park a bike at a location other than
the predefined ones. Nonetheless it's a fun project for me. (BTW: It seems that
the rest of the world uses a service from <a href="http://www.citybik.es/">CityBikes</a> which is already
visualized by <a href="http://ramnathv.github.io/bikeshare/">Ramnath Vaidyanathan</a>)</p>
<p>In a night shift, in mid 2012, I played around with a couple of apps on my
mobile, a http proxy (<a href="http://mitmproxy.org/">mitmproxy</a>) and tcpdump to find a way to MITM some of
these apps.</p>
<p>After a dozen of apps (most of them failed because of strict certificate
checks on the HTTPS front) and way to much coffee, my shift was going to an end.
So I started my favorite bike-renting app and looked for the nearest bike which
should bring me home.
After finding one, I noticed that I still had a proxy configured on my phone
and that the app didn't even remotely complain about the wrong certificate
(this is fixed now). Jackpot ... I thought ... and went home to bed because it
was 8am and I was tired enough. But that should be a good starting point for
some &quot;data collection and visualization&quot;.</p>
<p>The next night started with some traffic analyzing and a fast discovery of a
pair of credentials for the (more or less) public API endpoint. &quot;More or
less&quot;, because it needs some very basic authentication, based on your mobile
device. You'll know what I mean if you look at this snippit from the
<a href="https://github.com/Ik4ru5/pyABike">Ik4ru5/pyABike</a> repository.</p>
<pre><code class="makeup python"><span class="w">    </span><span class="n">userData</span><span class="p">.</span><span class="n">User</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">t_cab_android</span><span class="p">&#39;</span><span class="w"> </span><span class="c1">#</span><span class="c1">from android app
</span><span class="w">    </span><span class="n">userData</span><span class="p">.</span><span class="n">Password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">DELETED TO PREVENT PROBLEMS</span><span class="p">&#39;</span><span class="w"> </span><span class="c1">#</span><span class="c1">from android app
</span></code></pre>
<p>Source: <a href="https://github.com/Ik4ru5/pyABike/blob/master/PyABike.py#L46">Github</a></p>
<p>And since I have an iPhone, the credentials on my phone are a bit different.
Here is a SOAP-Request I use to gather my data. And since I only need one API
endpoint for my queries, I didn't start bothering with a python SOAP library
and just copy/paste the query from my phones traffic.</p>
<pre><code class="makeup python"><span class="w">  </span><span class="kt">def</span><span class="w"> </span><span class="nf">createRequest</span><span class="p">(</span><span class="nv">self</span><span class="p">,</span><span class="w"> </span><span class="nv">lati</span><span class="p">,</span><span class="w"> </span><span class="nv">longi</span><span class="p">,</span><span class="w"> </span><span class="nv">radius</span><span class="p">,</span><span class="w"> </span><span class="nv">amount</span><span class="p">)</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="sd">&quot;&quot;&quot;</span><span class="sd"> Returns an XML-Set of all the bikes around the specific coordinates </span><span class="sd">&quot;&quot;&quot;</span><span class="w">
</span><span class="w">    </span><span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">&quot;&quot;&quot;</span><span class="s2">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
</span><span class="s2">    &lt;SOAP:Envelope xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:SOAP=&quot;http://schemas.xmlsoap.org/soap/envelope/&quot;&gt;
</span><span class="s2">    &lt;SOAP:Body&gt;
</span><span class="s2">    &lt;foo.listFreeBikes xmlns=&quot;https://xml.dbcarsharing-buchung.de/hal2_cabserver/&quot;&gt;
</span><span class="s2">    &lt;CommonParams&gt;
</span><span class="s2">    &lt;UserData&gt;
</span><span class="s2">    &lt;User&gt;t_iphone_cab&lt;/User&gt;
</span><span class="s2">    &lt;Password&gt;DELETED TO PREVENT PROBLEMS&lt;/Password&gt;
</span><span class="s2">    &lt;/UserData&gt;
</span><span class="s2">    &lt;LanguageUID&gt;1&lt;/LanguageUID&gt;
</span><span class="s2">    &lt;RequestTime&gt;</span><span class="no">%s</span><span class="s2">&lt;/RequestTime&gt;
</span><span class="s2">    &lt;Version&gt;2&lt;/Version&gt;
</span><span class="s2">    &lt;/CommonParams&gt;
</span><span class="s2">    &lt;SearchPosition&gt;
</span><span class="s2">    &lt;Longitude&gt;</span><span class="no">%.6f</span><span class="s2">&lt;/Longitude&gt;
</span><span class="s2">    &lt;Latitude&gt;</span><span class="no">%.6f</span><span class="s2">&lt;/Latitude&gt;
</span><span class="s2">    &lt;/SearchPosition&gt;
</span><span class="s2">    &lt;maxResults&gt;</span><span class="no">%i</span><span class="s2">&lt;/maxResults&gt;
</span><span class="s2">    &lt;searchRadius&gt;</span><span class="no">%i</span><span class="s2">&lt;/searchRadius&gt;
</span><span class="s2">    &lt;/foo.listFreeBikes&gt;
</span><span class="s2">    &lt;/SOAP:Body&gt;
</span><span class="s2">    &lt;/SOAP:Envelope&gt;</span><span class="p">&quot;&quot;&quot;</span><span class="o">%</span><span class="w"> </span><span class="p">(</span><span class="n">datetime</span><span class="p">.</span><span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">(</span><span class="p">)</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="p">&quot;</span><span class="no">%Y</span><span class="s2">-</span><span class="no">%m</span><span class="s2">-</span><span class="no">%d</span><span class="s2">T</span><span class="no">%H</span><span class="s2">:</span><span class="no">%M</span><span class="s2">:</span><span class="no">%S</span><span class="p">&quot;</span><span class="p">)</span><span class="p">,</span><span class="w"> </span><span class="n">longi</span><span class="p">,</span><span class="w"> </span><span class="n">lati</span><span class="p">,</span><span class="w"> </span><span class="n">amount</span><span class="p">,</span><span class="w"> </span><span class="n">radius</span><span class="p">)</span><span class="w">
</span><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">data</span><span class="w">
</span></code></pre>
<p>Do you see the professional consistency of the usernames?
&quot;t_cab_android&quot; vs &quot;t_iphone_cab&quot;. Amazing ...</p>
<p>To make it clear: These credentials are only useful to query the backend for
bike-data and not user-data.</p>
<p>Nonetheless, I used a couple selfmade python modules to gather this data
periodically and save it to a database.</p>
<p>The application logic is very simple:</p>
<p>Look at a certian position (longitude, latitude) for bikes in a range of <strong>n</strong>
meters.
If a bike is found, check the database for an entry of that bike. If we don't
have an entry for that bike, we've never seen it before and create a new entry.
If - on the other hand - we know that bike already, we set the current position
and add the last position to the history.</p>
<p>With this little logic we can keep track of every bike for however long we're
interested in it and it's path through the city.</p>
<p>And here is an entry of a bike in the database:</p>
<pre><code class="makeup javascript"><span class="o">&gt;</span><span class="w"> </span><span class="nv">db</span><span class="p">.</span><span class="n">bikes</span><span class="p">.</span><span class="nf">findOne</span><span class="p">(</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">_id</span><span class="p">&quot;</span><span class="n"> : </span><span class="nf">ObjectId</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">500c6b57ea0f273df592c45a</span><span class="p">&quot;</span><span class="p">)</span><span class="o">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">bikenr</span><span class="p">&quot;</span><span class="n"> : </span><span class="mi">6935</span><span class="o">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">currentPos</span><span class="p">&quot;</span><span class="n"> : </span><span class="p">[</span><span class="w">
</span><span class="w">                </span><span class="mf">50</span><span class="mf">.</span><span class="mf">10824</span><span class="p">,</span><span class="w">
</span><span class="w">                </span><span class="mf">8</span><span class="mf">.</span><span class="mf">672381</span><span class="w">
</span><span class="w">        </span><span class="p">]</span><span class="o">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">history</span><span class="p">&quot;</span><span class="n"> : </span><span class="p">[</span><span class="w">
</span><span class="w">                </span><span class="p">{</span><span class="w">
</span><span class="w">                        </span><span class="p">&quot;</span><span class="s2">pos</span><span class="p">&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">                                </span><span class="mf">50</span><span class="mf">.</span><span class="mf">121815</span><span class="p">,</span><span class="w">
</span><span class="w">                                </span><span class="mf">8</span><span class="mf">.</span><span class="mf">614588</span><span class="w">
</span><span class="w">                        </span><span class="p">]</span><span class="p">,</span><span class="w">
</span><span class="w">                        </span><span class="p">&quot;</span><span class="s2">time</span><span class="p">&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nf">ISODate</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">2012-07-23T00:16:10.997Z</span><span class="p">&quot;</span><span class="p">)</span><span class="w">
</span><span class="w">                </span><span class="p">}</span><span class="p">,</span><span class="w">
</span><span class="w">                </span><span class="p">{</span><span class="w">
</span><span class="w">                        </span><span class="p">&quot;</span><span class="s2">pos</span><span class="p">&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">                                </span><span class="mf">50</span><span class="mf">.</span><span class="mf">121986</span><span class="p">,</span><span class="w">
</span><span class="w">                                </span><span class="mf">8</span><span class="mf">.</span><span class="mf">614226</span><span class="w">
</span><span class="w">                        </span><span class="p">]</span><span class="p">,</span><span class="w">
</span><span class="w">                        </span><span class="p">&quot;</span><span class="s2">time</span><span class="p">&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nf">ISODate</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">2012-07-23T07:30:47.639Z</span><span class="p">&quot;</span><span class="p">)</span><span class="w">
</span><span class="w">                </span><span class="p">}</span><span class="p">,</span><span class="w">
</span><span class="w">                </span><span class="p">{</span><span class="w">
</span><span class="w">                        </span><span class="p">&quot;</span><span class="s2">pos</span><span class="p">&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="w">                                </span><span class="mf">50</span><span class="mf">.</span><span class="mf">1220567603</span><span class="p">,</span><span class="w">
</span><span class="w">                                </span><span class="mf">8</span><span class="mf">.</span><span class="mf">6142222359</span><span class="w">
</span><span class="w">                        </span><span class="p">]</span><span class="p">,</span><span class="w">
</span><span class="w">                        </span><span class="p">&quot;</span><span class="s2">time</span><span class="p">&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nf">ISODate</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">2012-07-23T07:55:46.248Z</span><span class="p">&quot;</span><span class="p">)</span><span class="w">
</span><span class="w">                </span><span class="p">}</span><span class="w">
</span><span class="w">        </span><span class="p">]</span><span class="o">,</span><span class="w">
</span><span class="w">        </span><span class="p">&quot;</span><span class="s2">lastseen</span><span class="p">&quot;</span><span class="n"> : </span><span class="nf">ISODate</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">2012-07-23T10:47:06.573Z</span><span class="p">&quot;</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="o">&gt;</span><span class="w"> </span><span class="nv">db</span><span class="p">.</span><span class="n">bikes</span><span class="p">.</span><span class="nf">count</span><span class="p">(</span><span class="p">)</span><span class="w">
</span><span class="mi">745</span><span class="w">
</span><span class="err">}</span><span class="w">
</span></code></pre>
<p>As you can see, the positions are sometimes nearly the same, so I guess this is
the 24h theft-protection of the bike, which sends it's position to the backend
once every day if it hasn't moved.</p>
<p>Finaly, a OpenStreetMap-leaflet combination to visualize the data:</p>
<p>Watch it <a href="https://nafn.de/projects/callabike-map/">here</a> in full. The Boroughs are colored by the amount of bikes
that are currently available and the heatmap shows some data about the requests
I made against the Bike-API. This data is <em>static</em>, as I don't want to have my
unsecure REST-API available on the internet.</p>
<p>Credit for the GeoJSON of the Frankfurt boroughs goes to the folks at
<a href="https://github.com/frankfurt-gestalten/">Frankfurt Gestalten</a> and <a href="http://www.dailymo.de/2013/11/14/geodaten-fuer-frankfurt-am-main-stadtteile-zum-download/">DailyMo</a>.
Thanks also to Ik4ru5, because without the knowledge of other people playing
with the Bike-API, I'd never have started to visualize the data.</p>
<p>If I'll ever find fun in this type of data again, I'd definitely use
ElasticSearch as a replacement for MongoDB and Kibana3 as a replacement for my
DIY-Leaflet map. Just to try it out.</p>
<p><strong>EDIT: I deleted all signs of brands and so on to save myself from problems.</strong></p>
]]></content:encoded>
<category>leaflet</category>
<category>mongodb</category>
<category>python</category>
<category>geojson</category>
</item>
</channel>
</rss>