<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Posts on Dennis Strtizke</title>
    <link>https://stritzke.me/posts/</link>
    <description>Recent content in Posts on Dennis Strtizke</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Tue, 30 Dec 2025 11:51:26 +0100</lastBuildDate>
    <atom:link href="https://stritzke.me/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Hetzner Request Limits</title>
      <link>https://stritzke.me/posts/2025/12/hetzner-request-limits/</link>
      <pubDate>Tue, 30 Dec 2025 11:51:26 +0100</pubDate>
      <guid>https://stritzke.me/posts/2025/12/hetzner-request-limits/</guid>
      <description>&lt;p&gt;Maybe don&amp;rsquo;t run &lt;code&gt;watch -n 0.5 hcloud server list&lt;/code&gt; to interactively monitor the status of your Hetzner servers&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;hetzner-request-limit.png&#34; alt=&#34;Hetzner Cloud Console request limit&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;I still recommend that technique for commands where you are not rate limited. Especially, when working with Kubernetes&#xA;a simple &lt;code&gt;watch -n 0.5 kubectl get pod&lt;/code&gt; will make a huge difference for visibility!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>We don&#39;t share infrastructure code</title>
      <link>https://stritzke.me/posts/2025/11/we-dont-share-infrastructure-code/</link>
      <pubDate>Thu, 27 Nov 2025 10:10:44 +0100</pubDate>
      <guid>https://stritzke.me/posts/2025/11/we-dont-share-infrastructure-code/</guid>
      <description>&lt;p&gt;At my software development company Kition Software, we don&amp;rsquo;t share infrastructure code with our clients. For a long time&#xA;I thought of it as our secret sauce, which has to be protected. But I came to realise that chefs of famous restaurants&#xA;share their recipes all the time and are still famous. So maybe my assumptions were wrong.&lt;/p&gt;&#xA;&lt;p&gt;We typically build web software for clients who are the ultimate owner of the code produced. We, on the other hand, own&#xA;and run the infrastructure through a managed service contract to ensure smooth operation. Therefore, we own all things&#xA;related to infrastructure and maintenance, like Kubernetes Manifests and the actual operations and maintenance&#xA;procedures.&lt;/p&gt;&#xA;&lt;p&gt;I just recently understood the deeper reason I am against sharing our infrastructure code. Sharing the infrastructure&#xA;code would be equivalent to the chef sharing how to run the restaurant itself. Even if you tried to replicate it by the&#xA;letter, you wouldn&amp;rsquo;t be able to. Your premises have a different layout, you have different staff, and you are not that&#xA;chef. Your restaurant would be different.&lt;/p&gt;&#xA;&lt;p&gt;Also, you don&amp;rsquo;t want to run a restaurant&amp;hellip; You want to eat a great meal!&lt;/p&gt;&#xA;&lt;p&gt;So, I am against sharing infrastructure code, because we are running a chain of restaurants, whereas you just want to&#xA;replicate cooking the meal for yourself. Even if you are highly capable of replicating the setup, you wouldn&amp;rsquo;t want to.&#xA;You want to have a simple straightforward setup, to run your single application. That&amp;rsquo;s a lot different from our&#xA;multi-application setup and processes.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s why we provide you with Dockerfiles, a Docker Compose stack, and the outline of basic maintenance&#xA;tasks—everything you need for your kitchen.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>You are able to know host keys before connecting?</title>
      <link>https://stritzke.me/posts/2025/11/you-are-able-to-know-host-keys-before-connecting/</link>
      <pubDate>Wed, 26 Nov 2025 18:12:51 +0100</pubDate>
      <guid>https://stritzke.me/posts/2025/11/you-are-able-to-know-host-keys-before-connecting/</guid>
      <description>&lt;p&gt;How often in your tech career have you just said &lt;code&gt;yes&lt;/code&gt; when being confronted with the following prompt?&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh 115.166.58.26&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;The authenticity of host &amp;#39;115.166.58.26 (6c22:3021:deb3:e62f::1)&amp;#39; can&amp;#39;t be established.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ED25519 key fingerprint is SHA256:Bm5pigTo5Xk2vJs4ecRw0Bt7mREt8zWjOScqFO+xAfw.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;This key is not known by any other names.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Are you sure you want to continue connecting (yes/no/[fingerprint])?&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you are anything like most developers, you probably thought &lt;em&gt;many&lt;/em&gt; times. I also did that. A LOT. But begrudgingly&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;I knew of the risks, but most of the time the tools to verify host keys properly were just not available to me. Let me&#xA;share a solution, I am using since quite a while to solve this conundrum.&lt;/p&gt;&#xA;&lt;h2 id=&#34;set-host-keys-via-cloud-init&#34;&gt;Set host keys via &lt;code&gt;cloud-init&lt;/code&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Basically all cloud provider support &lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/index.html&#34;&gt;cloud-init&lt;/a&gt; through which we can provide information&#xA;during creation of a VM. I have used the following on Digital Ocean and Hetzner via Debian, Ubuntu and Fedora. Most&#xA;providers and operating systems should have support, though.&lt;/p&gt;&#xA;&lt;p&gt;Create the host key via&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh-keygen -t ed25519 -C &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;our-production-host&amp;#34;&lt;/span&gt; -P &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt; -f key&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create the &lt;code&gt;cloud-config.yaml&lt;/code&gt; via&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat &amp;gt; cloud-config.yaml &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;&amp;lt;EOF&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#cloud-config&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;ssh_keys:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    ed25519_private: &amp;#34;$(awk &amp;#39;{printf &amp;#34;%s\\n&amp;#34;, $0}&amp;#39; key)&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    ed25519_public: &amp;#34;$(awk &amp;#39;{printf &amp;#34;%s&amp;#34;, $0}&amp;#39; key.pub)&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;ssh_deletekeys: false&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;EOF&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;which produces something like&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#cloud-config&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;ssh_keys&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;ed25519_private&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-----BEGIN OPENSSH PRIVATE KEY-----\n ... \n-----END OPENSSH PRIVATE KEY-----\n&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;ed25519_public&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ssh-ed25519 AAAA...6e70 our-production-host&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;ssh_deletekeys&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now you are able to know the SSH key beforehand!&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# in OpenSSH format&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh-keygen -yf key&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKiS7+VfuTVytSmgG2cmJPup4KhQxZ5InNTCraWeow4Z&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ... or as a fingerprint&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh-keygen -lf key&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;256&lt;/span&gt; SHA256:phjJalhzT4myMIpK9MqXNnnfE8mR/zeFeb4SyRckBY8 &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;ED25519&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We are even able to spare the manual verification step, if we add the key into our known_hosts file before connecting.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;23.192.228.84 &lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;ssh-keygen -yf key&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; ~/.ssh/known_hosts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;May your SSH connections be truly yours.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Conditional Redaction in Django Templates</title>
      <link>https://stritzke.me/posts/2025/10/conditional-redaction-in-django-templates/</link>
      <pubDate>Thu, 16 Oct 2025 12:39:31 +0200</pubDate>
      <guid>https://stritzke.me/posts/2025/10/conditional-redaction-in-django-templates/</guid>
      <description>&lt;p&gt;Today I needed to redact names of users and some other sensitive strings from a significant part of a Django&#xA;application. After starting to litter template code with &lt;code&gt;{% if allow_unredacted %}...{% else %}...{% endif %}&lt;/code&gt;&#xA;statements, I came to my senses and implemented a &lt;a href=&#34;https://docs.djangoproject.com/en/5.2/howto/custom-template-tags/&#34;&gt;Custom Django Template Tag&lt;/a&gt;. That tag&#xA;automatically redacts content based on the request. Nothing crazy, maybe it inspires you to implement something similar?&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-starting-point&#34;&gt;The Starting Point&lt;/h2&gt;&#xA;&lt;p&gt;There were many templates along the lines of&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-django&#34; data-lang=&#34;django&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Some action by &amp;lt;span class=&amp;#34;fw-semibold&amp;#34;&amp;gt;&lt;span style=&#34;color:#75715e&#34;&gt;{{&lt;/span&gt; event.actor&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;user_display_name&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;}}&lt;/span&gt;&amp;lt;/span&amp;gt;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Dataset delivered from &amp;lt;span&amp;gt;&lt;span style=&#34;color:#75715e&#34;&gt;{{&lt;/span&gt; dataset.supplier.name &lt;span style=&#34;color:#75715e&#34;&gt;}}&lt;/span&gt;&amp;lt;/span&amp;gt;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To be safe I wanted the values to be hidden by default and only reveal them, if&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;the actor is the authenticated user or&lt;/li&gt;&#xA;&lt;li&gt;the user has other groups than the one named &lt;em&gt;Consultant&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;the-redaction-approach&#34;&gt;The Redaction Approach&lt;/h2&gt;&#xA;&lt;p&gt;I wanted to redact the text by rendering a replacement string and apply the &lt;code&gt;.redacted&lt;/code&gt; CSS class.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;redacted&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;filter&lt;/span&gt;: blur(&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;px&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;user-select&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;none&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pointer-events: &lt;span style=&#34;color:#66d9ef&#34;&gt;none&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That results in the following HTML, which renders as a blurred bar. Sneaky people using the DOM inspector to look up the&#xA;value are left in the dark, too.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Some action by &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fw-semibold redacted&amp;#34;&lt;/span&gt;&amp;gt;Nope&amp;amp;nbsp;Joe&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Dataset delivered from &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;redacted&amp;#34;&lt;/span&gt;&amp;gt;Nope&amp;amp;nbsp;Joe&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;the-template-tag&#34;&gt;The Template Tag&lt;/h2&gt;&#xA;&lt;p&gt;Migrating the code to a custom template tag allowed me to uniformly redact users and arbitrary strings based on the&#xA;current request and business requirements. First I made sure that we actually have access a request available to prevent&#xA;any undefined behaviour later.&lt;/p&gt;&#xA;&lt;p&gt;Continuing the flow, I am checking the users group membership and override the permission, if we get a user instance,&#xA;which actually is the user themself. As a small bonus I was able to apply the &lt;code&gt;user_display_name&lt;/code&gt; within the tag, which&#xA;again spared some repetition in the templates.&lt;/p&gt;&#xA;&lt;p&gt;Most importantly: based on the permission check, I replaced the value to some dummy string and added the &lt;code&gt;.redacted&lt;/code&gt; CSS&#xA;class.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; typing &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Union&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; django &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; template&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; django.contrib.auth.models &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; User&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; django.utils.html &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; format_html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; django_backoffice.templatetags.backoffice_tags &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; user_display_name&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; thetool.groups.group_consultant &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; GroupConsultant&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;register &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; template&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Library()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@register.simple_tag&lt;/span&gt;(takes_context&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;conditionally_redacted&lt;/span&gt;(context, value: Union[str, User], &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;css_classes):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    request &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; context&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;request&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; request:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Exception&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Pass a request to all templates containing the conditionally_redacted template tag&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    allow_unredacted &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; (GroupConsultant&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;is_only_group(request) &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; request&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;thetool_groups &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; [])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; isinstance(value, User):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        allow_unredacted &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; allow_unredacted &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; value &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; request&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;user&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; user_display_name(value)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; allow_unredacted:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        css_classes &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;redacted&amp;#34;&lt;/span&gt;,)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Nope&amp;amp;nbsp;Joe&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; format_html(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;span&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;/span&amp;gt;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        format_html(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; class=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(css_classes)) &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; css_classes &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using that template tag all redaction is done behind the scenes. Neat.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-django&#34; data-lang=&#34;django&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;{%&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;load&lt;/span&gt; common &lt;span style=&#34;color:#75715e&#34;&gt;%}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Some action by &lt;span style=&#34;color:#75715e&#34;&gt;{%&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;conditionally_redacted&lt;/span&gt; event.actor &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fw-semibold&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;%}&lt;/span&gt;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Dataset delivered from &lt;span style=&#34;color:#75715e&#34;&gt;{%&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;conditionally_redacted&lt;/span&gt; dataset.supplier.name &lt;span style=&#34;color:#75715e&#34;&gt;%}&lt;/span&gt;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;the-happy-dennis&#34;&gt;The Happy Dennis&lt;/h2&gt;&#xA;&lt;p&gt;I am happy about that implementation as I can easily assert its logic in unit tests and do not have to scatter the same&#xA;checks over and over again through different views and templates.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Tricking Django&#39;s title Filter</title>
      <link>https://stritzke.me/posts/2025/10/tricking-djangos-title-filter/</link>
      <pubDate>Wed, 08 Oct 2025 12:51:00 +0200</pubDate>
      <guid>https://stritzke.me/posts/2025/10/tricking-djangos-title-filter/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://docs.djangoproject.com/en/5.2/ref/templates/builtins/#title&#34;&gt;Django&amp;rsquo;s &lt;code&gt;title&lt;/code&gt; filter&lt;/a&gt; is often used to prettify strings in templates, for example, when rendering&#xA;model verbose names in an admin or API interface. When handling abbreviations it can be a bit too eager in capitalising,&#xA;though.&lt;/p&gt;&#xA;&lt;p&gt;What happens if you apply the filter to the following API key model?&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SupplierAPIKey&lt;/span&gt;(AbstractAPIKey):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Meta&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        verbose_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;supplier API key&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        verbose_name_plural &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;supplier API keys&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It capitalises each word in the string, resulting in &lt;code&gt;Supplier Api Key&lt;/code&gt;. Ugh&amp;hellip; That hurts my brain.&lt;/p&gt;&#xA;&lt;p&gt;Unicode to the rescue! The zero-width space (&lt;code&gt;\u200b&lt;/code&gt;) acts like a regular space for text processing — but, well, has&#xA;no width during rendering. By inserting it within the acronym, we can trick the &lt;code&gt;title&lt;/code&gt; filter into keeping our&#xA;uppercase characters as they are treated as individual words.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;supplier A&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\u200b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;P&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\u200b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;I key&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Be aware that these characters are still rendered and present. If you copy the following text and insert into a proper&#xA;editor, which shows non-printing characters, they show up: &lt;code&gt;Supplier A​P​I Key&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;May your strings be capitalised to your taste.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Testing Django Migrations with PostgreSQL and Foreign Keys</title>
      <link>https://stritzke.me/posts/2025/10/testing-django-migrations-with-postgresql-and-foreign-keys/</link>
      <pubDate>Wed, 01 Oct 2025 13:15:59 +0200</pubDate>
      <guid>https://stritzke.me/posts/2025/10/testing-django-migrations-with-postgresql-and-foreign-keys/</guid>
      <description>&lt;p&gt;The incredibly helpful blogpost &lt;a href=&#34;https://www.caktusgroup.com/blog/2016/02/02/writing-unit-tests-django-migrations/&#34;&gt;Writing Unit Tests for Django Migrations&lt;/a&gt; delivers what the title&#xA;promises. Making testing of &lt;code&gt;migrations.RunPython&lt;/code&gt; feasible and easy &amp;hellip; as long as you don&amp;rsquo;t have to fill foreign key&#xA;relations on PostgreSQL.&lt;/p&gt;&#xA;&lt;p&gt;Following the suggested approach, you&amp;rsquo;ll create records to be migrated in the &lt;code&gt;setUpBeforeMigration&lt;/code&gt; method.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TestLeadSourceMigration&lt;/span&gt;(TestMigrations):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    migrate_from &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0056_rename_creator_leadsource_created_by_user&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    migrate_to &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0057_leadsource_channel_and_more&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;setUpBeforeMigration&lt;/span&gt;(self, apps):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Lead &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; apps&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_model(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;leadtool&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Lead&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        LeadSource &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; apps&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_model(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;leadtool&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LeadSource&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        lead &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Lead&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;objects&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;create(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            identifier&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            first_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Jane&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            last_name&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Doe&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            email&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;jane.doe@example.com&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        LeadSource&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;objects&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;create(lead&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;lead)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;test_something&lt;/span&gt;(self):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        [&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running this test will not succeed.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;django.db.utils.OperationalError: cannot ALTER TABLE &amp;#34;leadtool_leadsource&amp;#34; because it has pending trigger events&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The suggested &lt;code&gt;TestMigrations&lt;/code&gt; class is a subclass of &lt;code&gt;TestCase&lt;/code&gt;, which executes each test method and associated&#xA;lifecycle methods like &lt;code&gt;setUp&lt;/code&gt;, and by extension &lt;code&gt;setUpBeforeMigration&lt;/code&gt;, in a single transaction. The pending foreign&#xA;key trigger events, prevent the &lt;code&gt;ALTER TABLE&lt;/code&gt; and any other DDL of the succeeding migration to run.&lt;/p&gt;&#xA;&lt;p&gt;We have a trivial fix though: inherit from &lt;code&gt;TransactionTestCase&lt;/code&gt;. Now the record creation is committed to the database.&#xA;Note that the database is reset to a known state by truncating all database tables instead of just rolling back a&#xA;transaction, which is slower. Well worth it, though!&lt;/p&gt;&#xA;&lt;p&gt;PS: Could we please take a moment to appreciate how well the wisdom of that 9.5 year old post held up?&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Clean Django Storage Backend Configuration</title>
      <link>https://stritzke.me/posts/2025/09/clean-django-storage-backend-configuration/</link>
      <pubDate>Thu, 25 Sep 2025 21:30:43 +0200</pubDate>
      <guid>https://stritzke.me/posts/2025/09/clean-django-storage-backend-configuration/</guid>
      <description>&lt;p&gt;TIL: You are able to pass arbitrary configuration values via the &lt;code&gt;OPTIONS&lt;/code&gt; key to Django storage backends. They are&#xA;passed as keyword arguments to the &lt;code&gt;__init__&lt;/code&gt; method of the storage backend.&lt;/p&gt;&#xA;&lt;p&gt;That fact is pointed out in &lt;a href=&#34;https://docs.djangoproject.com/en/5.2/ref/settings/#storages&#34;&gt;the Django &lt;code&gt;STORAGES&lt;/code&gt; documentation&lt;/a&gt; and I apparently even used that&#xA;before in &lt;a href=&#34;https://github.com/kition-dev/djangodefaults&#34;&gt;kition-dev/djangodefaults&lt;/a&gt;&amp;hellip; Using these &lt;code&gt;OPTIONS&lt;/code&gt; I was able to turn&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;STATICFILES_STORAGE_HASH_EXCLUSIONS &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;css/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;images/&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;STORAGES &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;staticfiles&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BACKEND&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;myproject.storage.SelectiveManifestStaticFilesStorage&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip; into &amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;STORAGES &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;staticfiles&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BACKEND&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;myproject.storage.SelectiveManifestStaticFilesStorage&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OPTIONS&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hash_exclusions&amp;#34;&lt;/span&gt;: [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;css/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;images/&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Having got rid of that dangling configuration value loosely floating in the settings is a win for me. As a bonus,&#xA;overriding the static files storage in the test settings discards the configuration options, too. Sweet.&lt;/p&gt;&#xA;&lt;p&gt;If your custom storage backend accepts keyword arguments, put them under &lt;code&gt;OPTIONS&lt;/code&gt; — it keeps settings cohesive and&#xA;easier to override in different environments.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Dynamic Vue.js configuration</title>
      <link>https://stritzke.me/posts/2021/10/dynamic-vue.js-configuration/</link>
      <pubDate>Wed, 27 Oct 2021 22:00:00 +0200</pubDate>
      <guid>https://stritzke.me/posts/2021/10/dynamic-vue.js-configuration/</guid>
      <description>&lt;p&gt;You are building a web application and want to easily swap an API endpoint URL, set API tokens or just don&amp;rsquo;t want to&#xA;trigger a build pipeline for changing that one string - you need dynamic configuration.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s get right to it. Here are three ways of doing dynamic configuration using Vue.js.&lt;/p&gt;&#xA;&lt;h2 id=&#34;html-data--attributes&#34;&gt;HTML &lt;code&gt;data-*&lt;/code&gt; attributes&lt;/h2&gt;&#xA;&lt;p&gt;Utilise HTML data-attributes to pass configuration values to your Vue app.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#a6e22e&#34;&gt;data-some-api-key&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ey.cafebabe&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &lt;span style=&#34;color:#a6e22e&#34;&gt;data-base-url&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://example.org&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Within your &lt;code&gt;main.js&lt;/code&gt; you are able to access these values via &lt;code&gt;document.querySelector(&amp;quot;#app&amp;quot;).dataset.someApiKey;&lt;/code&gt;. All&#xA;data attribute values are contained as camel cased properties of the &lt;code&gt;dataset&lt;/code&gt; property.&lt;/p&gt;&#xA;&lt;p&gt;From there you are able to pass these values to your main component, pass them to your Vuex store or do whatever else&#xA;these values are intended for.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dataset&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; document.&lt;span style=&#34;color:#a6e22e&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#app&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;dataset&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Vue&lt;/span&gt;({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;render&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;h&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;h&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;App&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;store&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createStore&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dataset&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;baseUrl&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;dataset&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;someApiKey&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}).&lt;span style=&#34;color:#a6e22e&#34;&gt;$mount&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;#app&amp;#39;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;when-to-use-this-approach&#34;&gt;When to use this approach?&lt;/h3&gt;&#xA;&lt;p&gt;This kind of configuration is easy to understand for non-tech people. Imagine creating something like the Google&#xA;tracking code: You would distribute your app by instructing people to paste the HTML snippet above, accompanied by a&#xA;script tag and tell them to insert their site identifier and customer token. People like it, if they understand and do&#xA;stuff by themselves!&lt;/p&gt;&#xA;&lt;p&gt;Also, this approach is useful, if a backend templating engine is serving the HTML in which the Vue app is included. In&#xA;that case you can use the configuration mechanism of your backend framework to configure the frontend application.&lt;/p&gt;&#xA;&lt;p&gt;When running in a Docker Container, the same mechanism can be utilised through an entrypoint script that reads&#xA;environment variables and inserts them in the HTML data attributes.&lt;/p&gt;&#xA;&lt;h2 id=&#34;configjs&#34;&gt;&lt;code&gt;config.js&lt;/code&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Start by creating a config file like below. Store it as &lt;code&gt;public/config.js&lt;/code&gt; so it is not included in your Webpack build&#xA;and served as a static file.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;config&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (() =&amp;gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;someApiKey&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ey.cafebabe&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;baseUrl&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://example.org&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Include the config through a script tag.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;html&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;lang&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;%= BASE_URL %&amp;gt;config.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- built files will be auto injected --&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When reading the config values, make sure to define a default value as there is no guarantee that the &lt;code&gt;config.js&lt;/code&gt; will&#xA;be loaded in all circumstances.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;process&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;env&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;VUE_APP_SOME_API_KEY&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// eslint-disable-next-line no-undef&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;typeof&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;config&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;undefined&amp;#34;&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// eslint-disable-next-line no-undef&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;someApiKey&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;when-to-use-this-approach-1&#34;&gt;When to use this approach?&lt;/h3&gt;&#xA;&lt;p&gt;Use this approach, if you want to provide the configuration in a file external of the application. You would be able to&#xA;change the &lt;code&gt;config.js&lt;/code&gt; on your web server, provide it via deployment tools or generate it from environment variables in&#xA;a container entrypoint script.&lt;/p&gt;&#xA;&lt;h2 id=&#34;remote-configjs&#34;&gt;Remote config(.js)&lt;/h2&gt;&#xA;&lt;p&gt;This one is similar to the previous case, where the &lt;code&gt;config.js&lt;/code&gt; is distributed alongside the application. Instead of&#xA;placing the file on the same web server as the application, you call an external location, where the config resides.&lt;/p&gt;&#xA;&lt;p&gt;I am feeling almost stupid to include this approach, but there are some differences in the usage. For one, you have to&#xA;be careful about security. Make sure that only trusted parties are able to change the config and no one is providing a&#xA;malicious config to you. For another, you are able to use external config systems like Spring Cloud Config, Consul, etc.&#xA;In these cases you would most likely process some structured data format like JSON though.&lt;/p&gt;&#xA;&lt;h3 id=&#34;when-to-use-this-approach-2&#34;&gt;When to use this approach?&lt;/h3&gt;&#xA;&lt;p&gt;Use this approach, if you have a configuration management system in place and want to use it for the frontend&#xA;application, too.&lt;/p&gt;&#xA;&lt;p&gt;Also, this approach might prove useful, if there are some restrictions on the deployment itself. Maybe you are not able&#xA;to change environment variables, the HTML or trigger a new deployment? This approach is for you.&lt;/p&gt;&#xA;&lt;h2 id=&#34;do-you-really-need-dynamic-runtime-configuration&#34;&gt;Do you really need dynamic runtime configuration?&lt;/h2&gt;&#xA;&lt;p&gt;Before implementing the runtime configuration think twice: is the added complexity really worth the configuration need?&lt;/p&gt;&#xA;&lt;p&gt;Can your configuration needs be dealt with via &lt;code&gt;.env&lt;/code&gt; files? That would make things way easier. Put your development&#xA;configuration values into &lt;code&gt;.env&lt;/code&gt; your production ones in &lt;code&gt;.env.production&lt;/code&gt; and let your build and distribution process&#xA;take care of the configuration.&lt;/p&gt;&#xA;&lt;p&gt;If this suffices, go that direction! It is straight forward, everyone knows how to find the values and everything stays&#xA;predictable.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Sensible Solidity testing via Go Ethereum</title>
      <link>https://stritzke.me/posts/2021/10/sensible-solidity-testing-via-go-ethereum/</link>
      <pubDate>Wed, 13 Oct 2021 09:00:00 +0200</pubDate>
      <guid>https://stritzke.me/posts/2021/10/sensible-solidity-testing-via-go-ethereum/</guid>
      <description>&lt;p&gt;You have built a smart contract, &lt;a href=&#34;https://hardhat.org/tutorial/testing-contracts.html&#34;&gt;tested it via unit tests&lt;/a&gt;, explored it &lt;a href=&#34;https://d12s.ventures/blog/2021/10/using-block-explorers-with-hardhat/&#34;&gt;via Metamask and a block explorer&lt;/a&gt; and now want to deploy to the Mainnet. Didn&amp;rsquo;t you forget something?&lt;/p&gt;&#xA;&lt;p&gt;Until now you most likely tested your Solidity code via a local network like &lt;code&gt;hardhat node&lt;/code&gt;, &lt;code&gt;ganache-cli&lt;/code&gt; or the Ganache UI. Those are very convenient and fast, but are not the same thing as the real network. In this post we will explore how to test your code using the &lt;a href=&#34;https://geth.ethereum.org&#34;&gt;&amp;ldquo;official Go implementation of the Ethereum protocol&amp;rdquo; called &lt;code&gt;geth&lt;/code&gt;&lt;/a&gt;, which enables us to be much closer to the Mainnet.&lt;/p&gt;&#xA;&lt;p&gt;I will be using Hardhat on macOS within &lt;code&gt;zsh&lt;/code&gt; in my descriptions, but similar workflows exist for other toolchains.&lt;/p&gt;&#xA;&lt;h2 id=&#34;start-a-local-network-via-geth&#34;&gt;Start a local network via Geth&lt;/h2&gt;&#xA;&lt;p&gt;If you are looking for a very quick local setup and are fine with having only one account available, &lt;code&gt;geth --dev&lt;/code&gt; is a solid option. The chain does not persist, if the command is terminated.&lt;/p&gt;&#xA;&lt;p&gt;In most cases you will need more though: multiple accounts, account locking and unlocking, mining controls, timing changes, etc. All of these are possible through &lt;code&gt;geth&lt;/code&gt;. Follow these steps to create your  &lt;code&gt;geth&lt;/code&gt; based local network.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#install-geth&#34;&gt;Install &lt;code&gt;geth&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#create-accounts&#34;&gt;Create accounts&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#genesis-block-configuration&#34;&gt;Create the Genesis block configuration and initialise the network&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#start-the-network&#34;&gt;Start the network&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#cleanup&#34;&gt;Cleanup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;install-geth&#34;&gt;Install &lt;code&gt;geth&lt;/code&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Follow the extensive &lt;a href=&#34;https://geth.ethereum.org/docs/install-and-build/installing-geth&#34;&gt;Installing &lt;code&gt;geth&lt;/code&gt;&lt;/a&gt; instructions in the documentation.&lt;/p&gt;&#xA;&lt;h3 id=&#34;create-accounts&#34;&gt;Create accounts&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;geth&lt;/code&gt; handles encrypted private keys only. Within the local development environment we (oftentimes) don&amp;rsquo;t really care about key security. To handle passwords create a file named &lt;code&gt;passwords&lt;/code&gt; containing the same password on each line. Create as many lines as you want to create accounts.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# passwords&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;secret&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;secret&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Execute the &lt;code&gt;geth account new&lt;/code&gt; command once for every account you need.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;geth account new --datadir data --password passwords&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;genesis-block-configuration&#34;&gt;Genesis Block configuration&lt;/h3&gt;&#xA;&lt;p&gt;Next we have to instruct &lt;code&gt;geth&lt;/code&gt; how our chain should behave. This is done via the genesis block.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;config&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;chainId&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;homesteadBlock&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;eip150Block&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;eip155Block&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;eip158Block&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;byzantiumBlock&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;constantinopleBlock&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;petersburgBlock&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;clique&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;period&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;epoch&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30000&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;difficulty&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;gasLimit&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;8000000&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;extradata&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0x0000000000000000000000000000000000000000000000000000000000000000&amp;lt;insert signer key without 0x prefix&amp;gt;0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;alloc&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;c736af4e3dd4e0b463c3cbfecc9ab84705b69d8b&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;balance&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;40000000000000000000&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;ba4b47ee3abdc81531304bb09d45a085fb59c6ce&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;balance&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;40000000000000000000&amp;#34;&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notable properties:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Use the &lt;code&gt;clique&lt;/code&gt; consensus algorithm, which is proof-of-authority and spares us to execute proof-of-work mining. Read more on &lt;a href=&#34;https://geth.ethereum.org/docs/interface/private-network#choosing-a-consensus-algorithm&#34;&gt;choosing a consensus algorithm&lt;/a&gt; in the documentation.&lt;/li&gt;&#xA;&lt;li&gt;The &lt;code&gt;extradata&lt;/code&gt; property needs to contain the account used to sign blocks. I recommend to use the first account as the signing account. This selection works well with most framework assumptions. Insert the signing account public key without the &lt;code&gt;0x&lt;/code&gt; prefix in the padded string.&lt;/li&gt;&#xA;&lt;li&gt;Add one entry per account in the &lt;code&gt;alloc&lt;/code&gt; property to assign an initial balance to the respective account. The value is specified in wei. (I find it very useful to execute &lt;code&gt;web3.toWei(10, &amp;quot;ether&amp;quot;)&lt;/code&gt; to convert Ether to wei, which outputs &lt;code&gt;10000000000000000000&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Initialise the network by running &lt;code&gt;geth init --datadir data genesis.json&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;start-the-network&#34;&gt;Start the network&lt;/h3&gt;&#xA;&lt;p&gt;Execute the following command to start the private network. Replace and insert all public keys of the accounts you want to unlock in the &lt;code&gt;--unlock&lt;/code&gt; flag. Remember that your &lt;code&gt;passwords&lt;/code&gt; file needs to have at least as many lines has you want to unlock accounts.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;geth --datadir data --nodiscover --mine --fakepow &lt;span style=&#34;color:#ae81ff&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  --http --http.addr 0.0.0.0 --http.vhosts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt; --http.api &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;eth,net,web3,txpool,debug&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  --allow-insecure-unlock --password data/passwords &lt;span style=&#34;color:#ae81ff&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  --unlock 0xc736af4e3dd4e0b463c3cbfecc9ab84705b69d8b,...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notable properties:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The &lt;code&gt;--nodiscover&lt;/code&gt; flag makes sure that our node doesn&amp;rsquo;t search for peers to sync&lt;/li&gt;&#xA;&lt;li&gt;The &lt;code&gt;--mine&lt;/code&gt; flag enables block creation on our node. In combination with the &lt;code&gt;--fakepow&lt;/code&gt; flag we disable proof-of-work mining.&lt;/li&gt;&#xA;&lt;li&gt;The &lt;code&gt;--http.*&lt;/code&gt; flags enables us to (1) run &lt;code&gt;geth attach&lt;/code&gt; via the network and (2) access the chain via a Docker based Blockscout instance.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;cleanup&#34;&gt;Cleanup&lt;/h3&gt;&#xA;&lt;p&gt;In the following commands we used a directory named &lt;code&gt;data&lt;/code&gt; as the storage location. Just remove it once you are done.&lt;/p&gt;&#xA;&lt;h2 id=&#34;run-your-test-suite&#34;&gt;Run your test suite&lt;/h2&gt;&#xA;&lt;p&gt;Now that the local &lt;code&gt;geth&lt;/code&gt; network is running, the actual testing is straight forward. First, within the &lt;code&gt;hardhat.config.js&lt;/code&gt; add a new network.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;exports&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;networks&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;geth&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://127.0.0.1:8545&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Having done that, execute your test suite via &lt;code&gt;npx hardhat test --network geth&lt;/code&gt;. Do your tests also work on the official Go Ethereum implementation? Yes, that is great. No? Good you caught that before deploying to Mainnet.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npx hardhat test --network geth&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Distribution Wallet&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    deployment&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ✓ should set the correct owner&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; passing &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;194ms&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you need to analyse some issues or want to dig deeper, start the Geth JavaScript console via &lt;code&gt;geth attach http://127.0.0.1:8545/&lt;/code&gt;, which allows you to execute the &lt;code&gt;web3&lt;/code&gt;, &lt;code&gt;eth&lt;/code&gt; and more commands.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ geth attach http://127.0.0.1:8545/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Welcome to the Geth JavaScript console!&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;instance: Geth/v1.10.8-stable/darwin-amd64/go1.16.6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;coinbase: 0xc736af4e3dd4e0b463c3cbfecc9ab84705b69d8b&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;at block: &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Tue Oct &lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2021&lt;/span&gt; 09:53:25 GMT+0200 &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;CEST&lt;span style=&#34;color:#f92672&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; modules: debug:1.0 eth:1.0 net:1.0 rpc:1.0 txpool:1.0 web3:1.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;To exit, press ctrl-d&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; web3.toWei&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;10, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ether&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;10000000000000000000&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; eth.accounts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0xc736af4e3dd4e0b463c3cbfecc9ab84705b69d8b&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0xd8508d13c889728420103d6025761dfd98f43c36&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0x1a30d4f3db1d4781daf11398b55c62001cbef590&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0x3057d5b4f3ee1a6a56965af6708736fce858bc59&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0xba4b47ee3abdc81531304bb09d45a085fb59c6ce&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; eth.getBalance&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;eth.accounts&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;1&lt;span style=&#34;color:#f92672&#34;&gt;])&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;45999979000000000000&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; web3.fromWei&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;eth.getBalance&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;eth.accounts&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;1&lt;span style=&#34;color:#f92672&#34;&gt;]))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;45.999979&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;bonus-block-explorer&#34;&gt;Bonus: Block Explorer&lt;/h2&gt;&#xA;&lt;p&gt;In the post &lt;a href=&#34;https://d12s.ventures/blog/2021/10/using-block-explorers-with-hardhat/&#34;&gt;Using Block Explorers with Hardhat&lt;/a&gt; we tried different approaches to visually explore the local network. That worked reasonably well.&lt;/p&gt;&#xA;&lt;p&gt;As our local network is now run through &lt;code&gt;geth&lt;/code&gt;, compatibility is of no issue anymore. Fancy some &lt;a href=&#34;https://docs.blockscout.com&#34;&gt;Blockscout&lt;/a&gt;? Follow these steps:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Make sure &lt;a href=&#34;#start-a-local-network-via-geth&#34;&gt;the &lt;code&gt;geth&lt;/code&gt; network is running&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Clone the &lt;a href=&#34;https://github.com/blockscout/blockscout&#34;&gt;Blockscout repository&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Change to the &lt;code&gt;docker&lt;/code&gt; directory&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Execute the Makefile, which will build Blockscout on its first execution. (This took about 15 minutes on my machine)&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;COIN&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;ETH &lt;span style=&#34;color:#ae81ff&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ETHEREUM_JSONRPC_VARIANT&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;geth &lt;span style=&#34;color:#ae81ff&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ETHEREUM_JSONRPC_HTTP_URL&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;http://host.docker.internal:8545 &lt;span style=&#34;color:#ae81ff&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;make start&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Visit &lt;a href=&#34;http://localhost:4000&#34;&gt;http://localhost:4000&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;going-forward&#34;&gt;Going forward&lt;/h2&gt;&#xA;&lt;p&gt;Looking at this post, I am quite surprised how lengthy it got. Maybe it is time to implement a script &lt;code&gt;geth-dev&lt;/code&gt; that provides the &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;cleanup&lt;/code&gt; and &lt;code&gt;accounts&lt;/code&gt; commands? Maybe something to integrate this easily into Mocha based tests? Send an &lt;a href=&#34;mailto:dennis.stritzke@d12s.ventures?subject=Solidity%20testing%20via%20geth&#34;&gt;email to me&lt;/a&gt; and tell me what you think!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>We wont use Ethermine Polygon Payouts</title>
      <link>https://stritzke.me/posts/2021/09/we-wont-use-ethermine-polygon-payouts/</link>
      <pubDate>Wed, 29 Sep 2021 11:15:00 +0200</pubDate>
      <guid>https://stritzke.me/posts/2021/09/we-wont-use-ethermine-polygon-payouts/</guid>
      <description>&lt;p&gt;At D12S Ventures we run a small 612 MH/s Ethererum mining operation. With the rollout of &lt;a href=&#34;https://eips.ethereum.org/EIPS/eip-1559&#34;&gt;EIP-1559&lt;/a&gt; on 4th August 2021 &lt;a href=&#34;https://support.bitfly.at/support/solutions/articles/8000060967-ethermine-org-payout-policy&#34;&gt;Ethermine changed the payout policies&lt;/a&gt; for miners, where the miner has to pay the payout transaction fees. Ethermine argues that they won&amp;rsquo;t be able to cover these costs and therefore need to pass them on.&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t like this change, but it it necessary. Also, I support the EIP-1559 that made the payout policy change necessary in the first place. Ultimately, the Ethereum Platform will be better through it.&lt;/p&gt;&#xA;&lt;p&gt;The change of the payout policy also brought the possibility to use Level 2 Polygon (formerly Matic) for payouts. Let&amp;rsquo;s see why we wont use them for now.&lt;/p&gt;&#xA;&lt;p&gt;For context, find our mining strategy below:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;We operate two miners, totalling 612 MH/s&lt;/li&gt;&#xA;&lt;li&gt;Payouts happen every 0.1 ETH mined&lt;/li&gt;&#xA;&lt;li&gt;Every 14 days we liquidate half of the mined ETH in the past 14 days to Euro.&lt;/li&gt;&#xA;&lt;li&gt;The remaining mined ETH is kept until a preset price-threshold is reached, where we will again cash out half of the ETH.&lt;/li&gt;&#xA;&lt;li&gt;We will keep holding some portion of ETH indefinitely.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Let us walk through the different payout options.&lt;/p&gt;&#xA;&lt;h2 id=&#34;ethermine-payouts-before-eip-1559&#34;&gt;Ethermine Payouts before EIP-1559&lt;/h2&gt;&#xA;&lt;p&gt;Before EIP-1559 and the payout policy change we received all payouts using a 0.00002 ETH transaction fee. This is insanely low. So low in fact that it only makes 0.02% of the transaction value.&lt;/p&gt;&#xA;&lt;p&gt;Cashing out to Euro involved a transaction to Coinbase Pro, the trading fees and a fiat wire fee (SEPA transfer). In total this did cost about 0.0005 - 0.003 ETH and dependent on the current ETH-EUR exchange rate 1.00 - 3.53€.&lt;/p&gt;&#xA;&lt;h2 id=&#34;ethermine-payouts-via-polygon&#34;&gt;Ethermine Payouts via Polygon&lt;/h2&gt;&#xA;&lt;p&gt;To test drive Polygon we used Polygon payouts for one week. This is the first time for us to try the Polygon network and it seems great. Low fees in the price region where MetaMask shows 0.00€ for a transaction. Nice! Also, wallet integration and swapping is easy.&lt;/p&gt;&#xA;&lt;p&gt;BUT, remember the strategy above where we liquidate half of the profits every two weeks to Euro? How would we do this with Polygon? Search an Exchange that supports the Polygon Network. For us that’s Binance. Binance supports Polygon Network transfers for MATIC, so we swap our ETH to MATIC. Now trading the MATIC-EUR pair, we get the fiat needed for electricity, salaries etc. But getting these Euros to a bank account is only possible via Credit Card and a 1% withdrawal fee. That does not seem like a great deal&amp;hellip;&lt;/p&gt;&#xA;&lt;h2 id=&#34;we-wont-use-polygon-for-ethermine-payouts-probably-never&#34;&gt;We wont use Polygon (for Ethermine Payouts, probably never)&lt;/h2&gt;&#xA;&lt;p&gt;Right now Polygon payouts are more tedious for us than Mainnet payouts. We have to do an additional token swap and face a fiat payout fee. This brings the total cost of the payouts to the same level as the ones based on the Mainnet.&lt;/p&gt;&#xA;&lt;p&gt;Also, we would be holding ETH on Polygon and not on the Mainnet. Considering that the transfer to the Mainnet requires a lot of Gas - as it is a smart contract transaction - and &lt;a href=&#34;https://docs.polygon.technology/docs/develop/ethereum-matic/plasma/getting-started&#34;&gt;takes 7 days&lt;/a&gt;, I don&amp;rsquo;t now, if we every going to switch to Polygon.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Foundation – Building in Public</title>
      <link>https://stritzke.me/posts/2021/09/foundation-building-in-public/</link>
      <pubDate>Wed, 22 Sep 2021 11:45:00 +0200</pubDate>
      <guid>https://stritzke.me/posts/2021/09/foundation-building-in-public/</guid>
      <description>&lt;h2 id=&#34;building&#34;&gt;building&lt;/h2&gt;&#xA;&lt;p&gt;I am building software since 2006. 10 years of that professionally. For me, creating software means solving problems and creating opportunities for real people. This is what keeps me going.&lt;/p&gt;&#xA;&lt;p&gt;Most of what I build lives in the proprietary domain, solving specific problems for a business or product of that business. There is at least &lt;a href=&#34;https://github.com/dennisstritzke/ipsec_exporter&#34;&gt;one notable exception&lt;/a&gt;, which serves anyone who likes to use it.&lt;/p&gt;&#xA;&lt;p&gt;While building, I must learn. A lot.&lt;/p&gt;&#xA;&lt;h2 id=&#34;in-public&#34;&gt;in public&lt;/h2&gt;&#xA;&lt;p&gt;Fortunately, building software for 10 years in a multitude of roles, domains, programming languages and architectures made a lot of that work feel ‘ordinary’. Nothing worth talking and certainly not writing about. Only when speaking to other developers about my ‘ordinary work’, I realised that this work may very well be novel from other people’s eyes.&lt;/p&gt;&#xA;&lt;p&gt;Thinking about how I acquire new skills and knowledge it is through the ‘ordinary work’ of other people. You might have guessed it: the ‘ordinary work’ of other people seems extraordinary to me.&lt;/p&gt;&#xA;&lt;p&gt;That’s what this blog is about: sharing my ‘ordinary work’ and taking you along when learning new skills.&lt;/p&gt;&#xA;&lt;p&gt;Join me building stuff in public!&lt;/p&gt;&#xA;&lt;p&gt;You can expect learnings and stories about Crypto, cloud native software development, infrastructure and keeping things running in production. All from the perspective of a software developer and technical founder.&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
